JUnit 5 使用 Mockito 2 进行测试

JUnit 5 刚出来那时,也就是第一个版本 5.0.0 时,还不能很好的支持 Mockito 的测试,因为 Mockito 没能跟得那么紧密。那时候 JUnit 5 只能试验性的提供了一个极不正式的 com.example.mockito.MockitoExtension, 看那包名就知道不是来真的,所以决定再等。JUnit 5 不再原生支持 JUnit 4 的 Rule,一切都将是 Extension,那也是要求 Mockito 能够与之俱进。现在等来了,JUnit 5 进化到了 5.2.0, Mockito 也早已有了一个单独的模块 mockito-junit-jupiter 来迎接它。

在 Mockito 2.1.0 的 What's new in Mockito 2 中记述了 JUnit 5 为 Mockito 2 开发了一个 MockitoExtension。追溯到 Mockito 2 的 Release Notes, 我们发现 Mockito 2 官方最早引入 MockitoExtension 的版本是 2.16.3(2018-03023)。我对 Mockito 对 JUnit 5 支持的最新更新是从这个 Pull Request MockitoExtension for JUnit5 得知的。

一句话讲就是现在的 Mockito 2 有原生态的 MockitoExtension 来支援 JUnit 5, 可以非常放心可靠的让 JUnit 5 和 Mockito 2 一起稳定工作。因此前面那个包名带 example 字样的 MockitoExtension 链接也就无效了。 阅读全文 >>

使用 Mockito 的 @InjectMocks 创建被测试类实例

初识 Mockito 这个测试框架后,我们要使用 Mock 的属性创建一个被测试类实例时,大概会下面这么纯手工来打造。

假定类 UserService 有一个属性 UserDao userDao, 需要构造 UserService 实例时 Mock 内部状态

UserDao userDao = Mockito.mock(UserDao.class);
UserService testMe = new UserService(userDao);

如此,userDao 的行为就可以自由模拟了,这种纯手工方式都不需要给测试类添加

@RunWith(MockitoJunitRuner.class)
//或
MockitoAnnotations.initMocks(this);

因为上面两句是给 Mockito 的注解使用的。

如果所有的 Mock 对象全部通过手工来创建,那就不容易体现出 Mockito 的优越性出来。因此对于被测试对象的创建,Mock 属性的注入应该让 @Mock 和 @InjectMocks 这两个注解大显身手了。

标注在实例变量上的 @Mock 相当于是 Mockito.mock(Class) 创建了一个 Mock 对象,而 @InjectMock 标的实例会寻找到相应 Mock 属性想法构造出被测试类的实例。看下面的例子: 阅读全文 >>

JMockit 中被 Mocked 的对象属性及方法的默认值

前脚研究完 Mockito 中被 Mocked 的对象属性及方法的默认值, 虽然目今更多的是拥抱着 Mockito, 但总有时对 JMockit 也会挤眉弄眼,谁叫 JMockit 无所不能呢!被 Mockito 的 Mock 对象方法的默认返回值洗脑之后,进而觉察出 JMockit 应该有同样的实现方式。

经过类似的测试,这里不详细列出测试过程,只是在基于前篇的测试中加入 JMockit 的依赖,最新版是 1.36。测试类 MyClassTest 中使用

来构造 MyClass 的 mock 对象 myClass, 其余代码是一样的。相关代码请前往上篇 Mockito 中被 Mocked 的对象属性及方法的默认值 中找。 使用 JMockit 后跑出来的效果如下: 阅读全文 >>

Mockito 中被 Mocked 的对象属性及方法的默认值

在 Java 测试中使用 Mockito 有段时日了,以前只是想当然的认为 Mock 的对象属性值和方法返回值都是依据同样的规则。基本类型是 0, 0.0, 或 false, 对象类型都是 null, Mock 对象的默认返回值也应该是一样的。直到最近有一天,有一个返回 Optional<String> 类型的方法,由于忘记对该方法打桩,意外的发现它返回的不是 null, 而 Optional.empty(), 因此才意识到此处定有蹊跷。着实有必要用代码验证一下 Mockito 是怎么决定属性及方法的各种返回类型的默认值的。

此次测试所用的 Mockito 版本是 mockito-core-2.12.0.

于是创建了下面一个类 MyClass 用于生成 Mock 对象,选取了一些典型的数据类型, 包括 int, Double, String, long[], Optional<String>, Collection<String>, Map<String, String>, 同时测试 Mock 对象默认的属性值与方法默认返回值。 阅读全文 >>

学习并体验 JUnit 5 新特性

上一篇 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 配置就是

这样在当前的 IntelliJ IDEA(2017.2.4) 可以执行 JUnit 5 的测试用例。但要让 Maven 找到 JUnit 5 的测试用例,还得在 pom.xml 中加上 阅读全文 >>

JUnit 中是以测试方法为一个独立的生命周期

在研究 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 方法来复位该实例变量,现在才知道那是多余的。像下面的代码

阅读全文 >>

JUnit 5 快速上手(从 JUnit 4 到 JUnit 5)

一直在关注 JUnit 5 的演进,自两年前首个 ALPHA 版后,经历了 6 的 Milestone, 3 个 RC 终于在 2017/09/10 正式发布了。其实还从未对其深究过,今天算是正式开始体验。

不像以往的版本,JUnit 5 现在是三个模块的合体 JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  1. JUnit Platform: 运行测试的基础平台。还定义了开发测试框架的 TestEngine API。并提供了命令行执行测试以及与 Gradle, Maven, JUnit4 Runner 的集成
  2. JUnit Jupiter: 包含了新的编程和扩展模型。它还提供了一个运行新型测试的 TestEngine 实现
  3. JUnit Vintage: 提供了一个让 JUnit Platform 运行 JUnit 3 和 JUnit 4 的 TestEngine 实现

以上三个模块分工还是很明确,因此

  1. 从现有的 JUnit 4 项目步入到 JUnit 5 至少两 JUnit Platform 和  JUnit Vintage 两个
  2. 建立全新项目可以只引入  JUnit Platform 和 JUnit Jupiter
  3. 混合型当然是三个全部引入

但是由于 jar 包之间本身存在某种依赖关系,所以实际上 pom.xml 可以比想像的更简单 阅读全文 >>

Kafka 发布消息时如何选择 Partition

本文旨在了解 Kafka 发送消息到有多个 Partition 的 Topic 时如何选择 Partition。或许多数人已经知道 Kafka 默认(当 key 为 null) 时采用 Round-robin 策略,也就是雨露均沾,风水轮流转,实现类是 DefaultPartitioner。但我们实际应用中为保持相关消息按序到,就必须送到指定的 Partition,方法可以有

  1. 指定 Partition 编号
  2. 指定 Key
  3. 自定义 Partitioner - 实现 org.apache.kafka.clients.producer.Partitioner, 并通过属性注册

还应考究当指定了 Key 或 Partition 编号发送消息后,后续消息 key 为 null 会选用哪个 Partition。最后再思考一个问题,Consumer 每次  poll 时是获得的消息列表是否只包含一个 Partition 源还是可以多个 Partiton 源。

为完成本次实验,可以本地搭建一个 Kafka 环境,参考 简单搭建 Apache Kafka 分布式消息系统。待 Zookeeper 和 Kafka 正常启动后,我们用下面的命令创建一个 Partition 数量为 3 的 Topic partition-test

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic partition-test

验证一下该 Topic 的信息 阅读全文 >>

JMockit 中捕获 mock 对象方法的调用参数

三个月前写过一篇 Mockito 中捕获 mock 对象方法的调用参数,一般项目中 Mockito 不决求助于 JMockit, 同样的在 JMockit 也需对捕获被 Mock 的方法调用参数。当我们用 new Expectations(){{}} 打桩并在后面断言了返回值,那就无需捕获参数来断言,匹配到了方法调用即证明传入的参数也是对的,如下面的代码所示

public class UserServiceTest {

    @Mocked
    private UserDao userDao;

    @Test
    public void couldCallUserDaoToAddUser() {
        new Expectations(){{
           userDao.findById(123);
           result = "Yanbin";
        }};

        UserService userService = new UserService(userDao);
        String user = userService.findBy(123);

        assertThat(user).isEqualTo("Yanbin");  //这里断言成功也就证明了 userDao.findById(123) 方法被调用,参数必须是 123
    }
}

但如果是未打桩的方法,或打桩是用的模糊参数(withInstanceOf(String.class)), 或是无返回值的方法就要事后对是否调用了某个方法以及传入什么参数的情况进行断言。 阅读全文 >>

使用 Awaitility 测试异步代码

对于同步方法的测试很简单,调用完后可立马检查执行状态; 而异步方法,由于我们无法确切的知道何时结束,因此以往的办法是用 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 中的显示方式):

  1. org.awaitility:awaitility:2.0.0:test
  2. org.assertj:assertj-core: version: 3.8.0:test
  3. org.mockito:mockito-core:2.7.22:test

当然如果项目中没有异步调用自然是不需要 Awaitility, 在我的项目中是基本不可能的。以上三种都追求 DSL,以流畅的方式进行愉快的测试。

现在来尝试下 Awaitility 的几种基本的用法,先假定有下面的代码 UserService 阅读全文 >>