每个开发人员都应该知道的JUnit 4和5批注

A Summary of JUnit 4 & 5 Annotations with Examples

在撰写本文之前,我只知道一些常用的JUnit 4注释,例如

@RunWith 
@Test
@Before
@After
@BeforeClass
@AfterClass

您必须注释掉几次测试? 令我惊讶的是,有注释可以做到这一点。

@Ignore("Reason for ignoring")
@Disabled("Reason for disabling")

好吧,事实证明,还有一些其他注释,尤其是在JUnit 5中,可以帮助编写更好,更有效的测试。


What to expect?

在本文中,我将通过用法示例介绍以下注释。 本文的目的是向您介绍该批注,而不会详细介绍每个批注。

*Github中也提供了本文的所有示例。 请签出以下存储库。 *

GitHub logo rhamedy / junit-annotations-examples

JUnit 4 and 5 Annotations with Examples

本文的目标读者是任何级别的开发人员。

Alt Text

JUnit 4

The following JUnit 4 annotations will be covered

Alt Text

JUnit 5

The following JUnit 5 annotations are explained with examples

Alt Text


JUnit Dependencies

本文中的所有示例均使用以下JUnit依赖项进行了测试。

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'
testCompileOnly 'junit:junit:4.12'
testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.3.1'

Please check out the Github repository for more details.


JUnit Annotations Usage

让我们通过一个简短的用法示例逐一探讨JUnit 4批注

The Hello World of Unit Testing

的@测试批注用于将方法标记为测试。

public class BasicJUnit4Tests {
  @Test
  public void always_passing_test() {
    assertTrue("Always true", true);
  }
}

The Class-Level and Test-Level Annotations

诸如@课前和@下课以后是JUnit 4类级注释。

public class BasicJUnit4Tests {
  @BeforeClass
  public static void setup() {
    // Setup resource needed by all tests.
  }
  @Before
  public void beforeEveryTest() {
    // This gets executed before each test.
  }
  @Test
  public void always_passing_test() {
    assertTrue("Always true", true);
  }
  @After
  public void afterEveryTest() {
    // This gets executed after every test.
  }
  @AfterClass
  public static void cleanup() {
    // Clean up resource after all are executed.
  }
}

批注@BeforeAll和@毕竟是JUnit 5 equivalents和imported using the following statements.

// JUnit 5
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.AfterAll

Ignoring a Test vs. Assumption

测试被忽略@忽视批注或断言可以更改为假设,并且JUnit Runner将忽略失败的假设。

在处理服务器与本地时区之类的方案时会使用假设。 当假设失败时,AssumptionViolationException被抛出,JUnit运行器将忽略它。

public class BasicJUnit4Tests {
  @Ignore("Ignored because of a good reason")
  @Test
  public void test_something() {
    assertTrue("Always fails", false);
  }
}


Executing Tests in Order

通常,写顺序不可知单元测试是一个好习惯。

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class FixedMethodOrderingTests {
  @Test
  public void first() {}
  @Test
  public void second() {}
  @Test
  public void third() {}
}

除了按测试名称的升序排列外,方法排序允许默认和虚拟机级别排序。


Adding Timeout to Tests

单元测试通常具有快速的执行时间; 但是,在某些情况下,单元测试可能需要更长的时间。

在JUnit 4,@测试批注接受超时参数如下所示

import org.junit.Ignore;
import org.junit.Test;
public class BasicJUnit4Tests {
  @Test(timeout = 1)
  public void timeout_test() throws InterruptedException {
    Thread.sleep(2); // Fails because it took longer than 1 second.
  }
}

在JUnit 5,超时发生在声明级别

import static java.time.Duration.ofMillis;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import org.junit.jupiter.api.Test;
public class BasicJUnit5Tests {
  @Test
  public void test_timeout() {
    // Test takes 2 ms, assertion timeout in 1 ms
    assertTimeout(ofMillis(1), () -> {
      Thread.sleep(2);
    });
  }
}

有时,在所有测试中应用超时(包括@ BeforeEach /之前和@ AfterEach /之后也一样

public class JUnitGlobalTimeoutRuleTests {
  @Rule
  public Timeout globalTimeout = new Timeout(2, TimeUnit.SECONDS);
  @Test
  public void timeout_test() throws InterruptedException {
    while(true); // Infinite loop
  }
  @Test
  public void timeout_test_pass() throws InterruptedException {
    Thread.sleep(1);
  }
}

Using Rule with JUnit Tests

我发现@规则在编写单元测试时非常有帮助。 一种规则适用于以下

  • 超时-如上图所示ExpectedException临时文件夹错误收集器验证者

ExpectedException Rule

此规则可用于确保测试抛出预期的异常。 在JUnit 4,我们可以做以下事情

public class BasicJUnit4Tests {
  @Test(expected = NullPointerException.class)
  public void exception_test() {
    throw new IllegalArgumentException(); // Fail. Not NPE.
  }
}

在JUnit 5但是,可以通过以下声明来实现上述目的

public class BasicJUnit5Tests {
  @Test
  public void test_expected_exception() {
    Assertions.assertThrows(NumberFormatException.class, () -> {
      Integer.parseInt("One"); // Throws NumberFormatException
    });
  }
}

我们还可以定义一个规则在类级别并在测试中重用

public class JUnitRuleTests {
  @Rule
  public ExpectedException thrown = ExpectedException.none();
  @Test
  public void expectedException_inMethodLevel() {
    thrown.expect(IllegalArgumentException.class);
    thrown.expectMessage("Cause of the error");
    throw new IllegalArgumentException("Cause of the error");
  }
}

TemporaryFolder Rule

这个规则可以在测试的生命周期内创建和删除文件和文件夹。

public class TemporaryFolderRuleTests {
  @Rule
  public TemporaryFolder temporaryFolder = new TemporaryFolder();
  @Test
  public void testCreatingTemporaryFileFolder() throws IOException {
    File file = temporaryFolder.newFile("testFile.txt");
    File folder = temporaryFolder.newFolder("testFolder");
    String filePath = file.getAbsolutePath();
    String folderPath = folder.getAbsolutePath();

    File testFile = new File(filePath);
    File testFolder = new File(folderPath);
    assertTrue(testFile.exists());
    assertTrue(testFolder.exists());
    assertTrue(testFolder.isDirectory());
 }
}

ErrorCollector Rule

在执行单元测试期间,如果有很多断言,而第一个断言失败,则跳过后续声明,如下所示。

@Test
public void reportFirstFailedAssertion() {
  assertTrue(false); // Failed assertion. Report. Stop execution.
  assertFalse(true); // It's never executed.
}

如果我们可以获取所有失败的断言的列表并立即而不是一个接一个地修复它们,将很有帮助。 这是错误收集器规则可以帮助实现这一目标。

public class ErrorCollectorRuleTests {
  @Rule
  public ErrorCollector errorCollector = new ErrorCollector();

  @Test
  public void reportAllFailedAssertions() {
    errorCollector.checkThat(true, is(false));  // Fail. Continue
    errorCollector.checkThat(false, is(false)); // Pass. Continue
    errorCollector.checkThat(2, equalTo("a"));  // Fail. Report all
  }
}

There is also the Verifier Rule that I won't go into details, and you can read more about it here.

For more information on @ClassRule and the difference between the two, please see this Stackoverflow post.


JUnit Suites

JUnit套件可用于对测试类进行分组并一起执行。 这是一个例子

public class TestSuiteA {
  @Test
  public void testSuiteA() {}
}
public class TestSuiteB {
  @Test
  public void testSuiteB() {}
}

假设还有许多其他测试类,我们可以使用以下注释运行这两个或其中之一

@RunWith(Suite.class)
@Suite.SuiteClasses({TestSuiteA.class, TestSuiteB.class})
public class TestSuite {
  // Will run tests from TestSuiteA and TestSuiteB classes
}

以上将导致以下结果

Alt Text


Categories in JUnit 4

在JUnit 4,我们可以利用分类目录从执行中包括和排除一组测试。 我们可以使用标记界面创建任意数量的类别,如下所示

没有实现的接口称为标记器接口。

public interface CategoryA {}
public interface CategoryB {}

现在我们有两个类别,我们可以用一个或多个类别类型注释每个测试,如下所示

public class CategoriesTests {
  @Test
  public void test_categoryNone() {
    System.out.println("Test without any category");
    assert(false);
  }
  @Category(CategoryA.class)
  @Test
  public void test1() {
    System.out.println("Runs when category A is selected.");
    assert(true);
  }
  @Category(CategoryB.class)
  @Test
  public void test2() {
    System.out.println("Runs when category B is included.");
    assert(false);
  }
  @Category({CategoryA.class, CategoryB.class})
  @Test
  public void test3() {
    System.out.println("Runs when either of category is included.");
    assert(true);
  }
}

一个特殊的JUnit Runner叫做Categories.class用于执行这些测试

@RunWith(Categories.class)
@IncludeCategory(CategoryA.class)
@ExcludeCategory(CategoryB.class)
@SuiteClasses({CategoriesTests.class})
public class CategroyTestSuite {}

上面只会运行测试测试1,但是,如果我们删除以下条目,则两者测试1和测试3被执行。

@ExcludeCategory(CategoryB.class)

Tagging & Filtering Tests in JUnit 5

除了类别中JUnit 4,JUnit 5引入了标记和过滤测试的功能。 假设我们有以下内容

@Tag("development")
public class UnitTests {
  @Tag("web-layer")
  public void login_controller_test() {}
  @Tag("web-layer")
  public void logout_controller_test() {}
  @Tag("db-layer")
  @Tag("dao")
  public void user_dao_tests() {}
}

@Tag("qa")
public class LoadTests {
  @Tag("auth")
  @Test
  public void login_test() {}
  @Tag("auth")
  @Test
  public void logout_test() {}
  @Tag("auth")
  @Test
  public void forgot_password_test() {}
  @Tag("report")
  @Test
  public void generate_monthly_report() {}
}

如上所示,标签适用于整个类以及单个方法。 让我们执行所有标记为的测试a在给定的包装中。

@RunWith(JUnitPlatform.class)
@SelectPackages("junit.exmaples.v2.tags")
@IncludeTags("qa")
public class JUnit5TagTests {}

以上将导致以下输出

Alt Text

如上所示,只有测试类具有a标记已运行。 让我们都运行a和发展标记测试,但过滤道和报告标记的测试。

@RunWith(JUnitPlatform.class)
@SelectPackages("junit.exmaples.v2.tags")
@IncludeTags({"qa", "development"})
@ExcludeTags({"report", "dao"})
public class JUnit5TagTests {}

如下所示,这两个测试分别带有道和报告被排除在外。

Alt Text


Parametrizing Unit Tests

JUnit允许参数化测试使用不同的参数执行,而不是使用不同的参数多次复制/粘贴测试或构建自定义实用程序方法。

@RunWith(Parameterized.class)
public class JUnit4ParametrizedAnnotationTests {
  @Parameter(value = 0)
  public int number;
  @Parameter(value = 1)
  public boolean expectedResult;
  // Must be static and return collection.
  @Parameters(name = "{0} is a Prime? {1}")
  public static Collection<Object[]> testData() {
    return Arrays.asList(new Object[][] {
      {1, false}, {2, true}, {7, true}, {12, false}
    });
  }
  @Test
  public void test_isPrime() {
    PrimeNumberUtil util = new PrimeNumberUtil();
    assertSame(util.isPrime(number), expectedResult);
  }
}

参数化test_isPrime测试我们需要以下

  • 利用专业参数化类JUnit运行器声明一个非私有静态方法,该方法返回带有注释的Collection@参数声明每个参数@参数和值属性利用@参数测试中带注释的字段

这是我们参数化的输出test_isPrime看起来像

Alt Text

上面是一个使用@参数注入,我们还可以使用构造函数获得相同的结果,如下所示。

public class JUnit4ConstructorParametrized {
  private int number;
  private boolean expectedResult;

  public JUnit4ConstructorParametrized(int input, boolean result) {
    this.number = input;
    this.expectedResult = result;
  }
  ...
}

在JUnit 5中,@ParameterizedTest介绍以下来源

  • 的@ValueSource的@EnumSource的@MethodSource的@CsvSource和@CsvFileSource

让我们详细探讨它们中的每一个。


Parameterized Tests with a ValueSource

的@ValueSource批注允许以下声明

@ValueSource(strings = {"Hi", "How", "Are", "You?"})
@ValueSource(ints = {10, 20, 30})
@ValueSource(longs = {1L, 2L, 3L})
@ValueSource(doubles = {1.1, 1.2, 1.3})

让我们在测试中使用以上之一

@ParameterizedTest
@ValueSource(strings = {"Hi", "How", "Are", "You?"})
public void testStrings(String arg) {
  assertTrue(arg.length() <= 4);
}

Parameterized Tests with an EnumSource

的@EnumSource注释可以通过以下方式使用

@EnumSource(Level.class)
@EnumSource(value = Level.class, names = { "MEDIUM", "HIGH"})
@EnumSource(value = Level.class, mode = Mode.INCLUDE, names = { "MEDIUM", "HIGH"})

相近价值来源, 我们可以用枚举源以下列方式

@ParameterizedTest
@EnumSource(value = Level.class, mode = Mode.EXCLUDE, names = { "MEDIUM", "HIGH"})
public void testEnums_exclude_Specific(Level level) {
  assertTrue(EnumSet.of(Level.MEDIUM, Level.HIGH).contains(level));
}

Parameterized Tests with a MethodSource

的@MethodSource annotation accepts a method name that is providing the input data. 的method that provides input data could return a single parameter, or we could make use of 争论如下所示

public class JUnit5MethodArgumentParametrizedTests {
  @ParameterizedTest
  @MethodSource("someIntegers")
  public void test_MethodSource(Integer s) {
    assertTrue(s <= 3);
  }

  static Collection<Integer> someIntegers() {
    return Arrays.asList(1,2,3);
  }
}

以下是一个例子争论它也可以用来返回POJO

public class JUnit5MethodArgumentParametrizedTests {
  @ParameterizedTest
  @MethodSource("argumentsSource")
  public void test_MethodSource_withMoreArgs(String month, Integer number) {
    switch(number) {
      case 1: assertEquals("Jan", month); break;
      case 2: assertEquals("Feb", month); break;
      case 3: assertEquals("Mar", month); break;
      default: assertFalse(true);
    }
  }
static Collection<Arguments> argumentsSource() {
    return Arrays.asList(
      Arguments.of("Jan", 1),
      Arguments.of("Feb", 2),
      Arguments.of("Mar", 3),
      Arguments.of("Apr", 4)); // Fail.
  }
}

Parameterized Tests with a CSV Sources

在执行包含CSV内容的测试时,JUnit 5提供两种不同类型的来源参数化测试

  • 一种CsvSource- 逗号分隔值一种CsvFileSource-对CSV文件的引用

这是一个例子CsvSource

@ParameterizedTest
@CsvSource(delimiter=',', value= {"1,'A'","2,'B'"})
public void test_CSVSource_commaDelimited(int i, String s) {
  assertTrue(i < 3);
  assertTrue(Arrays.asList("A", "B").contains(s));
}

假设我们在以下条目sample.csv文件下src / test / resources

Name, Age
Josh, 22
James, 19
Jonas, 55

的CsvFileSource情况如下

@ParameterizedTest
@CsvFileSource(resources = "/sample.csv", numLinesToSkip = 1, delimiter = ',', encoding = "UTF-8")
public void test_CSVFileSource(String name, Integer age) {
  assertTrue(Arrays.asList("James", "Josh").contains(name));
  assertTrue(age < 50);
}

由于最后一个输入,导致2次成功运行测试和1次失败sample.csv断言失败。

Alt Text

You can also design custom converters that would transform a CSV into an object. See here for more details.


Theory in JUnit4

注释@理论和亚军理论是实验功能。 和。。比较参数化测试,一个理论将数据点的所有组合馈送到测试,如下所示。

@RunWith(Theories.class)
public class JUnit4TheoriesTests {
  @DataPoint
  public static String java = "Java";
  @DataPoint
  public static String node = "node";
  @Theory
  public void test_theory(String a) {
    System.out.println(a);
  }
  @Theory
  public void test_theory_combos(String a, String b) {
    System.out.println(a + " - " + b);
  }
}

当执行以上测试类时,测试理论测试将输出2¹组合

Java
node

但是,测试test_theory_combos将输出两个数据点的所有组合。 换句话说,2²组合。

Java - Java
Java - node
node - Java
node - node

如果我们有以下数据点s然后test_theory_one测试将生成2³组合(2个args ^ 3个数据点)。 考试test_theory_two将创建3³组合。

@DataPoints
public static String[] oss = new String[] {"Linux", "macOS", "Windows"};
@Theory
public void test_theory_one(String a, String b) {
  System.out.println(a + " - " + b);
}
@Theory
public void test_theory_two(String a, String b, String c) {
  System.out.println(a + " <-> " + b + "<->" + c);
}

以下是有效的数据点

@DataPoints
public static Integer[] numbers() {
  return new Integer[] {1, 2, 3};
}

JUnit 5 Test DisplayName

JUnit 5引入了@显示名称注释,用于为单个测试或测试类提供显示名称,如下所示。

@Test
@DisplayName("Test If Given Number is Prime")
public void is_prime_number_test() {}

它会在控制台中显示如下

Alt Text

Repeating a JUnit Test

如果需要重复X次单元测试,JUnit 5提供@RepeatedTest注解。

@RepeatedTest(2)
public void test_executed_twice() {
  System.out.println("RepeatedTest"); // Prints twice
}

的@RepeatedTest伴随着currentReptition,totalRepetition变量以及TestInfo.java和RepetitionInfo.java对象。

@RepeatedTest(value = 3, name = "{displayName} executed {currentRepetition} of {totalRepetitions}")
@DisplayName("Repeated 3 Times Test")
public void repeated_three_times(TestInfo info) {
  assertTrue("display name matches", 
     info.getDisplayName().contains("Repeated 3 Times Test"));
}

我们也可以使用RepetitionInfo.java找出当前和重复的总数。

Alt Text

Executing Inner Class Unit Tests using JUnit 5's Nested

得知JUnit Runner没有扫描内部类进行测试,我感到很惊讶。

public class JUnit5NestedAnnotationTests {
  @Test
  public void test_outer_class() {
    assertTrue(true);
  }
  class JUnit5NestedAnnotationTestsNested {
    @Test
    public void test_inner_class() {
      assertFalse(true); // Never executed.
    }
  }
}

运行上述测试类时,它将仅执行test_outer_class并报告成功但是,当用@嵌套注解,两个测试都运行。

Alt Text


JUnit 5 Test Lifecycle with TestInstance Annotation

分别调用之前@测试方法,JUnit Runner创建该类的新实例。 可以通过以下方式更改此行为:@测试Instance

@TestInstance(LifeCycle.PER_CLASS)
@TestInstance(LifeCycle.PER_METHOD)

For more information on @TestInstance(LifeCycle.PER_CLASS) please check out the documentation.


DynamicTests using JUnit 5 TestFactory Annotation

带有注释的JUnit测试@测试是静态测试,因为它们是在编译时指定的。 另一方面,动态测试在运行时生成。 这是一个例子动态测试使用PrimeNumberUtil类。

public class Junit5DynamicTests {
  @TestFactory
  Stream<DynamicTest> dynamicTests() {
    PrimeNumberUtil util = new PrimeNumberUtil();
    return IntStream.of(3, 7 , 11, 13, 15, 17)
       .mapToObj(num -> DynamicTest.dynamicTest("Is " + num + " Prime?", () -> assertTrue(util.isPrime(number))));
    }
}

For more on dynamic tests, see this blog post.


Conditionally Executing JUnit 5 Tests

JUnit 5引入了以下注释,以允许有条件地执行测试。

@EnabledOnOs, @DisabledOnOs, @EnabledOnJre, @DisabledOnJre, @EnabledForJreRange, @DisabledForJreRange, @EnabledIfSystemProperty, @EnabledIfEnvironmentVariable, @DisabledIfEnvironmentVariable, @EnableIf, @DisableIf

这是使用示例@EnabledOnOs和@DisabledOnOs

public class JUnit5ConditionalTests {
  @Test
  @DisabledOnOs({OS.WINDOWS, OS.OTHER})
  public void test_disabled_on_windows() {
    assertTrue(true);
  }
  @Test
  @EnabledOnOs({OS.MAC, OS.LINUX})
  public void test_enabled_on_unix() {
    assertTrue(true);
  }
  @Test
  @DisabledOnOs(OS.MAC)
  public void test_disabled_on_mac() {
    assertFalse(false);
  }
}

我使用的是MacBook,输出如下

Alt Text

For examples of other annotations, please check out these tests.


Conclusion

感谢您的阅读。 请在评论中分享您的想法,建议和反馈。

Please Feel free to follow me on dev.to for more articles, Twitter, and join my professional network on LinkedIn.

最后,我还撰写了以下文章,可能会对您有所帮助。

关于如何开始为开源做贡献的指南

我认为可以帮助您成为更好的开发人员的主要习惯清单

最后,简短总结一下Java编码最佳实践

from: https://dev.to//rhamedy/junit-4-5-annotations-every-developer-should-know-5f6c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值