转载

如果还是接受不了Spock,那就试试JUnit5吧

JUnit那么牛掰,我看到Spock时还是一见钟情了,最近JUnit5的出现,又回心转意了……

为什么在选择JUnit和Spock之间纠结

每个Java开发者都会用JUnit。

有人连续几年在维护一个 Top 20 Java Libraries 排行榜,它使用了GitHub的API和Google BigQuery对超过27万(277975)个Java源代码文件分析而得出,2018年JUnit排名第三,此前JUnit连续三年蝉联冠军宝座。

分析的过程可以看我的另一篇文章 - 「小得103」2018年排名TOP100的Java库都有谁

如果还是接受不了Spock,那就试试JUnit5吧
image.png

几乎每个编程语言里都有JUnit的复制品,比如在.NET里NUnit,在C++里有CPPUnit。此外还有CUnit , PyUnit ,PHPUnit, OCUnit, DUnit, JSUnit 等等。

JUnit的诞生也足够传奇。

有一次坐飞机的时候Eric Gamma偶遇Kent Beck,两位大牛见面寒暄过以后就觉得很无聊了。旅途漫漫,干点啥好呢?

Kent Beck当时力推测试驱动开发(TDD),  但是没有一个工具或者框架能让大家轻松愉快的写测试,并且自动的运行测试。

两位大牛决定撸起袖子自己写一个,正好也可以实践一下XP里的结对编程 。等到飞机落地的时候,一个划时代的单元测试工具就新鲜出炉了,它的名字叫做JUnit。

Eric Gamma是设计模式四人帮(GoF)之一,后来还主导创建了Eclipse和Visual Studio Code。Kent Beck,是极限编程(XP)和测试驱动开发(TDD)的创建者。可以通过下面的图片领略一下两位大佬的风采。

如果还是接受不了Spock,那就试试JUnit5吧
image.png
如果还是接受不了Spock,那就试试JUnit5吧
image.png

多年前,我学习了Kent Beck的TDD后,十分依赖JUnit了。然而,当我第一次看到Spock的时,马上就移情别恋了。我喜欢Spock是因为:

  • 不用再为测试的方法名命名而发愁了

  • 我喜欢BDD的风格,尤其是生成的测试报告

  • 表格化的数据驱动的测试太吸引人了

  • 我喜欢Groovy,Spock是基于Groovy写的,Groovy所有的简洁魔幻的语法都可以使用;Spock的语法糖加上Groovy的语法糖,可以让测试代码减少一半儿,带来的简洁明了十分酸爽撩人

但是JUnit还是有Spock不及的优势的:

  • 第一个就是“流行”,这意味着学习成本低,培训成本低,毕竟软件开发还是整个团队的事情,不是所有人愿意学习Spock的

  • 也不是所有人愿意学习Groovy,但Java开发者都会Java,JUnit是基于Java的。另外,Java的强类型对IDE也特别友好,我一般实践TDD的时候,先用JUnit写测试代码,出错后敲击"option+回车",IntelliJ IDEA就会自动修复(比如生成还没有的类、方法等),过程十分流畅。而使用Spock后,IDE对Groovy这种动态语言的支持还是很不足的,这种行云流水的快感没有了

  • JUnit丰富的生态和兼容性是Spock所不能比的

如果还是接受不了Spock,那就试试JUnit5吧
image.png

TDD所推崇的先写测试代码,出现错误(编译错误或测试失败)IDE提示变红后,再写正式代码代码,让错误消失,IDE提示变绿。

在JUnit4的时代,我一有机会还是会选择Spock。但是,当JUnit5,这个下一代的JUnit版本正式发布后,也许可以重新选择JUnit了。

下面我们看看,JUnit5是怎么实现Spock的那些撩人的测试案例的。

是骡子是马拉出来遛遛

方法命名

在Spock中可以这样命名测试方法:

def "events are published to all subscribers"() {
    
}

现在JUnit5勉强可以应对了:

@Test
@DisplayName("当充值420个积分后,账户中有520积分,并生成一条对应的充值记录")
void recharge() {
}

断言

Spock写断言太自然了,这要感谢Groovy:

pc.clockRate >= 2333
pc.os == "Linux" : "判断操作系统是Linux"

//或者

with(pc) {
   vendor == "Sunny"
   clockRate >= 2333
   ram >= 406
   os == "Linux"
}

输出的提示还十分友好:

assert pc.clockRate >= 2333
       |  |         |
       |  1666      false
       ...

JUnit5得使用assertj来帮忙了:

assertThat(account.getBalance()).isEqualTo(new Amount(99));

assertThat(account.getConsumeRecords())
    .hasSize(1)
    .hasOnlyOneElementSatisfying(record ->
         assertThat(record.getAmount())
         .isEqualTo(new Amount(1)));

BDD风格

Spock最大亮点之一,Given-When-Than,不解释,代码一目了然:

def "HashMap accepts null key"() {
  given:"一个HashMap"
  def map = new HashMap()

  when:"给它添加null元素"
  map.put(null, "elem")

  then:"不抛出异常"
  notThrown(NullPointerException)
}

可以实现“Specifications as Documentation”的效果,也可以生成非常好的报告。

JUnit5使用@Displayname和@Nested也可以勉强应对吧:

如果还是接受不了Spock,那就试试JUnit5吧
image.png

运行结果:

如果还是接受不了Spock,那就试试JUnit5吧
image.png

报告:

如果还是接受不了Spock,那就试试JUnit5吧
image.png

数据驱动测试

Spock的另一个大亮点,不解释看代码:

import spock.lang.Unroll

class DataDrivenTest extends Specification {
    @Subject def arithmeticOperation = new ArithmeticOperation()

    @Unroll
    def "can add"() {
        expect:
        arithmeticOperation.add(1, b) >= 2

        where:
        b << [1, 2, 3, 4, 5]
    }

    @Unroll
    def "can add #a to #b and receive #result"() {
        expect:
        arithmeticOperation.add(a, b) == result

        where:
        a  | b  | result
        1  | 3  | 4
        3  | 4  | 7
        10 | 20 | 30
    }
}

执行效果:

如果还是接受不了Spock,那就试试JUnit5吧

JUnit5怎么实现呢?

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.stream.Stream;

public class DataDrivenTest {
    private final ArithmeticOperation arithmeticOperation = new ArithmeticOperation();

    @ParameterizedTest
    @ValueSource(ints = { 1, 2, 3, 4, 5 })
    void canAdd(int b) {
        assertTrue(arithmeticOperation.add(1, b) >= 2);
    }

    @ParameterizedTest(name = "can add {0} to {1} and receive {2}")
    @MethodSource("additionProvider")
    void canAddAndAssertExactResult(int a, int b, int result) {
        assertEquals(result, arithmeticOperation.add(a, b));
    }

    static Stream<Arguments> additionProvider() {
        return Stream.of(
            Arguments.of(1, 3, 4),
            Arguments.of(3, 4, 7),
            Arguments.of(10, 20, 30)
        );
    }
}

运行效果:

如果还是接受不了Spock,那就试试JUnit5吧
image.png

总结

此外,还有Mock/Stub、按条件执行测试等功能,如果大家有兴趣看的话,后续再写。

总之,可以看到,JUnit5在表达性上,已经在努力追赶Spock了,但局限于Java语法的局限,还是有不小的差距的。但相对于JUnit4,JUnit5进步已经非常大了。如果你尤其是你的团队不想学习Spock/Groovy的话,或者喜欢IDE对Java更好的提示的话,那最起码可以考虑用JUnit5代替JUnit4了。

原文  http://mp.weixin.qq.com/s?__biz=MzU1Njc5MTEwOA==&mid=2247483792&idx=1&sn=f58d806dc6c78facac0c11027228b92a
正文到此结束
Loading...