testable-mock/docs/zh-cn/doc/use-mock.md
2020-12-30 07:24:49 +08:00

123 lines
7.1 KiB
Markdown
Raw Blame History

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.

快速Mock被测类的任意方法调用
---
相比以往Mock工具以类为粒度的Mock方式`TestableMock`允许用户直接定义需要Mock的单个方法并遵循约定优于配置的原则按照规则自动在测试运行时替换被测方法中的指定方法调用。
> 归纳起来就两条:
> - Mock非构造方法拷贝原方法定义到测试类增加一个与调用者类型相同的参数加`@MockMethod`注解
> - Mock构造方法拷贝原方法定义到测试类返回值换成构造的类型方法名随意加`@MockContructor`注解
> **Mock约定**
> - 测试类与被测类的包路径应相同,且名称为`被测类名+Test`。通常采用`Maven`或`Gradle`构建的Java项目均符合这种惯例。此约定在未来的`TestableMock`版本中可能会被放宽或去除(请关注[Issue-12](https://github.com/alibaba/testable-mock/issues/12))。
> - 请勿在Mock方法的定义中访问任何非静态成员。当前包含`@MockMethod`或`@MockContructor`注解的方法会在运行期被自动修改为静态(`static`)方法。此约定在未来的版本中将被去除,请留意相关更新。
具体的Mock方法定义约定如下
#### 1. 覆写任意类的方法调用
在测试类里定义一个有`@MockMethod`注解的普通方法,使它与需覆写的方法名称、参数、返回值类型完全一致,然后在其参数列表首位再增加一个类型为该方法原本所属对象类型的参数。
此时被测类中所有对该需覆写方法的调用将在单元测试运行时将自动被替换为对上述自定义Mock方法的调用。
**注意**:当遇到待覆写方法有重名时,可以将需覆写的方法名写到`@MockMethod`注解的`targetMethod`参数里这样Mock方法自身就可以随意命名了。
例如,被测类中有一处`"anything".substring(1, 2)`调用,我们希望在运行测试的时候将它换成一个固定字符串,则只需在测试类定义如下方法:
```java
// 原方法签名为`String substring(int, int)`
// 调用此方法的对象`"anything"`类型为`String`
// 则Mock方法签名在其参数列表首位增加一个类型为`String`的参数(名字随意)
// 此参数可用于获得当时的实际调用者的值和上下文
@MockMethod
private String substring(String self, int i, int j) {
return "sub_string";
}
```
下面这个例子展示了`targetMethod`参数的用法,其效果与上述示例相同:
```java
// 使用`targetMethod`指定需Mock的方法名
// 此方法本身现在可以随意命名,但方法参数依然需要遵循相同的匹配规则
@MockMethod(targetMethod = "substring")
private String use_any_mock_method_name(String self, int i, int j) {
return "sub_string";
}
```
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_common_method()`测试用例。(由于Kotlin对String类型进行了魔改故Kotlin示例中将被测方法在`BlackBox`类里加了一层封装)
#### 2. 覆写被测类自身的成员方法
有时候在对某些方法进行测试时希望将被测类自身的另外一些成员方法Mock掉。
操作方法与前一种情况相同Mock方法的第一个参数类型需与被测类相同即可实现对被测类自身不论是公有或私有成员方法的覆写。
例如,被测类中有一个签名为`String innerFunc(String)`的私有方法,我们希望在测试的时候将它替换掉,则只需在测试类定义如下方法:
```java
// 被测类型是`DemoMock`
// 因此在定义Mock方法时在目标方法参数首位加一个类型为`DemoMock`的参数(名字随意)
@MockMethod
private String innerFunc(DemoMock self, String text) {
return "mock_" + text;
}
```
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_member_method()`测试用例。
#### 3. 覆写任意类的静态方法
对于静态方法的Mock与普通方法相同。但需要注意的是静态方法的Mock方法被调用时传入的第一个参数实际值始终是`null`。
例如,在被测类中调用了`BlackBox`类型中的静态方法`secretBox()`,改方法签名为`BlackBox secretBox()`则Mock方法如下
```java
// 目标静态方法定义在`BlackBox`类型中
// 在定义Mock方法时在目标方法参数首位加一个类型为`BlackBox`的参数(名字随意)
// 此参数仅用于标识目标类型,实际传入值将始终为`null`
@MockMethod
private BlackBox secretBox(BlackBox ignore) {
return new BlackBox("not_secret_box");
}
```
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_static_method()`测试用例。
#### 4. 覆写任意类的new操作
在测试类里定义一个有`@MockContructor`注解的普通方法使该方法返回值类型为要被创建的对象类型且方法参数与要Mock的构造函数参数完全一致方法名称随意。
此时被测类中所有用`new`创建指定类的操作并使用了与Mock方法参数一致的构造函数将被替换为对该自定义方法的调用。
例如,在被测类中有一处`new BlackBox("something")`调用希望在测试时将它换掉通常是换成Mock对象或换成使用测试参数创建的临时对象则只需定义如下Mock方法
```java
// 要覆写的构造函数签名为`BlackBox(String)`
// 无需在Mock方法参数列表增加额外参数Mock方法的名称随意起
@MockContructor
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
```
> 也可以依然使用`@MockMethod`注解,并配置`targetMethod`参数值为`"<init>"`,其余同上。效果与使用`@MockContructor`注解相同
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_new_object()`测试用例。
#### 5. 识别当前测试用例和调用来源
在Mock方法中可以通过`TestableTool.SOURCE_METHOD`变量来识别**进入该Mock方法前的被测类方法名称**;此外,通过`TestableTool.MOCK_CONTEXT`变量能够为Mock方法注入**额外的上下文参数**,从而区分处理不同的调用场景。
注意,由于`TestableMock`并不依赖(也不希望依赖)任何特定测试框架,因而无法自动识别单个测试用例的结束位置,这使得设置到`TestableTool.MOCK_CONTEXT`变量的参数可能会在同测试类中跨测试用例存在。建议总是在使用后及时使用`MOCK_CONTEXT.clear()`清空上下文也可将这行语句添加到单元测试框架特定的测试用例结束的统一位置比如JUnit 5的`@AfterEach`方法。
> `TestableTool.MOCK_CONTEXT`变量目前是在测试类内共享的,当单元测试并行运行时,建议请选择`parallel`类型为`classes`
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_get_source_method_name()`和`should_able_to_get_test_case_name()`测试用例。
#### 6. 验证Mock方法被调用的顺序和参数
在测试用例中可用通过`TestableTool.verify()`方法,配合`with()`、`withInOrder()`、`without()`、`withTimes()`等方法实现对Mock调用情况的验证。
详见[校验Mock调用](zh-cn/doc/matcher.md)文档。