17
AssertJ
Calvin Xiao edited this page 2014-02-04 01:45:28 -08:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Overview

AssertJ网站: http://joel-costigliola.github.io/assertj/

JUnit自己的Assert.assertEquals(String message, String expected, String actual); 是公认的烂API,你很难记住三个参数的位置。
所以后来收编了Hamcrest改成一个从左到右的读法,assertThat(actual, equalTo(expected)); 但实际写起来并不顺手。
所以后来又有了小三Fest Assertion上位,但它后来又不怎么更新了,最后fork了一个活跃的AssertJ出来,最新的代码是这样写的:

import static org.assertj.core.api.Assertions.*;

// common assertions
assertThat(yoda).isInstanceOf(Jedi.class);
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron);
assertThat(frodo).isIn(fellowshipOfTheRing);
assertThat(sauron).isNotIn(fellowshipOfTheRing);

// String specific assertions
assertThat(frodo.getName()).startsWith("Fro").endsWith("do")
                           .isEqualToIgnoringCase("frodo");

// collection specific assertions
assertThat(fellowshipOfTheRing).hasSize(9)
                               .contains(frodo, sam)
                               .doesNotContain(sauron);

// map specific assertions (One ring and elves ring bearers initialized before)
assertThat(ringBearers).hasSize(4)
                       .contains(entry(Ring.oneRing, frodo), entry(Ring.nenya, galadriel))
                       .doesNotContain(entry(Ring.oneRing, aragorn));

// throwable specific assertions
try {
  fellowshipOfTheRing.get(9); 
  failBecauseExceptionWasNotThrown(IndexOutOfBoundsException.class);
} catch (Exception e) {
  assertThat(e).isInstanceOf(IndexOutOfBoundsException.class)
               .hasMessage("Index: 9, Size: 9")
               .hasNoCause();
}

可见,新的语法,可以更顺畅的从左往右读,可以用Fluent API连续的测试,而且对各种常见对象都有一些很方便的测试方法封装,包括Class, 基本对象, 基本类型, Collections, Map, Date, Exception, File,还有扩展模块支持Guava里的几种集合和Joda-time。

在Eclipse里的配置

要从Junit的assertEquals()方便的换成assertThat(),重要一点是你在代码里打完assertThat,Eclipse能自动提示你import static org.assertj.core.api.Assertions.*; 在Eclipse 点击 preferences > Java > Editor > Content assist > Favorites > New Type,加入org.assertj.core.api.Assertions,IntelliJ的方法类似。

另外,你应该已经配置了Eclipse的static import总是给* 而不是展开:preferences > Java > Code Style > Organize Import, 设置Number of static imports needed of * to 1。

批量替换JUnit Assert到AssertJ

官网上有批量转换Junit Assert的介绍,可以用它提供的Linux Shell脚本,也可以在Intellij里用正则表达式来替换,把assertEquals(expected, actual) 替换为assertThat(actual).isEqualTo(expected),及其它assertNull(), assertTrue()等的转换

在Eclipse上正则的语法略有不同,可参考下面的例子自行编写:

Search:  assertEquals\((.*), (.*)\);
Replace: assertThat\($2\).isEqualTo($1);

另外

没在前面例子里演示的技巧

  • 下一个版本,把打酱油的assertThat()可以简写成BDD风格的then(),更短。
  • as函数,加入错误描述,as要写在测试的前面。
assertThat(myList).as("list should be empty").isEmpty();
  • extracting函数,用字符串定义需要反射取出的属性/方法,比如遍历一个List,将里面每个对象的属性拿出来判断。当然,用名字反射永远的问题就是属性/函数改名的时候不能自动重构。
assertThat(userList).extracting("name").contains("calvin", "kevin");
  • Date的比较可以忽略时间上毫秒级的不同,只要是秒上的数值一样,或者绝对值相差在一秒内的就算对,比如第6.9秒与第7.3秒。类似的还有同一天,同一小时。
assertThat(date1).isEqualToIgnoringSeconds(date2);
assertThat(date1).isInSameSecondWindowAs(date2);
  • isXmlEqualTo忽略XML字符串之间换行缩进这种格式上的差别,只比较实际内容。issJsonEqualTo用JSON-Unit项目,也有类似fluent API。
assertThat(oneLineXml).isXmlEqualTo(expectedXml);
  • 两个Bean之间只比较某些特定的fields,而不是用equals()函数比较,isEqualToIgnoringGivenFields,isEqualToComparingOnlyGivenFields,isEqualToIgnoringNullFields 等,比如比较数据库里查出来的actual对象与这边厢new出来的expected对象,可能会少一些属性的时候就会用到。
  • File有hasContent(String expected),InputStream有hasContentEqualTo(InputStream expected),有或者是contenOf(),可以用上StringAssert里的函数。
File xFile = writeFile("xFile", "The Truth Is Out There");
assertThat(contentOf(xFile)).startsWith("The Truth").contains("Is Out");
  • 不在assert错误时立刻抛出异常,而是所有assert跑完后一次过抛出,免得fix完一个问题,再执行测试又才看到另一个。
  SoftAssertions softly = new SoftAssertions();
  softly.assertThat(mansion.guests()).as("Living Guests");
  softly.assertThat(mansion.kitchen()).as("Kitchen");
  softly.assertAll();