JUnit 5 튜토리얼, 파트 1 : JUnit 5, Mockito 및 Hamcrest를 사용한 단위 테스트

JUnit 5는 Java로 단위 테스트를 개발하기위한 사실상의 새로운 표준입니다. 이 최신 버전은 Java 5의 제약 조건을 벗어 났고 Java 8의 많은 기능, 특히 람다 식 지원을 통합했습니다.

JUnit 5에 대한 두 부분으로 구성된이 소개의 전반부에서는 JUnit 5로 테스트를 시작합니다. JUnit 5를 사용하도록 Maven 프로젝트를 구성하는 방법, @Test@ParameterizedTest주석을 사용하여 테스트를 작성하는 방법 , 그리고 JUnit 5에서 새로운 라이프 사이클 어노테이션으로 작업하는 방법 또한 필터 태그 사용에 대한 간단한 예를 볼 수 있으며 JUnit 5를 써드 파티 어설 션 라이브러리 (이 경우 Hamcrest)와 통합하는 방법을 보여줄 것입니다. . 마지막으로, JUnit 5를 Mockito와 통합하는 방법에 대한 빠른 튜토리얼 소개를 받게되므로 복잡한 실제 시스템에 대해보다 강력한 단위 테스트를 작성할 수 있습니다.

다운로드 코드 가져 오기이 자습서의 예제에 대한 소스 코드를 가져옵니다. JavaWorld를 위해 Steven Haines가 작성했습니다.

테스트 주도 개발

일정 기간 동안 Java 코드를 개발해왔다면 테스트 기반 개발에 매우 ​​익숙 할 것이므로이 섹션을 간략하게 유지하겠습니다. 그러나 단위 테스트를 작성 하는 이유 와 개발자가 단위 테스트를 설계 할 때 사용하는 전략 을 이해하는 것이 중요합니다 .

TDD (테스트 주도 개발)는 코딩, 테스트 및 디자인을 결합하는 소프트웨어 개발 프로세스입니다. 애플리케이션의 품질을 향상시키는 것을 목표로하는 테스트 우선 접근 방식입니다. 테스트 주도 개발은 다음 라이프 사이클로 정의됩니다.

  1. 테스트를 추가하십시오.
  2. 모든 테스트를 실행하고 새로운 테스트가 실패하는지 관찰하십시오.
  3. 코드를 구현하십시오.
  4. 모든 테스트를 실행하고 새로운 테스트가 성공하는지 관찰하십시오.
  5. 코드를 리팩터링하십시오.

그림 1은이 TDD 수명주기를 보여줍니다.

스티븐 헤인즈

코드를 작성하기 전에 테스트를 작성하는 데는 두 가지 목적이 있습니다. 첫째, 해결하려는 비즈니스 문제에 대해 생각하게합니다. 예를 들어, 성공적인 시나리오는 어떻게 작동해야합니까? 어떤 조건이 실패해야합니까? 어떻게 실패해야합니까? 둘째, 먼저 테스트하면 테스트에 대한 자신감이 높아집니다. 코드를 작성한 후 테스트를 작성할 때마다 실제로 오류가 발생하는지 확인하기 위해 테스트를 중단해야합니다. 먼저 테스트를 작성하면이 추가 단계를 피할 수 있습니다.

행복한 경로에 대한 테스트를 작성하는 것은 일반적으로 쉽습니다. 좋은 입력이 주어지면 클래스는 결정적 응답을 반환해야합니다. 그러나 특히 복잡한 구성 요소에 대해 부정적인 (또는 실패) 테스트 케이스를 작성하는 것은 더 복잡 할 수 있습니다.

예를 들어 데이터베이스 저장소에 대한 테스트 작성을 고려하십시오. 행복한 경로에서 우리는 데이터베이스에 레코드를 삽입하고 생성 된 모든 키를 포함하여 생성 된 개체를 다시받습니다. 실제로는 다른 레코드가 이미 보유하고있는 고유 한 열 값이있는 레코드를 삽입하는 것과 같은 충돌 가능성도 고려해야합니다. 또한 사용자 이름이나 암호가 변경되어 저장소가 데이터베이스에 연결할 수없는 경우 어떻게됩니까? 전송 중에 네트워크 오류가 발생하면 어떻게됩니까? 정의 된 제한 시간 내에 요청이 완료되지 않으면 어떻게됩니까?

강력한 구성 요소를 빌드하려면 가능성이 있거나 가능성이없는 모든 시나리오를 고려하고 이에 대한 테스트를 개발하고 해당 테스트를 충족하는 코드를 작성해야합니다. 이 기사의 뒷부분에서 다양한 실패 시나리오를 생성하기위한 전략과 이러한 시나리오를 테스트하는 데 도움이되는 JUnit 5의 몇 가지 새로운 기능을 살펴볼 것입니다.

JUnit 5 채택

한동안 JUnit을 사용했다면 JUnit 5의 일부 변경 사항이 조정될 것입니다. 다음은 두 버전의 차이점에 대한 높은 수준의 요약입니다.

  • JUnit 5는 이제 org.junit.jupiter그룹에 패키지되어 Maven 및 Gradle 프로젝트에 포함하는 방법이 변경됩니다.
  • JUnit 4에는 JDK 5의 최소 JDK가 필요했습니다. JUnit 5에는 최소 JDK 8이 필요합니다.
  • JUnit 4 개에서는의가 @Before, @BeforeClass, @After, 및 @AfterClass주석은에 의해 대체되었다 @BeforeEach, @BeforeAll, @AfterEach,와 @AfterAll, 각각.
  • JUnit 4의 @Ignore주석이 주석으로 대체되었습니다 @Disabled.
  • @Category주석 이 주석으로 대체되었습니다 @Tag.
  • JUnit 5는 새로운 어설 션 메소드 세트를 추가합니다.
  • 러너는 확장 구현자를위한 새 API로 확장으로 대체되었습니다.
  • JUnit 5는 테스트 실행을 중지하는 가정을 도입합니다.
  • JUnit 5는 중첩 및 동적 테스트 클래스를 지원합니다.

이 기사에서는 이러한 새로운 기능의 대부분을 살펴 보겠습니다.

JUnit 5를 사용한 단위 테스트

Let's start simple, with an end-to-end example of configuring a project to use JUnit 5 for a unit test. Listing 1 shows a MathTools class whose method converts a numerator and denominator to a double.

Listing 1. An example JUnit 5 project (MathTools.java)

 package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException("Denominator must not be 0"); } return (double)numerator / (double)denominator; } }

We have two primary scenarios for testing the MathTools class and its method:

  • A valid test, in which we pass non-zero integers for the numerator and denominator.
  • A failure scenario, in which we pass a zero value for the denominator.

Listing 2 shows a JUnit 5 test class to test these two scenarios.

Listing 2. A JUnit 5 test class (MathToolsTest.java)

 package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } }

In Listing 2, the testConvertToDecimalInvalidDenominator method executes the MathTools::convertToDecimal method inside an assertThrows call. The first argument is the expected type of exception to be thrown. The second argument is a function that will throw that exception. The assertThrows method executes the function and validates that the expected type of exception is thrown.

The Assertions class and its methods

The org.junit.jupiter.api.Test annotation denotes a test method. Note that the @Test annotation now comes from the JUnit 5 Jupiter API package instead of JUnit 4's org.junit package. The testConvertToDecimalSuccess method first executes the MathTools::convertToDecimal method with a numerator of 3 and a denominator of 4, then asserts that the result is equal to 0.75. The org.junit.jupiter.api.Assertions class provides a set of static methods for comparing actual and expected results. The Assertions class has the following methods, which cover most of the primitive data types:

  • assertArrayEquals compares the contents of an actual array to an expected array.
  • assertEquals compares an actual value to an expected value.
  • assertNotEquals compares two values to validate that they are not equal.
  • assertTrue validates that the provided value is true.
  • assertFalse validates that the provided value is false.
  • assertLinesMatch compares two lists of Strings.
  • assertNull validates that the provided value is null.
  • assertNotNull validates that the provided value is not null.
  • assertSame validates that two values reference the same object.
  • assertNotSame validates that two values do not reference the same object.
  • assertThrows validates that the execution of a method throws an expected exception (you can see this in the testConvertToDecimalInvalidDenominator example above).
  • assertTimeout validates that a supplied function completes within a specified timeout.
  • assertTimeoutPreemptively validates that a supplied function completes within a specified timeout, but once the timeout is reached it kills the function's execution.

If any of these assertion methods fail, the unit test is marked as failed. That failure notice will be written to the screen when you run the test, then saved in a report file.

Using delta with assertEquals

When using float and double values in an assertEquals, you can also specify a delta that represents a threshold of difference between the two. In our example we could have added a delta of 0.001, in case 0.75 was actually returned as 0.750001.

Analyzing your test results

In addition to validating a value or behavior, the assert methods can also accept a textual description of the error, which can help you diagnose failures. For example:

 Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); 

The output will show the expected value of 0.75 and the actual value. It will also display the specified message, which can help you understand the context of the error. The difference between the two variations is that the first one always creates the message, even if it is not displayed, whereas the second one only constructs the message if the assertion fails. In this case, the construction of the message is trivial, so it doesn't really matter. Still, there is no need to construct an error message for a test that passes, so it's usually a best practice to use the second style.

Finally, if you're using an IDE like IntelliJ to run your tests, each test method will be displayed by its method name. This is fine if your method names are readable, but you can also add a @DisplayName annotation to your test methods to better identify the tests:

@Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); }

Running your unit test

In order to run JUnit 5 tests from a Maven project, you need to include the maven-surefire-plugin in the Maven pom.xml file and add a new dependency. Listing 3 shows the pom.xml file for this project.

Listing 3. Maven pom.xml for an example JUnit 5 project

  4.0.0 com.javaworld.geekcap junit5 jar 1.0-SNAPSHOT    org.apache.maven.plugins maven-compiler-plugin 3.8.1  8 8    org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4    junit5 //maven.apache.org   org.junit.jupiter junit-jupiter 5.6.0 test   

JUnit 5 dependencies

JUnit 5 packages its components in the org.junit.jupiter group and we need to add the junit-jupiter artifact, which is an aggregator artifact that imports the following dependencies:

  • junit-jupiter-api defines the API for writing tests and extensions.
  • junit-jupiter-engine 단위 테스트를 실행하는 테스트 엔진 구현입니다.
  • junit-jupiter-params 매개 변수화 된 테스트를 지원합니다.

다음으로 maven-surefire-plugin테스트를 실행하기 위해 빌드 플러그인 을 추가해야합니다 .

마지막으로 maven-compiler-plugin람다와 같은 Java 8 기능을 사용할 수 있도록 Java 8 이상 버전에을 포함해야합니다.

실행 해!

다음 명령을 사용하여 IDE 또는 Maven에서 테스트 클래스를 실행합니다.

mvn clean test

성공하면 다음과 유사한 출력이 표시됩니다.

 [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.832 s [INFO] Finished at: 2020-02-16T08:21:15-05:00 [INFO] ------------------------------------------------------------------------