上一篇 JUnit 5 快速上手(从 JUnit 4 到 JUnit 5) 介绍了如何在一个项目中同时使用 JUnit 4 和 JUnit 5。现在来开始了解 JUnit 5 的新特性. 我们现在的项目基本是用 Maven 来管理依赖,在 Maven 项目中如何引入 JUnit 5 可以参考官方例子 junit5-maven-consumer. 我们知道 JUnit 5 包括三个模块,不用 JUnit 4 的话只要 Platform 和 Jupiter, 而 Jupiter Maven 模块本身依赖于 JUnit Platform, 因此应用 JUnit 5 的项目 Maven 配置就是1<dependency> 2 <groupId>org.junit.jupiter</groupId> 3 <artifactId>junit-jupiter-engine</artifactId> 4 <version>5.0.0</version> 5 <scope>test</scope> 6</dependency>
这样在当前的 IntelliJ IDEA(2017.2.4) 可以执行 JUnit 5 的测试用例。但要让 Maven 找到 JUnit 5 的测试用例,还得在pom.xml中加上 Read More
在研究 JUnit 5 新特性的时候,学习到其中有一节 Test Instance Lifecycle, 才意识到对 JUnit 的理解一直存在一个误区,以为 JUnit 是以测试类为一个生命周期的,其实不然。不管是 JUnit 5 还是 JUnit 4 或更早的版本,JUnit 都是以测试方法为一个独立的生命周期。
只是到了 JUnit 5 提供了方法来把生命周期由方法改为测试类,对于单个测试类可以使用注解
@TestInstance(Lifecycle.PER_CLASS)来指定用一个测试实例来跑所有的测试方法,这就意味着测试类中的成员变量只被初始化一次。@TestInstance的 Lifecycle 默认是 PER_METHOD, JUnit 4 就是 PER_METHOD, 而且是不能改的。如果在 JUnit 5 中改变为 PER_CLASS, 恐怕反而会出许多乱子,每个测试方法本就该是完全独立的。比如在同一个类中多个测试方法使用了同一个实例变量的情况下,总会用一个
@After方法来复位该实例变量,现在才知道那是多余的。像下面的代码1public class CalculatorTest { 2 3 private int number = 100; 4 5 @Test 6 public void test1() { 7 System.out.println(number); 8 number = 200; 9 } 10 11 @Test 12 public void test2() { 13 System.out.println(number); 14 number = 300; 15 } 16}
一直在关注 JUnit 5 的演进,自两年前首个 ALPHA 版后,经历了 6 的 Milestone, 3 个 RC 终于在 2017/09/10 正式发布了。其实还从未对其深究过,今天算是正式开始体验。
不像以往的版本,JUnit 5 现在是三个模块的合体 JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform: 运行测试的基础平台。还定义了开发测试框架的 TestEngine API。并提供了命令行执行测试以及与 Gradle, Maven, JUnit4 Runner 的集成
- JUnit Jupiter: 包含了新的编程和扩展模型。它还提供了一个运行新型测试的 TestEngine 实现
- JUnit Vintage: 提供了一个让 JUnit Platform 运行 JUnit 3 和 JUnit 4 的 TestEngine 实现
以上三个模块分工还是很明确,因此
- 从现有的 JUnit 4 项目步入到 JUnit 5 至少两 JUnit Platform 和 JUnit Vintage 两个
- 建立全新项目可以只引入 JUnit Platform 和 JUnit Jupiter
- 混合型当然是三个全部引入
但是由于 jar 包之间本身存在某种依赖关系,所以实际上 pom.xml 可以比想像的更简单 Read More
- 两年前写过一篇 Java 反射修改 final 属性值, 在这里重新温习一下,假设有个类
class Person {
这里声明 name 为非静态的属性只是为了说明反射修改 final 属性无关乎静态不静态,静态只是表现在它是一个类属性,在一个类加载器空间只会有一份拷贝,仅此而已。
public final String name = "Mike";
}
创建一个通用方法进行反射修改属性值1public static void modify(Object object, String fieldName, Object newFieldValue) throws Exception { 2 Field field = object.getClass().getDeclaredField(fieldName); 3 4 Field modifiersField = Field.class.getDeclaredField("modifiers"); 5 modifiersField.setAccessible(true); //Field 的 modifiers 是私有的 6 modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 7 8 if(!field.isAccessible()) { 9 field.setAccessible(true); 10 } 11 12 field.set(object, newFieldValue); 13}
调用 modify(...) 方法试图修改 person 的 name 属性 Read More 我们可以用自定义的 URLClassLoader 从外部动态加载类,并使用它。但数据库驱动的管理类 DriverManager 却不比较苛刻,不承认非当前应用系统加载器加载的驱动类。见 DriverManager 的 JavaDoc
When the method
getConnectionis called, theDriverManagerwill attempt to locate a suitable driver from amongst those loaded at initialization and those loaded explicitly using the same classloader as the current applet or application对于有有应用自定义类加载器加载数据库驱动类的需求时,就要对原 Driver 简单包装一下。继续往后会说介绍为什么要这么做。
说明一下,DriverManager 能够根据 JDBC 连接字符串匹配到驱动类,所以一般来说都不需要显式调用 DriverManager.registerDriver() 方法。
先看 DriverManager 在应用外部驱动类时会出现什么情况 Read More
Apache Avro 是类似于 Google protobuf 那样的数据交换协议,但 Avro 可以用 JSON 格式来定义 Schema, 所以相比而言更容易上手。它也是 Hadoop, Kafka 所采用的交换格式。对于生成的 avro 序列化文件如果要编写代码来解读其中内容的话就太过于麻烦,Apache 给了我们一个便捷的工具来处理 Avro Schema 和数据。
Java 版的 Avro Tools 可点击链接 avro-tools-1.8.2.jar 下载,当前版本 1.8.2(发布于 2017/05/20),执行命令是java -jar avro-tools-1.8.2.jar ..............
如果是 Mac 平台,还可以通过brew install avro-tools
来安装,执行命令就只是avro-tools了。
在本文中还会用到一个 JSON 格式化高亮显示的工具jq, 在 Mac 下通过以下命令安装brew install jq
avro-tools 和 jq 已准备就绪,接下来演示下如何使用它们。
Read More
三个月前写过一篇 Mockito 中捕获 mock 对象方法的调用参数,一般项目中 Mockito 不决求助于 JMockit, 同样的在 JMockit 也需对捕获被 Mock 的方法调用参数。当我们用new Expectations(){{}}打桩并在后面断言了返回值,那就无需捕获参数来断言,匹配到了方法调用即证明传入的参数也是对的,如下面的代码所示1public class UserServiceTest { 2 3 @Mocked 4 private UserDao userDao; 5 6 @Test 7 public void couldCallUserDaoToAddUser() { 8 new Expectations(){{ 9 userDao.findById(123); 10 result = "Yanbin"; 11 }}; 12 13 UserService userService = new UserService(userDao); 14 String user = userService.findBy(123); 15 16 assertThat(user).isEqualTo("Yanbin"); //这里断言成功也就证明了 userDao.findById(123) 方法被调用,参数必须是 123 17 } 18}
但如果是未打桩的方法,或打桩是用的模糊参数(withInstanceOf(String.class)), 或是无返回值的方法就要事后对是否调用了某个方法以及传入什么参数的情况进行断言。 Read More- 对于同步方法的测试很简单,调用完后可立马检查执行状态; 而异步方法,由于我们无法确切的知道何时结束,因此以往的办法是用
Thread.sleep(500)来预估一个执行时间。然后通常我们估计的要长于实际的时间,这就很浪费,况且偶然的超过预估的等待时间也并不意味着代码有问题。还有sleep方法还抛出一个检测异常InterruptedException, 一般会要对Thread.sleep(500)作下简单包装。
于是今天要介绍的 Awaitility 就应运而生了,专门针对异步方法的测试。它的官方文档在 https://github.com/awaitility/awaitility/wiki/Usage。本文主要关注在 Java 8 环境下用 Lambda 的代码书写方式。Awaitlity 实际运行是以某种轮询的方式来检查是否达到某个运行状态,可设定最多,最少等待时间,或永久等待,或自定义轮询策略,之后就开始进行需要的断言,所以它可以尽可能的节省测试异步方法所需的时长。而不像Thread.sleep(500)一路等到黑,并且没有回头路。
通常我会在项目中给 JUnit 配上三个最佳伴侣,它们是(按mvn dependency:tree中的显示方式):- org.awaitility:awaitility:2.0.0:test
- org.assertj:assertj-core: version: 3.8.0:test
- org.mockito:mockito-core:2.7.22:test
当然如果项目中没有异步调用自然是不需要 Awaitility, 在我的项目中是基本不可能的。以上三种都追求 DSL,以流畅的方式进行愉快的测试。
现在来尝试下 Awaitility 的几种基本的用法,先假定有下面的代码UserServiceRead More
修改私有属性来 Mock 可能不是一种很好的测试方式, 因为属性名是动态的,但有时不得已而为了,例如下面的代码:1public class UserService { 2 private ExternalApi external = ExternalApi.default(); 3 private UserDao userDao; 4 5 public UserService(UserDao userDao) { 6 this.userDao = userDao; 7 } 8 9 public User findUserById(int id) { 10 return userDao.findById(external.convertId(id)); 11 } 12}
测试时欲隔离对 ExternalApi 的外部依赖, 当然可以把它也作为构造函数的一个参数,这样创建 UserService 实例时就可以 Mock external 属性。不过 external 经常是不变的,所以作为方法参数的必要性也不大。这就希望能在构造出 UserService 之后对 external 私有属性进行 Mock 处理。
在 Mockito 1.x 和 2.x 下要使用不同的方式,分别使用到 Whitebox 和 FieldSetter 类,它们都来自于mockito.internal.util.reflection包,可见 Mockito 打心底不推荐直接使用它们,但谁叫它们是 public 的呢。还有一种方式是使用 PowerMock + Mockito, 这是后话。 Read More
Java 测试的 Mock 框架以前是用 JMockit, 最近用了一段时间的 Mockito, 除了它流畅的书写方式,经常这也 Mock 不了,那也 Mock 不了,需要迁就于测试来调整实现代码,使得实现极不优雅。比如 Mockito 在 私有方法,final 方法,静态方法,final 类,构造方法面前统统的缴械了。powermock 虽然可作 Mockito 的伴侣来突破 Mockito 本身的一些局限,但是我一用它来 Mock 一个构造方法就出错Caused by: java.lang.ClassNotFoundException: org.mockito.exceptions.Reporter
原因是 Mockito 变化太快,powermock 跟不上它的步伐 -- https://github.com/powermock/powermock/issues/684,于是我只能止步。
不得已再祭出 JMockit 这号称(也确实是)一无所不能的大杀器,在此见识一下它怎么 Mock 构造函数的
本篇实例所使用的 JMockit 版本是 1.30, 当前最新版 1.31, 由于尚未被 Maven 中央仓库收录,所以暂用 1.30。在 pom.xml 中如下方式引入 Read More