0


使用 AssertJ 进行单元测试的提示

单元测试已成为开发的标准部分。许多工具可以以许多不同的方式用于它。本文演示了一些提示,或者说,对我来说效果很好的最佳实践。

在本文中,您将了解
如何使用 JUnit 和 Assert 框架编写干净且可读的单元测试
在某些情况下如何避免假阳性测试
编写单元测试时应避免的事项
不要过度使用 NPE 检查
我们都倾向于在主代码中尽可能避免 NullPointerException,因为它可能会导致丑陋的后果。我相信我们主要关心的不是在测试中避免NPE。我们的目标是以干净、可读和可靠的方式验证被测组件的行为。

不良做法
过去很多次,即使不需要断言,我也使用过断言,如下例所示:isNotNull

@Test
public void getMessage(){
    assertThat(service).isNotNull();
    assertThat(service.getMessage()).isEqualTo("Hello world!");}

此测试产生如下错误:

java.lang.AssertionError: 
Expecting actual not to be null
    at com.github.aha.poc.junit.spring.StandardSpringTest.test(StandardSpringTest.java:19)

良好做法
尽管附加断言并不真正有害,但由于以下原因,应避免使用:isNotNull

它不会增加任何附加值。它只是需要阅读和维护的更多代码。
无论如何,测试都会失败,我们看到了失败的真正根本原因。该测试仍然实现了其目的。servicenull
使用 AssertJ 断言时,生成的错误消息甚至更好。
请参阅下面修改后的测试断言。

@Test
public void getMessage(){
    assertThat(service.getMessage()).isEqualTo("Hello world!");}

修改后的测试会产生如下错误:

java.lang.NullPointerException: Cannot invoke "com.github.aha.poc.junit.spring.HelloService.getMessage()" because "this.service" is null
    at com.github.aha.poc.junit.spring.StandardSpringTest.test(StandardSpringTest.java:19)

注意:该示例可以在 SimpleSpringTest 中找到。

断言值而不是结果
有时,我们会编写一个正确的测试,但以“糟糕”的方式。这意味着测试完全按预期工作并验证了我们的组件,但失败没有提供足够的信息。因此,我们的目标是断言价值而不是比较结果。

不良做法
让我们看看几个这样的糟糕测试:

// #1
assertThat(argument.contains("o")).isTrue();

// #2
var result ="Welcome to JDK 10";
assertThat(result instanceof String).isTrue();

// #3
assertThat("".isBlank()).isTrue();

// #4
Optional<Method> testMethod = testInfo.getTestMethod();
assertThat(testMethod.isPresent()).isTrue();

上述测试中的一些错误如下所示。

#1
Expecting value to be true but was false
    at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
    at com.github.aha.poc.junit5.params.SimpleParamTests.stringTest(SimpleParamTests.java:23)#3
Expecting value to be true but was false
    at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
    at com.github.aha.poc.junit5.ConditionalTests.checkJdk11Feature(ConditionalTests.java:50)

良好做法
使用 AssertJ 及其流畅的 API 可以轻松解决问题。上面提到的所有情况都可以很容易地改写为:

// #1
assertThat(argument).contains("o");

// #2
assertThat(result).isInstanceOf(String.class);

// #3
assertThat("").isBlank();

// #4
assertThat(testMethod).isPresent();

与前面提到的完全相同的错误现在提供了更多价值。

#1
Expecting actual:
  "Hello"
to contain:
  "f" 
    at com.github.aha.poc.junit5.params.SimpleParamTests.stringTest(SimpleParamTests.java:23)#3
Expecting blank but was: "a"
    at com.github.aha.poc.junit5.ConditionalTests.checkJdk11Feature(ConditionalTests.java:50)

注意:该示例可以在 SimpleParamTests 中找到。

与组相关的断言在一起
断言链接和相关的代码缩进对测试的清晰度和可读性有很大帮助。

不良做法
当我们编写测试时,我们最终会得到正确但可读性较差的测试。让我们想象一个测试,我们想要在其中查找国家/地区并进行以下检查:

计算找到的国家/地区。
使用多个值断言第一个条目。
此类测试可能如下所示:

@Test
void listCountries(){
    List<Country> result =...;

    assertThat(result).hasSize(5);
    var country = result.get(0);
    assertThat(country.getName()).isEqualTo("Spain");
    assertThat(country.getCities().stream().map(City::getName)).contains("Barcelona");

良好做法
尽管前面的测试是正确的,但我们应该通过将相关断言组合在一起(第 9-11 行)来大大提高可读性。这里的目标是断言一次,并根据需要编写许多链接断言。请参阅下面的修改版本。result

@Test
void listCountries(){
    List<Country> result =...;

    assertThat(result)
        .hasSize(5)
        .singleElement()
        .satisfies(c ->{
            assertThat(c.getName()).isEqualTo("Spain");
            assertThat(c.getCities().stream().map(City::getName)).contains("Barcelona");});}

注意:该示例可以在 CountryRepositoryOtherTests 中找到。

防止误报成功测试
当使用任何带有参数的断言方法时,参数也必须包含在使用者中。否则,测试将一直通过 - 即使比较失败,这意味着错误的测试。仅当断言引发 or 异常时,测试才会失败。我想这很清楚,但很容易忘记它并写错测试。它不时发生在我身上。ThrowingConsumer assertThat RuntimeException AssertionError

不良做法
假设我们有几个国家/地区代码,我们想要验证每个代码是否满足某些条件。在我们的虚拟案例中,我们想断言每个国家/地区代码都包含“a”字符。正如你所看到的,这是无稽之谈:我们有大写的代码,但我们没有在断言中应用不区分大小写。

@Test
void assertValues() throws Exception {
    var countryCodes = List.of("CZ", "AT", "CA");
    
    assertThat( countryCodes )
        .hasSize(3)
        .allSatisfy(countryCode -> countryCode.contains("a"));}

令人惊讶的是,我们的测试成功通过了。

在这里插入图片描述
良好做法
如本节开头所述,我们的测试可以通过消费者中的附加功能轻松纠正(第 7 行)。正确的测试应该是这样的:assertThat

@Test
void assertValues() throws Exception {
    var countryCodes = List.of("CZ", "AT", "CA");
    
    assertThat( countryCodes )
        .hasSize(3)
        .allSatisfy(countryCode -> assertThat( countryCode ).containsIgnoringCase("a"));}

现在,测试按预期失败,并显示正确的错误消息。

java.lang.AssertionError: 
Expecting all elements of:
  ["CZ", "AT", "CA"]
to satisfy given requirements, but these elements did not:

"CZ"
error: 
Expecting actual:
  "CZ"
to contain:
  "a"(ignoring case)
    at com.github.aha.sat.core.clr.AppleTest.assertValues(AppleTest.java:45)

链断言
最后一个提示不是真正的实践,而是建议。应使用 AssertJ fluent API 来创建更具可读性的测试。

非链接断言
让我们考虑一下 test,其目的是测试组件的日志记录。这里的目标是检查:listLogs

断言收集的日志数
断言存在和日志消息DEBU GINFO

@Test
void listLogs() throws Exception {
    ListAppender<ILoggingEvent> logAppender =...;
    
    assertThat( logAppender.list ).hasSize(2);
    assertThat( logAppender.list ).anySatisfy(logEntry ->{
            assertThat( logEntry.getLevel()).isEqualTo(DEBUG);
            assertThat( logEntry.getFormattedMessage()).startsWith("Initializing Apple");});
    assertThat( logAppender.list ).anySatisfy(logEntry ->{
            assertThat( logEntry.getLevel()).isEqualTo(INFO);
            assertThat( logEntry.getFormattedMessage()).isEqualTo("Here's Apple runner");});}

链接断言
通过上面提到的 Fluent API 和链接,我们可以这样更改测试:

@Test
void listLogs() throws Exception {
    ListAppender<ILoggingEvent> logAppender =...;
    
    assertThat( logAppender.list )
        .hasSize(2)
        .anySatisfy(logEntry ->{
            assertThat( logEntry.getLevel()).isEqualTo(DEBUG);
            assertThat( logEntry.getFormattedMessage()).startsWith("Initializing Apple");})
        .anySatisfy(logEntry ->{
            assertThat( logEntry.getLevel()).isEqualTo(INFO);
            assertThat( logEntry.getFormattedMessage()).isEqualTo("Here's Apple runner");});}

注意:该示例可在 AppleTest 中找到。

摘要和源代码
AssertJ 框架为其流畅的 API 提供了很多帮助。在本文中,提供了一些技巧和提示,以产生更清晰、更可靠的测试。请注意,这些建议大多是主观的。这取决于个人喜好和代码风格。

标签: 单元测试 java junit

本文转载自: https://blog.csdn.net/QWQ123Q/article/details/136728370
版权归原作者 小徐博客 所有, 如有侵权,请联系我们删除。

“使用 AssertJ 进行单元测试的提示”的评论:

还没有评论