单元测试
一个单元测试分三步:
1. 准备数据
2. 执行测试方法
3. 断言测试结果
Java里面写单元测试都会以来JUnit,而且该包夜提供了一些的Assert的API。但不得不说确实不好用,可读性差。所以不推荐使用。很多人也喜欢用Hamcrest,非常好用(我现在还是用它)。遗憾的是从2012年开始就没有后续更新了。之后了FEST,只是它也不更新了。前段时间去ThoughtWorks 面试,发现他们提倡用 Assertj。这里整理下。
Assertj
Assertj 号称"Java的流水式断言“,目前最新版本是 3.10.0,从 assertj 3 开始支持JDK8。Github上也有很多下载。在项目中引入Assertj,只需添加如下依赖:
<!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.10.0</version>
<scope>test</scope>
</dependency>
看个例子,构造一个 PersonInfo 对象,包含三个属性,然后验证其属性。
public class PersonInfo {
private String name;
private int age;
private Date birthDate;
// 省略其他
}
对应测试用例:
@Test
public void testPersonInfo() {
// 准备数据
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.set(1998, Calendar.JULY, 13);
Date date = calendar.getTime();
// 获取对象
PersonInfo ka = new PersonInfo("KaKa", 21, date);
// 断言
assertThat(ka).isNotNull().isInstanceOf(PersonInfo.class);// Object/Class
assertThat(ka.getName()).startsWith("K").contains("a").isEqualTo("KaKa");// String
assertThat(ka.getAge()).isPositive().isGreaterThan(20).isGreaterThanOrEqualTo(21);// int
assertThat(ka.getBirthDate()).isBeforeYear(2000).isInSameMonthAs("1998-07-01").isInSameDayAs(date);// java.util.Date
}
是不是很简单?这种流水式或者说链式断言用起来非常方便。
注意,这里 assertThat 是 import static org.assertj.core.api.Assertions.assertThat;
API
assertj 断言支持字符串、数字、日期、List、Map、Class等类型,此外还提供了好用的 fail 方法。除此之外,对Java中的Exception、Iterable、JodaTime、Guava等都提供支持。下边看些例子:
上边例子中已经介绍了字符串、数字和日期的断言。此处省略。
List
例如:
@Test
public void testList() {
List<String> names = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
// assertj 3 需要加 asList()
assertThat(names).asList()
.hasSize(4).startsWith("zhangsan").endsWith("zhaoliu")
.contains("lisi", atIndex(1))
.containsOnlyOnce("wangwu");
}
Map
例如:
@Test
public void testMap() {
Map<String, String> map = new HashMap();
map.put("a", "A");
map.put("b", "B");
map.put("c", "C");
assertThat(map).extracting("a", "b", "c").contains("A").doesNotContain("D");
assertThat(map).satisfies(s -> s.containsKey("b")).extracting("b").contains("B");
}
Class
例如
@Test
public void testClass() {
// 断言 不是注解类
assertThat(PersonInfo.class).isNotAnnotation();
// 断言 不是注解类
assertThat(Deprecated.class).isAnnotation();
// 断言 存在注解 @Deprecated
assertThat(PersonInfo.class).hasAnnotation(Deprecated.class);
// 断言 不是接口
assertThat(PersonInfo.class).isNotInterface();
// 断言 Object 类是 PersonInfo 类的父类
assertThat(Object.class).isAssignableFrom(PersonInfo.class);
}
Fail
例如:
@Test
public void testFail() {
try {
fail("在不检查任何条件的情况下使断言失败。显示一则消息");
} catch (AssertionError ae) {
System.out.println(ae);// 会有输出
}
try {
failBecauseExceptionWasNotThrown(RuntimeException.class);
} catch (AssertionError ae) {
System.out.println(ae);// 会有输出
}
}
Exception
Junit自带的就已经支持exception的处理。有意思的是,除了正常方法,它还可以接受各种 lamda 表达式。
// 该例子来源于官网
@Test
public void testException() {
assertThatExceptionOfType(IOException.class).isThrownBy(() -> { throw new IOException("boom!"); })
.withMessage("boom!")
.withMessageContaining("oom")
.withMessage("%s!", "boom")
.withStackTraceContaining("IOException")
.withNoCause();
}
如果编译器不支持 JDK8,可以在 pom.xml 中加入插件
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
用了这么久,觉得 assertj 非常好用,可读性也高。只是个人在写的时候喜欢 debug,而 assertj 查看中间结果时都是函数式的,不容易看数据。