4

JUnit5 使用者:为何 Spock 值得你看它一眼

 3 years ago
source link: https://blog.dteam.top/posts/2020-04/spock-is-better.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

JUnit5 使用者:为何 Spock 值得你看它一眼

胡键 Posted at — Apr 15, 2020 阅读 236

这篇文章的诞生纯属巧合:一是早上看到微信群中有人问 Junit 的问题(往往这个时候我就忍不住向他们推销 Spock,同样的,这次也没有忍住),二是下午读到一篇转发的 JUnit5 vs Spock 的文章。于是乎,顺应天意写了下来。

我们团队已有多年(5+ 年)的 Spock 使用经验,不仅用在 Grails 项目中,而且在几乎所有 Java 项目中同样也采用它作为测试用例的书写工具。正如我在工具推荐给出的推荐原因:

Groovy DSL、jvm 下最好用的测试框架

Spock 完全担得起这样的评价!看看下面的代码示例,相信你有自己的判断。

阅读前的提醒:

  • 本文代码主要来自于上面的文章,部分来自于我之前写的文章。并且,代码对比点也来自于那篇文章。
  • 上面那篇文章给出了更详细对比,因为本文不是翻译,对于想了解全文内容的小伙伴请自行前往阅读。
  • 如果你懒得读那篇文章,只想看结论,我简单归纳如下:
    • JUnit5 名气和活跃度更大。
    • 凡事跟写代码相关的,Spock 均胜出。
    • 作者自己更喜欢 Spock。

好了,“ talk is cheap, show me the code ”。

JUnit5

class SimpleCalculatorTest {
    @Test
    void shouldAddTwoNumbers() {
        //given
        Calculator calculator = new Calculator();
        //when
        int result = calculator.add(1, 2);
        //then
        assertEquals(3, result);
    }
}

Spock

class SimpleCalculatorSpec extends Specification {
    def "should add two numbers"() {
        given:
            Calculator calculator = new Calculator()
        when:
            int result = calculator.add(1, 2)
        then:
            result == 3
    }
}

JUnit5

@Test
void shouldThrowBusinessExceptionOnCommunicationProblem() {
    //when
    Executable e = () -> client.sendPing(TEST_REQUEST_ID)
    //then
    CommunicationException thrown = assertThrows(CommunicationException.class, e);
    assertEquals("Communication problem when sending request with id: " + TEST_REQUEST_ID,
                 thrown.getMessage());
    assertEquals(TEST_REQUEST_ID, thrown.getRequestId());
}

Spock

你没看错,Spock 的测试方法名称可以是字符串,而且在我们的实际使用过程中直接就写成中文,这样产生出来的测试报告一眼就看明白什么问题。

def "should capture exception"() {
    when:
        client.sendPing(TEST_REQUEST_ID)
    then:
        CommunicationException e = thrown()
        e.message == "Communication problem when sending request with id: $TEST_REQUEST_ID"
        e.requestId == TEST_REQUEST_ID
}

有条件执行测试

JUnit5

结合相应的注解来做,这里就列出几例,其余自己去查文档。

@Test
@DisabledOnOs(OS.WINDOWS)
void shouldTestSymlinksBasedLogic() {
    ...
}

@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*32.*")
void shouldBeRunOn32BitSystems() {
    ...
}

Spock

利用 Groovy 动态语言的特性,使用“注解 + 闭包”的形式,提供更灵活的使用。

@IgnoreIf({ !jvm.java8Compatible })
def "should return empty Optional by default for unstubbed methods with Java 8+"() { ... }

@Requires({ sys["targetEnvironment"] != "prod" })
def "should execute smoke testing on non production environment"() { ... }

Mocking

JUnit5

需结合 Mockito

@Test
public void should_not_call_remote_service_if_found_in_cache() {
    //given
    given(cacheMock.getCachedOperator(CACHED_MOBILE_NUMBER)).willReturn(Optional.of(PLUS));
    //when
    service.checkOperator(CACHED_MOBILE_NUMBER);
    //then
    then(webserviceMock).should(never()).checkOperator(CACHED_MOBILE_NUMBER);
//   verify(webserviceMock, never()).checkOperator(CACHED_MOBILE_NUMBER);   //alternative syntax
}

Spock

内置了 Mock 机制

def "should not hit remote service if found in cache"() {
    given:
        cacheMock.getCachedOperator(CACHED_MOBILE_NUMBER) >> Optional.of(PLUS)
    when:
        service.checkOperator(CACHED_MOBILE_NUMBER)
    then:
        0 * webserviceMock.checkOperator(CACHED_MOBILE_NUMBER)
}

JUnit5

@ParameterizedTest(name = "value to pay for invoice {0} should be {1}")
@MethodSource("invoiceProvider")
void shouldCalculateToPayValueForInvoice(Invoice invoice, BigDecimal expectedValueToPay) {
    //when
    int valueToPay = invoice.toPayValue();
    //expect
    assertEquals(expectedValueToPay, valueToPay);
}

private static Stream<Arguments> invoiceProvider() {
    return Stream.of(
            Arguments.of(regularInvoice(), 54),
            Arguments.of(overduedInvoice(), 81),
            Arguments.of(paidInvoice(), 0)
    );
}

Spock

这是我的最爱

@Unroll
def "should sum two integers (#x + #y = #expectedResult)"() {
    when:
        int result = calculator.add(x, y)
    then:
        result == expectedResult
    where:
         x |  y || expectedResult
         1 |  2 ||  3
        -2 |  3 ||  1
        -1 | -2 || -3
}

因为不用 JUnit 好多年,对于 JUnit5 没有去研究是否提供了对于并发测试编写的内部支持。

这里的例子来自于我之前写的 Spock + Vert.x 自动化测试的文章

when:
BlockingVariable<Integer> rowCount = new BlockingVariable<>()
BlockingVariable<String> callback = new BlockingVariable<>()
pgUtils.simpleSql(NamedQuery.uncalledCallback) { rowSet ->
    rowCount.set(rowSet.size())
    callback.set(rowSet.asList()[0].getString('callback'))
}

then:
rowCount.get() == 1
callback.get() == 'callback2'

几乎是无痛编写!

Spock 对于并发测试提供了若干辅助类:

  • BlockingVariable,这个类的作用就是一直阻塞,直到有值。它非常适合测试异步回调返回值的测试场景。
  • AsyncConditions,这个类用于测试异步条件是否。
  • PollingConditions,类似上面,但更强。

看完代码,你的答案是什么呢?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK