Java几种常用的断言风格你怎么选?

日常工作中,不管你是写Unit Test,还是采用TDD的编程方式进行开发,都会遇到断言。而断言的风格常见的会有Assert、BDD风格,对于这些常见的断言风格你怎么选择呢?

01 Assert风格

JUnit中提供了这样的assert断言风格,例如:

1[@Test](https://my.oschina.net/azibug) 2 void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { 3 EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); 4 5 String result = entranceMachine.execute(Action.INSERT_COIN); 6 7 assertEquals("opened", result); 8 assertEquals(EntranceMachineState, entranceMachineState.UNLOCKED); 9 }

Hamcrest和AssertJ都提供了assertThat()这样风格的断言,例如:

AssertJ提供的assertThat()的断言语法

1[@Test](https://my.oschina.net/azibug) 2 void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { 3 EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); 4 5 String result = entranceMachine.execute(Action.INSERT_COIN); 6 7 assertThat(result).isEqualsTo("opened"); 8 assertThat(EntranceMachineState).isEqualsTo(entranceMachineState.UNLOCKED); 9 }

Hamcrest提供的assertThat()断言语法

1[@Test](https://my.oschina.net/azibug) 2 void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { 3 EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); 4 5 String result = entranceMachine.execute(Action.INSERT_COIN); 6 7 assertThat(result, is("opened")); 8 assertThat(EntranceMachineState, is(entranceMachineState.UNLOCKED)); 9 }

对比上面三种断言语法,因为场景简单,所以结果差异并不是很大。对于我个人更加偏向于使用AssertJ提供的断言风格。因为这种风格避免JUnit提供的断言中经常遇到的问题,expected在前还是actural在前的问题。相比于Hamcrest的断言风格,在日常工作中综合对比发现AssertJ的更加清晰,毕竟AssertJ中assertThat只需要接收一个参数,而不用关注括号是否对齐的问题。

日常工作中如果使用TDD,且场景适当(例如上面例子),那么Hamcreate和AssertJ的差别不是很大。JUnit5默认提供了Hamcreate的断言,不需要额外的再引入其他依赖。

02 BDD风格

代码的可读性越来越收到开发者的重视。测试代码的可读性同样重要,为了让测试代码结构清晰,便于业务逻辑变动时能快读读懂测试的上下文,很多开发团队约定了BDD的风格来组织测试代码。其中包含两部分的约定:测试方法名的约定,测试代码段落的约定。

例如前面的例子:

1 [@Test](https://my.oschina.net/azibug) 2 void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { 3 ... 4 }

虽然方法名很长,但是通过方法名我们能够快速知道测试类中有哪些测试,通过方法名我们能够清晰的当前测试的上下文,在测什么,期望的结果什么。通过方法名而不是通过比方法名长很多的代码段来获取测试在测什么的信息,毕竟阅读代码时间和修改代码时间可能是10:1,甚至20:1。所以团队约定BDD的风格组织在后续修改代码时,是受益良多的。

当需要也带具体的测试代码的时候,团队发现按照BDD这种三段式的风格来组织代码受益良多。例如:

1[@Test](https://my.oschina.net/azibug) 2 void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { 3 EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); 4 5 String result = entranceMachine.execute(Action.INSERT_COIN); 6 7 assertThat(result).isEqualsTo("opened"); 8 assertThat(EntranceMachineState).isEqualsTo(entranceMachineState.UNLOCKED); 9 }

我们可以清晰的知道哪行代码在描述上下文,哪几行代码在描述测试意图,哪几行代码在描述测试结果验证。

BDD的风格能够帮助团队将测试代码维护的较为清晰。AssertJ提供了BDD风格的断言方式。使用then()语法。例如:

1@Test 2 void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { 3 EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); 4 5 String result = entranceMachine.execute(Action.INSERT_COIN); 6 7 then(result).isEqualsTo("opened"); 8 then(EntranceMachineState).isEqualsTo(entranceMachineState.UNLOCKED); 9 }

断言变化不大。但是真正仔细读的时候,会发现使用then()还是简单那么一点点的。

我们常用的Mock工具Mockito,也提供了BDD风格的断言:then(), should(), and()。

1import static org.mockito.BDDMockito.then; 2import static org.assertj.core.api.BDDAssertions.and; 3import static org.mockito.Mockito.mock; 4import static org.mockito.Mockito.times; 5 6@SuppressWarnings("static-access") 7@Test 8public void bdd_assertions_with_bdd_mockito() { 9 Person person = mock(Person.class) 10 person.ride(bike); 11 12 person.ride(bike); 13 14 then(person).should(times(2)).ride(bike); 15 and.then(person.hasBike()).isTrue(); 16}

所以日常开发中,我会首先选择then(),其次会选择assertThat()。

除了以上两种断言风格,流式断言让代码更清晰,断言重复内容更少

当我们需要为某个结果测试多个测试点时,如果为每个测试点都组织一次相同的上下文,那么重复代码太多。带来的价值就是那么一点点区别,所以在测试力度上我们可以根据经验来在开发工程中动态调整。

下面据一个例子,当我们需要验证有一个查询方法返回的List的结果时,不单单要验证List中元素的数量,还要验证元素是否时期望的顺序。那么流式写法会缩减一部分重复的断言代码。

1then(users).hasSize(3) 2 .containsExactlyInAnyOrder( 3 firstUser, 4 secondUser, 5 thirdUser);

上面是日常工作中经常使用到的断言技巧,你的怎么选择的呢?那种风格无所谓能工作就行?

参考

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Google Java 代码规范

1.(1)简介本文档用于Java编程语言的Google源代码编码标准的完整定义。Java源文件定义为Google风格。于其他编程风格指南一样,所涉及的问题不止包含代码格式美化,还包括其他类型的约定或者编码标准。但是本文档主要关注普遍遵循的严格规则,并避免提供意义不明的可执行建议(无论任何方式)。1.1.

Flutter仿写一个iOS风格的通讯录

此文章主要介绍怎么使用Flutter的Cupertino风格控件,写一个iOS风格的通讯录,还有在此过程中遇到的问题及解决办法。大家在用Flutter写App的时候,一般都会使用material风格的控件,因为material风格的控件比较丰富,但是,他在iOS上就会显得Android气息比较重,不太适合,所以本文章将通过用仿写iOS通讯录,系统地介绍C

C++系统学习之C库assert

C库之<cassertassert.h定义了一个作为标准调试工具的宏宏函数函数说明assertEvaluateassertion(macro)assert当使用assert()里,给它一个参数,即一个表示断言为真的表达式。预处理器产生测试该断言的代码。如果断言不为真,则发出一