JavaRush /Java Blog /Random-KO /Java 단위 테스트: 기술, 개념, 실습

Java 단위 테스트: 기술, 개념, 실습

Random-KO 그룹에 게시되었습니다
오늘날에는 테스트가 포함되지 않은 애플리케이션을 거의 찾을 수 없으므로 이 주제는 초보 개발자에게 그 어느 때보다 관련성이 높습니다. 테스트 없이는 아무데도 갈 수 없습니다. 광고로서 제 과거 기사를 보시길 권합니다. 그 중 일부는 테스트를 다루고 있습니다(그래도 기사는 매우 유용할 것입니다).
  1. MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트
  2. 다국어 애플리케이션 구현
  3. 파일을 애플리케이션에 저장하고 그에 관한 데이터를 데이터베이스에 저장
원칙적으로 어떤 유형의 테스트가 사용되는지 고려한 다음 단위 테스트에 대해 알아야 할 모든 것을 자세히 연구하겠습니다.

테스트 유형

테스트란 무엇입니까? Wiki에서는 다음과 같이 말합니다. " 테스트 또는 테스트는 시스템을 다양한 상황에 배치하고 관찰 가능한 변경 사항을 추적하여 시스템의 기본 프로세스를 연구하는 방법입니다." 즉, 이는 특정 상황에서 시스템이 올바르게 작동하는지 테스트하는 것입니다. 단위 테스트에 관한 모든 것: 방법, 개념, 연습 - 2그럼 어떤 유형의 테스트가 있는지 살펴보겠습니다.
  1. 단위 테스트는 시스템의 각 모듈을 개별적으로 테스트하는 테스트입니다. 이는 모듈과 같이 시스템에서 최소한으로 분할 가능한 부분인 것이 바람직합니다.

  2. 시스템 테스트는 애플리케이션의 더 큰 부분이나 시스템 전체의 작동을 테스트하는 높은 수준의 테스트입니다.

  3. 회귀 테스트는 새로운 기능이나 버그 수정이 애플리케이션의 기존 기능에 영향을 미치는지, 오래된 버그가 다시 나타나는지 확인하는 데 사용되는 테스트입니다.

  4. 기능 테스트는 애플리케이션의 일부가 사양, 사용자 스토리 등에 명시된 요구 사항을 준수하는지 확인하는 것입니다.

    기능 테스트 유형:

    • 시스템의 내부 구현에 대한 지식을 바탕으로 애플리케이션의 일부가 요구 사항을 준수하는지 확인하는 "화이트 박스" 테스트
    • 시스템의 내부 구현에 대한 지식 없이 애플리케이션의 일부가 요구 사항을 준수하는지 확인하는 "블랙 박스" 테스트입니다 .
  5. 성능 테스트는 특정 부하에서 시스템 또는 그 일부가 실행되는 속도를 결정하기 위해 작성된 테스트 유형입니다.
  6. 부하 테스트 - 표준 부하 하에서 시스템의 안정성을 확인하고 애플리케이션이 올바르게 작동하는 최대 피크를 찾기 위해 고안된 테스트입니다.
  7. 스트레스 테스트는 비표준 부하에서 응용 프로그램의 기능을 확인하고 시스템이 충돌하지 않는 최대 피크를 결정하기 위해 설계된 테스트 유형입니다.
  8. 보안 테스트 - 시스템 보안(해커, 바이러스 공격, 기밀 데이터에 대한 무단 액세스 및 기타 삶의 즐거움)을 확인하는 데 사용되는 테스트입니다.
  9. 현지화 테스트는 애플리케이션에 대한 현지화 테스트입니다.
  10. 사용성 테스트는 사용자의 사용성, 이해성, 매력, 학습성을 확인하는 것을 목표로 하는 테스트 유형입니다.
  11. 이 모든 것이 듣기에는 좋지만 실제로는 어떻게 작동합니까? 간단합니다. Mike Cohn의 테스트 피라미드가 사용됩니다. 단위 테스트에 관한 모든 것: 기술, 개념, 실습 - 4이것은 피라미드의 단순화된 버전입니다. 이제 더 작은 부분으로 나뉩니다. 그러나 오늘 우리는 변태하지 않고 가장 간단한 옵션을 고려할 것입니다.
    1. 단위 - 응용 프로그램의 다양한 계층에서 사용되는 단위 테스트로, 응용 프로그램의 가장 작은 분할 가능한 논리(예: 클래스, 그러나 대부분 메서드)를 테스트합니다. 이러한 테스트는 일반적으로 외부 로직에서 최대한 격리하려고 합니다. 즉, 애플리케이션의 나머지 부분이 표준 모드에서 작동하는 것처럼 보이게 하기 위한 것입니다.

      이러한 테스트는 작은 부분을 테스트하고 매우 가벼우며 많은 리소스를 소비하지 않기 때문에(다른 유형보다 더 많이) 항상 많은 테스트가 있어야 합니다(리소스란 RAM과 시간을 의미함).

    2. 통합 - 통합 테스트. 이는 시스템의 더 큰 부분, 즉 여러 논리 부분(여러 메서드 또는 클래스)의 조합인지 또는 외부 구성 요소 작업의 정확성인지 확인합니다. 일반적으로 이러한 테스트는 단위 테스트보다 더 무겁기 때문에 개수가 적습니다.

      통합 테스트의 예로, 데이터베이스에 연결하고 데이터베이스와 함께 작동하는 메서드가 올바르게 실행되는지 확인하는 것을 고려할 수 있습니다 .

    3. UI - 사용자 인터페이스의 작동을 확인하는 테스트입니다. 이는 애플리케이션의 모든 수준에서 논리에 영향을 미치므로 엔드투엔드(end-to-end)라고도 합니다. 일반적으로 해당 경로는 가장 무겁고 가장 필요한(사용되는) 경로를 확인해야 하기 때문에 그 수가 훨씬 적습니다.

      위 그림에서 우리는 삼각형의 여러 부분의 면적 비율을 볼 수 있습니다. 실제 작업에서 이러한 테스트 횟수는 거의 동일한 비율로 유지됩니다.

      오늘은 가장 많이 사용되는 테스트인 단위 테스트에 대해 자세히 살펴보겠습니다. 모든 자존심 있는 Java 개발자는 기본 수준에서 단위 테스트를 사용할 수 있어야 하기 때문입니다.

    단위 테스트의 주요 개념

    테스트 커버리지 (코드 커버리지)는 애플리케이션 테스트 품질에 대한 주요 평가 중 하나입니다. 테스트에 포함된 코드의 비율입니다(0-100%). 실제로 많은 사람들은 필요하지 않은 곳에 테스트를 추가하기 시작하기 때문에 동의하지 않는 이 비율을 쫓습니다. 예를 들어, 우리 서비스에는 추가 논리 없이 표준 CRUD(생성/가져오기/업데이트/삭제) 작업이 있습니다. 이러한 방법은 저장소와 함께 작동하는 레이어에 작업을 위임하는 순전히 중개자입니다. 이 상황에서 우리는 테스트할 것이 없습니다. 아마도 이 메소드가 Tao의 메소드를 호출하는지 여부일 수도 있지만 이는 심각하지 않습니다. 테스트 적용 범위를 평가하기 위해 일반적으로 JaCoCo, Cobertura, Clover, Emma 등의 추가 도구가 사용됩니다. 이 문제에 대한 더 자세한 연구를 위해 몇 가지 적절한 기사를 보관하십시오. TDD (테스트 중심 개발) - 테스트 중심 개발. 이 접근 방식에서는 먼저 특정 코드를 확인하는 테스트가 작성됩니다. 이는 블랙박스 테스트로 밝혀졌습니다. 우리는 입력에 무엇이 있는지 알고 출력에 어떤 일이 일어나야 하는지 알고 있습니다. 이렇게 하면 코드 중복이 방지됩니다. 테스트 중심 개발은 애플리케이션의 작은 기능 각각에 대한 테스트를 설계하고 개발하는 것부터 시작됩니다. TDD 접근 방식에서는 먼저 코드가 수행할 작업을 정의하고 확인하는 테스트가 개발됩니다. TDD의 주요 목표는 코드를 더 명확하고 단순하며 오류 없이 만드는 것입니다. 단위 테스트에 관한 모든 것: 방법, 개념, 연습 - 6이 접근 방식은 다음 구성 요소로 구성됩니다.
    1. 우리는 테스트를 작성하고 있습니다.
    2. 우리는 통과 여부에 관계없이 테스트를 실행합니다(모든 것이 빨간색임을 확인합니다. 놀라지 마세요. 이렇게 되어야 합니다).
    3. 이 테스트를 만족해야 하는 코드를 추가합니다(테스트 실행).
    4. 우리는 코드를 리팩토링합니다.
    단위 테스트는 테스트 자동화 피라미드에서 가장 작은 요소라는 사실을 바탕으로 TDD는 이를 기반으로 합니다. 단위 테스트의 도움으로 우리는 모든 클래스의 비즈니스 로직을 테스트할 수 있습니다. BDD(행동 중심 개발) - 행동을 통한 개발. 이 접근 방식은 TDD를 기반으로 합니다. 보다 구체적으로, 개발에 관련된 모든 사람을 위한 시스템 동작을 설명하는 명확한 언어(일반적으로 영어)로 작성된 예제를 사용합니다. 이 용어는 주로 테스터와 비즈니스 분석가에게 영향을 미치므로 더 깊이 다루지는 않겠습니다. 테스트 케이스 - 테스트 중인 코드의 구현을 확인하는 데 필요한 단계, 특정 조건 및 매개변수를 설명하는 스크립트입니다. Fixture 는 테스트 중인 메서드를 성공적으로 실행하는 데 필요한 테스트 환경의 상태입니다. 이는 사용된 조건 하에서 미리 결정된 개체 집합과 해당 동작입니다.

    테스트 단계

    테스트는 세 단계로 구성됩니다.
    1. 테스트할 데이터(픽스처) 지정.
    2. 테스트 중인 코드 사용(테스트 중인 메서드 호출)
    3. 결과를 확인하고 예상한 결과와 비교합니다.
    단위 테스트에 관한 모든 것: 방법, 개념, 실습 - 7테스트 모듈성을 보장하려면 애플리케이션의 다른 계층과 격리되어야 합니다. 이는 스텁, 모의 및 스파이를 사용하여 수행할 수 있습니다. 모의 개체는 사용자 정의할 수 있고(예: 각 테스트에 특정) 우리가 수신할 응답 형식으로 메서드 호출에 대한 기대치를 설정할 수 있는 개체입니다. 기대값 확인은 Mock 객체 호출을 통해 수행됩니다. 스텁 - 테스트 중 호출에 대해 유선 응답을 제공합니다. 또한 통화에 대한 정보(예: 매개변수 또는 통화 횟수)를 저장할 수도 있습니다. 이들은 때때로 스파이( Spy ) 라는 자체 용어로 불립니다 . 때때로 스텁모의 라는 용어가 혼동됩니다. 차이점은 스텁은 아무것도 확인하지 않고 주어진 상태만 시뮬레이션한다는 것입니다. 모의 객체는 기대치를 갖는 객체입니다. 예를 들어, 특정 클래스 메서드는 특정 횟수만큼 호출되어야 합니다. 즉, 테스트는 스텁으로 인해 중단되지 않지만 모의로 인해 중단될 수 있습니다.

    테스트 환경

    이제 사업을 시작하겠습니다. Java에 사용할 수 있는 여러 테스트 환경(프레임워크)이 있습니다. 그 중 가장 인기 있는 것은 JUnit과 TestNG입니다. 검토를 위해 다음을 사용합니다. 단위 테스트에 관한 모든 것: 방법, 개념, 연습 - 8JUnit 테스트는 테스트에만 사용되는 클래스에 포함된 메서드입니다. 클래스 이름은 일반적으로 끝에 +Test를 붙여 테스트 중인 클래스와 동일하게 지정됩니다. 예를 들어 CarService→ CarServiceTest입니다. Maven 빌드 시스템은 테스트 영역에 이러한 클래스를 자동으로 포함합니다. 실제로 이 클래스를 테스트 클래스라고 합니다. 기본 주석을 조금 살펴보겠습니다. @Test - 이 메서드를 테스트 메서드로 정의합니다(사실 이 주석으로 표시된 메서드는 단위 테스트입니다). @Before - 각 테스트 전에 실행될 메서드를 표시합니다. 예를 들어 클래스 테스트 데이터 채우기, 입력 데이터 읽기 등 @After - 각 테스트 후에 호출될 메서드 위에 배치됩니다(데이터 정리, 기본값 복원). @BeforeClass - 메소드 위에 위치하며 @Before와 유사합니다. 그러나 이 메서드는 특정 클래스에 대한 모든 테스트 전에 한 번만 호출되므로 정적이어야 합니다. 테스트 데이터베이스를 들어 올리는 등 보다 강력한 작업을 수행하는 데 사용됩니다. @AfterClass 는 @BeforeClass와 반대입니다. 특정 클래스에 대해 한 번 실행되지만 모든 테스트 후에 실행됩니다. 예를 들어 영구 리소스를 정리하거나 데이터베이스 연결을 끊는 데 사용됩니다. @Ignore - 아래 메서드는 비활성화되어 있으며 전체 테스트를 실행할 때 무시됩니다. 예를 들어 기본 방법이 변경되어 테스트를 다시 실행할 시간이 없는 경우와 같이 다양한 경우에 사용됩니다. 이러한 경우에는 설명(@Ignore("Some Description"))을 추가하는 것이 좋습니다. @Test (예상 = Exception.class) - 부정적인 테스트에 사용됩니다. 이는 오류가 발생한 경우 메서드가 어떻게 작동하는지 확인하는 테스트입니다. 즉, 테스트에서는 메서드가 일부 예외를 throw할 것으로 예상합니다. 이러한 메서드는 @Test 주석으로 표시되지만 오류가 발생합니다. @Test(timeout=100) - 메서드가 100밀리초 이내에 실행되는지 확인합니다. @Mock - 클래스는 주어진 객체를 모의 개체로 설정하기 위해 필드 위에 사용되며(이것은 Junit 라이브러리가 아니라 Mockito에서 가져온 것임) 필요한 경우 특정 상황에서 모의 ​​개체의 동작을 설정합니다. , 테스트 방법에서 직접. @RunWith(MockitoJUnitRunner.class) - 메서드가 클래스 위에 배치됩니다. 테스트를 실행하기 위한 버튼입니다. 실행기는 다를 수 있습니다. 예를 들어 MockitoJUnitRunner, JUnitPlatform, SpringRunner 등이 있습니다. JUnit 5에서는 @RunWith 주석이 더 강력한 @ExtendWith 주석으로 대체되었습니다. 결과를 비교하는 몇 가지 방법을 살펴보겠습니다.
    • assertEquals(Object expecteds, Object actuals)— 전송된 객체가 동일한지 확인합니다.
    • assertTrue(boolean flag)— 전달된 값이 true를 반환하는지 확인합니다.
    • assertFalse(boolean flag)— 전달된 값이 false를 반환하는지 확인합니다.
    • assertNull(Object object)– 객체가 null인지 확인합니다.
    • assertSame(Object firstObject, Object secondObject)— 전달된 값이 동일한 객체를 참조하는지 확인합니다.
    • assertThat(T t, Matcher<T> matcher)— t가 매처에 지정된 조건을 만족하는지 확인합니다.
    Assertj에는 유용한 비교 형식도 있습니다. assertThat(firstObject).isEqualTo(secondObject) 나머지는 위의 다른 변형이므로 여기서는 기본 방법에 대해 설명했습니다.

    테스트 실습

    이제 구체적인 예를 사용하여 위 자료를 살펴보겠습니다. 서비스 업데이트 방법을 테스트하겠습니다. dao 레이어는 기본값이므로 고려하지 않습니다. 테스트용 스타터를 추가해 보겠습니다.
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <version>2.2.2.RELEASE</version>
       <scope>test</scope>
    </dependency>
    따라서 서비스 클래스는 다음과 같습니다.
    @Service
    @RequiredArgsConstructor
    public class RobotServiceImpl implements RobotService {
       private final RobotDAO robotDAO;
    
       @Override
       public Robot update(Long id, Robot robot) {
           Robot found = robotDAO.findById(id);
           return robotDAO.update(Robot.builder()
                   .id(id)
                   .name(robot.getName() != null ? robot.getName() : found.getName())
                   .cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
                   .producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
                   .build());
       }
    }
    8 - 데이터베이스에서 업데이트된 객체 가져오기 9-14 - 수신 객체에 필드가 있는 경우 빌더를 통해 객체 생성 - 필드가 없으면 설정 - 데이터베이스에 있는 내용 그대로 두고 테스트를 살펴보세요.
    @RunWith(MockitoJUnitRunner.class)
    public class RobotServiceImplTest {
       @Mock
       private RobotDAO robotDAO;
    
       private RobotServiceImpl robotService;
    
       private static Robot testRobot;
    
       @BeforeClass
       public static void prepareTestData() {
           testRobot = Robot
                   .builder()
                   .id(123L)
                   .name("testRobotMolly")
                   .cpu("Intel Core i7-9700K")
                   .producer("China")
                   .build();
       }
    
       @Before
       public void init() {
           robotService = new RobotServiceImpl(robotDAO);
       }
    1 - Runner 4 - 모의 객체를 대체하여 dao 레이어에서 서비스를 분리합니다. 11 - 클래스에 대한 테스트 엔터티 설정(테스트 햄스터로 사용할 엔터티) 22 - 테스트할 서비스 객체 설정
    @Test
    public void updateTest() {
       when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
       when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
       Robot robotForUpdate = Robot
               .builder()
               .name("Vally")
               .cpu("AMD Ryzen 7 2700X")
               .build();
    
       Robot resultRobot = robotService.update(123L, robotForUpdate);
    
       assertNotNull(resultRobot);
       assertSame(resultRobot.getId(),testRobot.getId());
       assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
       assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
       assertEquals(resultRobot.getProducer(),testRobot.getProducer());
    }
    여기에서는 테스트가 세 부분으로 명확하게 구분되어 있습니다. 3-9 - 고정 장치 설정 11 - 테스트된 부분 실행 13-17 - 결과 확인 자세한 내용: 3-4 - moka dao에 대한 동작 설정 5 - 설정 표준 위에 업데이트할 인스턴스 11 - 메서드를 사용하고 결과 인스턴스 가져오기 13 - 0이 아닌지 확인 14 - 결과 ID와 지정된 메서드 인수 확인 15 - 이름이 업데이트되었는지 확인 16 - CPU 17로 결과를 살펴봅니다. - 업데이트 인스턴스 필드에 이를 설정하지 않았으므로 동일하게 유지되어야 합니다. 확인해 보겠습니다. 단위 테스트에 관한 모든 것: 방법, 개념, 연습 - 9시작합시다: 단위 테스트에 관한 모든 것: 기술, 개념, 실습 - 10테스트는 녹색입니다. 숨을 내쉴 수 ​​있습니다.) 요약하자면, 테스트는 코드 품질을 향상시키고 개발 프로세스를 더욱 유연하고 안정적으로 만듭니다. 수백 개의 클래스 파일로 소프트웨어를 재설계할 때 얼마나 많은 노력을 기울여야 하는지 상상해 보십시오. 이러한 모든 클래스에 대해 단위 테스트를 작성하면 자신감을 갖고 리팩토링할 수 있습니다. 그리고 가장 중요한 것은 개발 중에 오류를 쉽게 찾는 데 도움이 된다는 것입니다. 여러분, 오늘은 그게 전부입니다. 좋아요를 붓고 댓글을 작성하세요))) 단위 테스트에 관한 모든 것: 방법, 개념, 실습 - 11
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION