2020-11-26 07:04:41 +08:00
|
|
|
|
测试无返回值的方法
|
|
|
|
|
---
|
|
|
|
|
|
2020-11-28 17:03:39 +08:00
|
|
|
|
如何对void类型的方法进行测试一直是许多单元测试框架在悄悄回避的话题,由于以往的单元测试手段主要是对被测单元的返回结果进行校验,当遇到方法没有返回值时就会变得无从下手。
|
|
|
|
|
|
|
|
|
|
从功能的角度来说,虽然void方法不返回任何值,但它的执行一定会对外界产生某些潜在影响,我们将其称为方法的"副作用",比如:
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
|
|
|
|
1. 初始化某些外部变量(私有成员变量或者全局静态变量)
|
|
|
|
|
2. 在方法体内对外部对象实例进行赋值
|
|
|
|
|
3. 输出了日志
|
|
|
|
|
4. 调用了其他外部方法
|
|
|
|
|
5. ... ...
|
|
|
|
|
|
|
|
|
|
|
2020-12-12 00:05:53 +08:00
|
|
|
|
> 不返回任何值也不产生任何"副作用"的方法没有存在的意义。
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
2020-12-12 00:05:53 +08:00
|
|
|
|
这些"副作用"的本质归纳来说可分为两类:**修改外部变量**和**调用外部方法**。
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
2020-12-23 20:23:09 +08:00
|
|
|
|
通过`TestableMock`的私有字段访问和Mock校验器可以很方便的实现对"副作用"的结果检查。
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
2020-12-12 00:05:53 +08:00
|
|
|
|
### 1. 修改外部变量的void方法
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
|
|
|
|
例如,下面这个方法会根据输入修改私有成员变量`hashCache`:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
class Demo {
|
|
|
|
|
private Map<String, Integer> hashCache = mapOf();
|
|
|
|
|
|
|
|
|
|
public void updateCache(String domain, String key) {
|
|
|
|
|
String cacheKey = domain + "::" + key;
|
|
|
|
|
Integer num = hashCache.get(cacheKey);
|
|
|
|
|
hashCache.put(cacheKey, count == null ? initHash(key) : nextHash(num, key));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
... // 其他方法省略
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-12-23 20:23:09 +08:00
|
|
|
|
若要测试此方法,可以利用`TestableMock`直接读取私有成员变量的值,对结果进行校验:
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
|
|
|
|
```java
|
2020-11-30 22:57:37 +08:00
|
|
|
|
@EnablePrivateAccess // 启用TestableMock的私有成员访问功能
|
2020-11-26 07:04:41 +08:00
|
|
|
|
class DemoTest {
|
|
|
|
|
private Demo demo = new Demo();
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testSaveToCache() {
|
|
|
|
|
Integer firstVal = demo.initHash("hello"); // 访问私有方法
|
|
|
|
|
Integer nextVal = demo.nextHash(firstVal, "hello"); // 访问私有方法
|
|
|
|
|
demo.saveToCache("demo", "hello");
|
|
|
|
|
assertEquals(firstVal, demo.hashCache.get("demo::hello")); // 读取私有变量
|
|
|
|
|
demo.saveToCache("demo", "hello");
|
|
|
|
|
assertEquals(nextVal, demo.hashCache.get("demo::hello")); // 读取私有变量
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-12-12 00:05:53 +08:00
|
|
|
|
### 2. 调用外部方法的void方法
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
|
|
|
|
例如,下面这个方法会根据输入打印信息到控制台:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
class Demo {
|
|
|
|
|
public void recordAction(Action action) {
|
|
|
|
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss ");
|
|
|
|
|
String timeStamp = df.format(new Date());
|
|
|
|
|
System.out.println(timeStamp + "[" + action.getType() + "] " + action.getTarget());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2020-12-23 20:23:09 +08:00
|
|
|
|
若要测试此方法,可以利用`TestableMock`快速Mock掉`System.out.println`方法。在Mock方法体里可以继续执行原调用(相当于并不影响本来方法功能,仅用于做调用记录),也可以直接留空(相当于去除了原方法的副作用)。
|
2020-12-12 00:05:53 +08:00
|
|
|
|
|
2020-12-10 09:17:44 +08:00
|
|
|
|
在执行完被测的void类型方法以后,用`InvokeVerifier.verify()`校验传入的打印内容是否符合预期:
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
class DemoTest {
|
|
|
|
|
private Demo demo = new Demo();
|
|
|
|
|
|
|
|
|
|
// 拦截`System.out.println`调用
|
2020-12-11 20:28:33 +08:00
|
|
|
|
@MockMethod
|
2020-12-10 09:17:44 +08:00
|
|
|
|
public void println(PrintStream ps, String msg) {
|
|
|
|
|
// 执行原调用
|
|
|
|
|
ps.println(msg);
|
|
|
|
|
}
|
2020-11-26 07:04:41 +08:00
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
public void testRecordAction() {
|
|
|
|
|
Action action = new Action("click", ":download");
|
|
|
|
|
demo.recordAction();
|
|
|
|
|
// 验证Mock方法`println`被调用,且传入参数符合预期
|
2020-12-17 11:55:16 +08:00
|
|
|
|
verify("println").with(matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} \\[click\\] :download"));
|
2020-11-26 07:04:41 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|