recommand to use static declared mock method

This commit is contained in:
金戟 2020-12-27 22:07:08 +08:00
parent e11f2e1645
commit 9a7372d9ab
9 changed files with 76 additions and 26 deletions

View File

@ -35,11 +35,11 @@ The `String` type in Kotlin language is actually `kotlin.String` instead of `jav
In actual scenarios, there are very few scenarios where methods in the `String` class need to be mocked, so `TestableMock` has not dealt with this situation specifically.
#### 6. When trigger a single test case in IntelliJ IDE 2020.3, why class with `@EnablePrivateAccess` annotation report still private member access errors?
#### 6. Will the mock definition still be valid when it is **indirectly called** from other test classes?
From version `2020.2.2`, the compiler provided by IntelliJ handle the annotation processor of the `JSR-269` specification in an incompatible way of maven. You can turn on the "Delegate IDE build/run actions to maven" option in "Build Tools > Maven > Runner" of IntelliJ system configuration:
Equally effective, the scope of mock is the entire test runtime process.
![delegate-ide-build-to-maven](https://testable-code.oss-cn-beijing.aliyuncs.com/delegate-ide-build-to-maven.png)
For example, some private methods and external invocation in the `Aaa` class are mocked, mock method are defined in the test class `AaaTest`. When testing the `Bbb` class in another test class `BbbTest`, some mocked methods are invoked indirectly in the `Aaa` class, the actual call will also be routed to the mock method defined in the `AaaTest` class.
#### 7. Can `TestableMock` be used for testing Android projects?

View File

@ -0,0 +1,22 @@
Use TestableMock In IDE
---
## Use IntelliJ IDE
IntelliJ IDE supports the `JSR-269` annotation processor and the `maven-surefire-plugin` arguments very well (both are techniques back the `TestableMock`). Usually you don't need any special configuration to make everything work, it's all out of the box.
> In IntelliJ 2020.3 and later versions, its built-in annotation processor had some parameter types changed, which no longer consistent with the standard `Maven` compilation process. In the version of `TestableMock` lower than `0.4.5`, you will encounter the problem that the `@EnablePrivateAccess` annotation does not take effect. You can turn on "Delegate IDE build/run actions to maven" option in "Build Tools > Maven > Runner" of the system configuration to solve the problem.
>
> ![delegate-ide-build-to-maven](https://testable-code.oss-cn-beijing.aliyuncs.com/delegate-ide-build-to-maven.png)
## Use Eclipse IDE
Since the built-in compilation feature of `Eclipse` is based on a self-made compiler, it is not compatible with the standard `javac` compilation process, which will cause the `@EnablePrivateAccess` annotation to be invalid when running test cases in the IDE. However, the function of accessing the private members of the class under test through the `PrivateAccessor` tool class will not be affected by differences in the compiler.
If the `@EnablePrivateAccess` annotation is used in the project, you can use `mvn test -Dtest=<TestClassName>` and `mvn test -Dtest=<TestClassName>#<TestCaseName>` in the command line of `Eclipse` to run a single test class or test case.
At the same time, because the built-in unit test executor of `Eclipse` completely ignores the configuration of the `pom.xml` file, additional configuration is required to use the Mock function.
Take the use of `JUnit` as an example. You need to pull down from the small triangle next to the run button on the IDE toolbar, select "Run Configurations...", select the task to run the unit test on the left side, and switch to "arguments" Tab on the right side, append a `-javaagent:` parameter in the "VM Options", the following figure is an example, note that the `testable-agent` package should be modified to match the actual situation of the local Maven repository path.
![eclipse-junit-configuration](https://testable-code.oss-cn-beijing.aliyuncs.com/eclipse-junit-configuration.png)

View File

@ -7,13 +7,15 @@ Compared with the class-granularity mocking practices of existing mock tools, `T
> - Mock non-constructive method, copy the original method definition to the test class, add a parameter of the same type as the caller, and add a `@MockMethod` annotation
> - Mock construction method, copy the original method definition to the test class, replace the return value with the constructed type, the method name is arbitrary, and add a `@MockContructor` annotation
> **Note**: There is also a convention in the current version that the name of the test class should be `<NameOfClassUnderTest> + Test` (and in the same package path), which is usually the by-default naming convention of Java project managed by `Maven` or `Gradle`. This constraint may be relaxed or removed in future versions of `TestableMock`.
> **Mock convention**:
> - The name of the test class should be `<NameOfClassUnderTest> + Test` (and in the same package path), which is usually the by-default naming convention of Java project managed by `Maven` or `Gradle`. This constraint may be relaxed or removed in future versions of `TestableMock`.
> - Methods that is decorated by `@MockMethod` or `@MockContructor` annotations will be automatically modified to `static` methods during runtime. Please do not access any non-`static` members in these methods. To be on the safe side, it is recommended to define these methods directly as `static`. In future versions, a compile-time warning will be added to non-`static` declared mock methods.
The detail mock method definition convention is as follows:
#### 1. Mock method calls of any class
Define an ordinary method annotated with `@MockMethod` in the test class with exactly the same signature (name, parameter, and return value type) as the method to be mocked, and then add an extra parameter as the first parameter of method, with the same type as the object that the method originally belongs to.
Define an ordinary method annotated with `@MockMethod` in the test class with exactly the same signature (name, parameter, and return value type) as the method to be mocked (`static` declaration is recommended), and then add an extra parameter as the first parameter of method, with the same type as the object that the method originally belongs to.
At this time, all invocations to that original method in the class under test will be automatically replaced with invocations to the above-mentioned mock method when the unit test is running.
@ -27,7 +29,7 @@ For example, there is a call to `"anything".substring(1, 2)` in the class under
// Adds a `String` type parameter to the first position the mock method parameter list (parameter name is arbitrary)
// This parameter can be used to get the value and context of the actual invoker at runtime
@MockMethod
private String substring(String self, int i, int j) {
private static String substring(String self, int i, int j) {
return "sub_string";
}
```
@ -38,7 +40,7 @@ The following example shows the usage of the `targetMethod` parameter, and its e
// Use `targetMethod` to specify the name of the method that needs to be mocked
// The method itself can now be named arbitrarily, but the method parameters still need to follow the same matching rules
@MockMethod(targetMethod = "substring")
private String use_any_mock_method_name(String self, int i, int j) {
private static String use_any_mock_method_name(String self, int i, int j) {
return "sub_string";
}
```
@ -57,7 +59,7 @@ For example, there is a private method with the signature `String innerFunc(Stri
// The type to test is `DemoMock`
// So when defining the mock method, add a parameter of type `DemoMock` to the first position of parameter list (the name is arbitrary)
@MockMethod
private String innerFunc(DemoMock self, String text) {
private static String innerFunc(DemoMock self, String text) {
return "mock_" + text;
}
```
@ -75,7 +77,7 @@ For example, if the static method `secretBox()` of the `BlackBox` type is invoke
// When defining the mock method, add a parameter of type `BlackBox` to the first position parameter list (the name is arbitrary)
// This parameter is only used to identify the target type, the actual incoming value will always be `null`
@MockMethod
private BlackBox secretBox(BlackBox ignore) {
private static BlackBox secretBox(BlackBox ignore) {
return new BlackBox("not_secret_box");
}
```
@ -84,7 +86,7 @@ For complete code examples, see the `should_able_to_mock_static_method()` test c
#### 4. Mock `new` operation of any type
Define an ordinary method annotated with `@MockContructor` in the test class, make the return value type of the method the type of the object to be created, and the method parameters are exactly the same as the constructor parameters to be mocked, the method name is arbitrary.
Define an ordinary method annotated with `@MockContructor` in the test class (`static` declaration is recommended), make the return value type of the method the type of the object to be created, and the method parameters are exactly the same as the constructor parameters to be mocked, the method name is arbitrary.
At this time, all operations in the class under test that use `new` to create the specified class (and use the constructor that is consistent with the mock method parameters) will be replaced with calls to the custom method.
@ -94,7 +96,7 @@ For example, if there is a call to `new BlackBox("something")` in the class unde
// The signature of the constructor to be mocked is `BlackBox(String)`
// No need to add additional parameters to the mock method parameter list, and the name of the mock method is arbitrary
@MockContructor
private BlackBox createBlackBox(String text) {
private static BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
```

View File

@ -4,9 +4,10 @@
- [Fast Mocking](en-us/doc/use-mock.md)
- [Test Void Method](en-us/doc/test-void-method.md)
- Usage Reference
- Usage Guide
- [Verify Mock Invocation](en-us/doc/invoke-matcher.md)
- [Frequently Asked Questions](en-us/doc/frequently-asked-questions.md)
- [Use TestableMock In IDE](en-us/doc/use-in-ide.md)
- [Self-Help Troubleshooting](en-us/doc/troubleshooting.md)
- [Testable Maven Plugin](en-us/doc/use-maven-plugin.md)

View File

@ -35,11 +35,11 @@ Kotlin语言中的`String`类型实际上是`kotlin.String`,而非`java.lang.S
实际场景中需要对`String`类中的方法进行Mock的场景很少`TestableMock`暂未对这种情况做特别处理。
#### 6. 在IntelliJ IDE 2020.3版本中运行单个测试用例时,用了`@EnablePrivateAccess`注解还是报私有成员访问错误
#### 6. 当被Mock的方法被其它测试类**间接调用**时依然有效吗
IntelliJ从`2020.2.2`版本以后IntelliJ对`JSR-269`规范注解处理器的处理机制发生了变化与Maven标准不再完全兼容。可通过IntelliJ系统配置的"Build Tools > Maven > Runner"中开启"Delegate IDE build/run actions to maven"选项解决:
同样有效Mock的作用范围是整个测试运行过程。
![delegate-ide-build-to-maven](https://testable-code.oss-cn-beijing.aliyuncs.com/delegate-ide-build-to-maven.png)
例如测试类`AaaTest`中Mock了`Aaa`类的某些私有方法、以及某些公有方法中的外部调用;在另一个测试类`BbbTest`中测试`Bbb`类时,某些方法间接用到了`Aaa`类被Mock过的方法或调用此时实际调用的同样会是`AaaTest`类中定义的Mock方法。
#### 7. `TestableMock`能否用于Android项目的测试

View File

@ -0,0 +1,22 @@
在IDE中运行单元测试
---
## 使用IntelliJ IDE
IntelliJ IDE对`TestableMock`所用到的`JSR-269`注释处理器以及`maven-surefire-plugin`插件的附加参数均支持良好。通常无需特殊配置,可开箱即用。
> 在IntelliJ 2020.3及后续版本里,其内置的编译期注释处理器运行时参数类型与标准`Maven`编译过程不一致。在`TestableMock`低于`0.4.5`的版本中会遇到`@EnablePrivateAccess`注解未生效的问题,可通过在系统配置的"Build Tools > Maven > Runner"中开启"Delegate IDE build/run actions to maven"选项解决。
>
> ![delegate-ide-build-to-maven](https://testable-code.oss-cn-beijing.aliyuncs.com/delegate-ide-build-to-maven.png)
## 使用Eclipse IDE
由于`Eclipse`内置的自动编译功能基于三方编译器实现,与标准`javac`编译过程不兼容会导致在IDE中运行测试用例时`@EnablePrivateAccess`注解无效。不过,通过`PrivateAccessor`工具类访问被测类私有成员的功能不会受编译器差异影响。
若项目中使用了`@EnablePrivateAccess`注解,可在`Eclipse`的命令行中使用`mvn test -Dtest=<测试类名>`和`mvn test -Dtest=<测试类名>#<测试用例名>`来运行单个测试类或测试用例。
同时,由于`Eclipse`内置的单元测试执行器完全忽略`pom.xml`文件的配置因此若需使用Mock功能需进行额外配置。
以使用`JUnit`为例方法为从IDE工具栏的运行按钮旁边的小三角处下拉选择"Run Configurations...",左侧选择要运行单元测试的任务,在右侧切换到"arguments"标签页,在"VM Options"里添加`-javaagent:`参数,下图为示例,注意应修改`testable-agent`包为与实际情况匹配的本地Maven仓库路径。
![eclipse-junit-configuration](https://testable-code.oss-cn-beijing.aliyuncs.com/eclipse-junit-configuration.png)

View File

@ -7,7 +7,9 @@
> - Mock非构造方法拷贝原方法定义到测试类增加一个与调用者类型相同的参数加`@MockMethod`注解
> - Mock构造方法拷贝原方法定义到测试类返回值换成构造的类型方法名随意加`@MockContructor`注解
> **注意**:当前版本还有一项约定是,测试类与被测类的包路径应相同,且名称为`被测类名+Test`,通常采用`Maven`或`Gradle`构建的Java项目符合这种惯例。此约定在未来的`TestableMock`版本中可能会被放宽或去除。
> **Mock约定**
> - 测试类与被测类的包路径应相同,且名称为`被测类名+Test`。通常采用`Maven`或`Gradle`构建的Java项目均符合这种惯例。此约定在未来的`TestableMock`版本中可能会被放宽或去除(请关注[Issue-12](https://github.com/alibaba/testable-mock/issues/12))。
> - 包含`@MockMethod`或`@MockContructor`注解的方法会在运行期被自动修改为`static`方法,请勿在这些方法中访问任何非`static`成员。为保险起见,建议将这些方法直接定义为`static`在未来版本中会对非静态定义的Mock方法增加编译期警告。
具体的Mock方法定义约定如下
@ -27,7 +29,7 @@
// 则Mock方法签名在其参数列表首位增加一个类型为`String`的参数(名字随意)
// 此参数可用于获得当时的实际调用者的值和上下文
@MockMethod
private String substring(String self, int i, int j) {
private static String substring(String self, int i, int j) {
return "sub_string";
}
```
@ -38,7 +40,7 @@ private String substring(String self, int i, int j) {
// 使用`targetMethod`指定需Mock的方法名
// 此方法本身现在可以随意命名,但方法参数依然需要遵循相同的匹配规则
@MockMethod(targetMethod = "substring")
private String use_any_mock_method_name(String self, int i, int j) {
private static String use_any_mock_method_name(String self, int i, int j) {
return "sub_string";
}
```
@ -51,13 +53,13 @@ private String use_any_mock_method_name(String self, int i, int j) {
操作方法与前一种情况相同Mock方法的第一个参数类型需与被测类相同即可实现对被测类自身不论是公有或私有成员方法的覆写。
例如,被测类中有一个签名为`String innerFunc(String)`的私有方法,我们希望在测试的时候将它替换掉,则只需在测试类定义如下方法:
例如,被测类中有一个签名为`String innerFunc(String)`的私有方法(建议使用静态方法),我们希望在测试的时候将它替换掉,则只需在测试类定义如下方法:
```java
// 被测类型是`DemoMock`
// 因此在定义Mock方法时在目标方法参数首位加一个类型为`DemoMock`的参数(名字随意)
@MockMethod
private String innerFunc(DemoMock self, String text) {
private static String innerFunc(DemoMock self, String text) {
return "mock_" + text;
}
```
@ -75,7 +77,7 @@ private String innerFunc(DemoMock self, String text) {
// 在定义Mock方法时在目标方法参数首位加一个类型为`BlackBox`的参数(名字随意)
// 此参数仅用于标识目标类型,实际传入值将始终为`null`
@MockMethod
private BlackBox secretBox(BlackBox ignore) {
private static BlackBox secretBox(BlackBox ignore) {
return new BlackBox("not_secret_box");
}
```
@ -84,7 +86,7 @@ private BlackBox secretBox(BlackBox ignore) {
#### 4. 覆写任意类的new操作
在测试类里定义一个有`@MockContructor`注解的普通方法使该方法返回值类型为要被创建的对象类型且方法参数与要Mock的构造函数参数完全一致方法名称随意。
在测试类里定义一个有`@MockContructor`注解的普通方法(建议使用静态方法)使该方法返回值类型为要被创建的对象类型且方法参数与要Mock的构造函数参数完全一致方法名称随意。
此时被测类中所有用`new`创建指定类的操作并使用了与Mock方法参数一致的构造函数将被替换为对该自定义方法的调用。
@ -94,7 +96,7 @@ private BlackBox secretBox(BlackBox ignore) {
// 要覆写的构造函数签名为`BlackBox(String)`
// 无需在Mock方法参数列表增加额外参数Mock方法的名称随意起
@MockContructor
private BlackBox createBlackBox(String text) {
private static BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
```

View File

@ -4,9 +4,10 @@
- [快速Mock任意方法](zh-cn/doc/use-mock.md)
- [测试无返回值的方法](zh-cn/doc/test-void-method.md)
- 使用参考
- 使用指南
- [校验Mock调用](zh-cn/doc/invoke-matcher.md)
- [常见使用问题](zh-cn/doc/frequently-asked-questions.md)
- [在IDE运行单元测试](zh-cn/doc/use-in-ide.md)
- [自助问题排查](zh-cn/doc/troubleshooting.md)
- [Testable Maven插件](zh-cn/doc/use-maven-plugin.md)

View File

@ -68,7 +68,7 @@ public class TestClassHandler extends BaseClassHandler {
if (thisRef != null) {
mn.localVariables.remove(thisRef);
} else {
LogUtil.error("Fail to find `this` reference in none-static method " + getName(cn, mn));
LogUtil.error("Fail to find `this` reference in non-static method " + getName(cn, mn));
return;
}
for (AbstractInsnNode in : mn.instructions) {
@ -76,7 +76,7 @@ public class TestClassHandler extends BaseClassHandler {
if (((VarInsnNode)in).var > 0) {
((VarInsnNode)in).var--;
} else if (in.getOpcode() == ALOAD) {
LogUtil.error("Attempt to access none-static member in mock method " + getName(cn, mn));
LogUtil.error("Attempt to access non-static member in mock method " + getName(cn, mn));
return;
}
}