Merge branch 'master' into 0.5

* master:
  support private method invoke with null parameter
  put parameter constructor doc to sidebar
  update en-us docs to fit targetClass parameter usage
This commit is contained in:
金戟 2021-01-21 22:36:18 +08:00
commit 019e407a4f
13 changed files with 126 additions and 52 deletions

View File

@ -10,9 +10,8 @@
阅读[这里](https://mp.weixin.qq.com/s/KyU6Eu7mDkZU8FspfSqfMw)了解更多故事。
> 特别说明
> 1. 如有遇到启动报空指针异常或"Operand stack overflow"错误,请升级`TestableMock`版本这是BUG已修复😛
> 2. 如遇到"Attempt to access none-static member in mock method"错误,参见[常见问题](https://alibaba.github.io/testable-mock/#/zh-cn/doc/frequently-asked-questions)第8条
> 3. 如果有遇到其他任何使用问题和建议,请直接在[Issue](https://github.com/alibaba/testable-mock/issues)中提出,也可通过[Pull Request](https://github.com/alibaba/testable-mock/pulls)提交您的代码我们将在24小时内回复并处理
> 1. 如遇到"Attempt to access none-static member in mock method"错误,参见[常见问题](https://alibaba.github.io/testable-mock/#/zh-cn/doc/frequently-asked-questions)第8条
> 2. 如果有遇到其他任何使用问题和建议,请直接在[Issue](https://github.com/alibaba/testable-mock/issues)中提出,也可通过[Pull Request](https://github.com/alibaba/testable-mock/pulls)提交您的代码我们将在24小时内回复并处理
-----
@ -22,7 +21,7 @@
- `0.4.x` 当前版本,进行中的工作内容参考[Issue](https://github.com/alibaba/testable-mock/issues)清单
- `0.5` 实现以"Mock方法集"为单元的Mock方法复用机制让测试类之间可以方便复用相同的Mock方法
- `0.6` 实现第四项单元测试增强能力"快速入参构造器"。不论被测方法所需的参数结构多么错综复杂、甚至没有合适的构造方法、甚至需要私有内部类对象... 呼唤TestableMock马上递给您~
- `0.5.?` 实现第四项单元测试增强能力"[快速入参构造器]()"
## 目录结构

View File

@ -32,7 +32,7 @@ public class DemoPrivateAccess {
* private static method with arguments
*/
private static String privateStaticFuncWithArgs(String str, int i) {
return str + " + " + i;
return (str == null ? "null" : str) + " + " + i;
}
/**

View File

@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* 演示私有成员访问功能
@ -57,6 +58,20 @@ class DemoPrivateAccessTest {
void should_able_to_update_final_field() {
demoPrivateAccess.pi = 4.13;
assertEquals(Double.valueOf(4.13), demoPrivateAccess.pi);
PrivateAccessor.set(demoPrivateAccess, "pi", 3.14);
assertEquals(Double.valueOf(3.14), PrivateAccessor.get(demoPrivateAccess, "pi"));
}
@Test
void should_able_to_use_null_parameter() {
demoPrivateAccess.pi = null;
assertNull(demoPrivateAccess.pi);
assertEquals("null + 1", DemoPrivateAccess.privateStaticFuncWithArgs(null, 1));
PrivateAccessor.set(demoPrivateAccess, "pi", null);
assertNull(PrivateAccessor.get(demoPrivateAccess, "pi"));
assertEquals("null + 1", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFuncWithArgs", null, 1));
}
}

View File

@ -1,6 +1,6 @@
Quickly construct complex input parameters
Parameter constructor
---
Generate arbitrarily nested object instances, simplify their internal member assignment methods, and solve the problem of lengthy initialization code for the parameters of the tested method.
No matter how intricate the parameter structure required by the method under test is, even there is no suitable construction method, or even there are private internal class objects... Call `TestableMock`, the parameter object will be handed to you immediately~
This feature is planned to be released in the `0.6` version.
This feature is planned to be released in the `0.5.x` version.

View File

@ -3,9 +3,10 @@ Use TestableMock
`TestableMock` is an assist tool for Java unit testing based on source code and bytecode enhancement, including the following functions:
- [Quickly mock arbitrary call](en-us/doc/use-mock.md): quickly replace any method invocation in the class under test with a mock method, solve the cumbersome use of traditional mock tools problem
- [Access private members of the class under test](en-us/doc/private-accessor.md): enable unit tests directly invoke or access private members of the class under test, solve the problems of private member initialization and private method testing
- [Quick mock arbitrary call](en-us/doc/use-mock.md): quickly replace any method invocation in the class under test with a mock method, solve the cumbersome use of traditional mock tools problem
- [Auxiliary test void method](en-us/doc/test-void-method.md): use the mock validator to check the internal logic of method, solve the problem that unit testing is difficult to implement to the method with no return value
- [Quickly construct complicated parameter object](en-us/doc/parameter-constructor.md)generate arbitrarily nested object instances, simplify their internal member assignment methods, solve the problem of long initialization codes for method parameters
## Use in Maven project
@ -67,7 +68,7 @@ dependencies {
}
```
Then add javaagent to `test` configuration
Then add `javaagent` to "test" configuration
```groovy
test {
@ -76,3 +77,19 @@ test {
```
See the [build.gradle](https://github.com/alibaba/testable-mock/blob/master/demo/java-demo/build.gradle) file of project `java-demo` and the [build.gradle.kts](https://github.com/alibaba/testable-mock/blob/master/demo/kotlin-demo/build.gradle.kts) file of project `kotlin-demo`.
> For Android project tested with `Robolectric` framework, please use the same method to add `TestableMock` dependency as above, and add `javaagent` configuration as follows:
>
> ```groovy
> android {
> testOptions {
> unitTests {
> all {
> jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
> }
> }
> }
> }
> ```
>
> See [issue-43](https://github.com/alibaba/testable-mock/issues/43) for a complete example.

View File

@ -4,80 +4,86 @@ Fast Mocking
Compared with the class-granularity mocking practices of existing mock tools, `TestableMock` allows developers to directly define a single method and use it for mocking. With the principle of convention over configuration, mock method replacement will automatically happen when the specified method in the test class match an invocation in the class under test.
> In summary, there are two simple rules:
> - 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 non-constructive method, copy the original method definition to the test class, 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
> **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`.
> - Do NOT access any non-`static` members in mock methods. Currently, methods that is decorated by `@MockMethod` or `@MockContructor` annotations will be automatically modified to `static` methods during runtime. In future versions, this constraint will be removed.
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, and then add the type of target object (which the method originally belongs to) as `targetMethod` parameter of `@MockMethod` annotation.
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.
**Note**: When several methods to be mocked have the same name, you can put the name of the method to be mocked in the `targetMethod` parameter of `@MockMethod` annotation, so that the mock method itself can be named at will.
For example, there is a call to `"anything".substring(1, 2)` in the class under test, and we want to change it to a fixed string when running the test, we only need to define the following method in the test class:
```java
// The original method signature is `String substring(int, int)`
// The object `"anything"` that invokes this method is of type `String`
// 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) {
@MockMethod(targetClass = String.class)
private String substring(int i, int j) {
return "sub_string";
}
```
When several methods to be mocked have the same name, you can put the name of the method to be mocked in the `targetMethod` parameter of `@MockMethod` annotation, so that the mock method itself can be named at will.
The following example shows the usage of the `targetMethod` parameter, and its effect is the same as the above example:
```java
// 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) {
@MockMethod(targetClass = String.class, targetMethod = "substring")
private String use_any_mock_method_name(int i, int j) {
return "sub_string";
}
```
Sometimes, the mock method need to access the member variables in the original object that initiated the invocation, or invoke other methods of the original object. At this point, you can remove the `targetClass` parameter in the `@MockMethod` annotation, and then add a extra parameter whose type is the original object type of the method to the first index of the method parameter list.
The `TestableMock` convention is that when the `targetClass` parameter value of the `@MockMethod` annotation is empty, the first parameter of the mock method is the type of the target method, and the parameter name is arbitrary. In order to facilitate code reading, it is recommended to name this parameter as `self` or `src`. Example as follows:
```java
// 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) {
// Call the original method is also allowed
return self.substring(i, j);
}
```
For complete code examples, see the `should_able_to_mock_common_method()` test cases in the `java-demo` and `kotlin-demo` sample projects. (Because Kotlin has made magical changes to the String type, the method under test in the Kotlin example adds a layer of encapsulation to the `BlackBox` class)
#### 2. Mock the member method of the class under test itself
Sometimes, when testing certain methods, it is desirable to mock out some other member methods of the class under test itself.
The solution is the same as the previous case. The first parameter type of the mock method needs to be the same as that of the class under test.
The solution is the same as the previous case. Just set `targetClass` parameter value to the type of class under test.
For example, there is a private method with the signature `String innerFunc(String)` in the class under test. If we want to replace it during testing, we only need to define the following method in the test class:
```java
// 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) {
@MockMethod(targetClass = DemoMock.class)
private String innerFunc(String text) {
return "mock_" + text;
}
```
Similarly, if the method in the above example needs to access the original tested object that initiated the call, it may not use the `targetClass` parameter, but when defining the mock method, add a parameter of type `DemoMock` to the first index of the method parameter list.
For complete code examples, see the `should_able_to_mock_member_method()` test case in the `java-demo` and `kotlin-demo` sample projects.
#### 3. Mock static methods of any class
Mock for static methods is the same as for any ordinary methods. But it should be noted that when the mock method of a static method is called, the actual value of the first parameter passed in is always `null`.
Mock for static methods is the same as for any ordinary methods.
For example, if the static method `secretBox()` of the `BlackBox` type is invoked in the class under test, and the method signature is changed to `BlackBox secretBox()`, the mock method is as follows:
For example, if the static method `secretBox()` of the `BlackBox` type is invoked in the class under test, and the method signature is `BlackBox secretBox()`, then the mock method is as follows:
```java
// The target static method is defined in the `BlackBox` type
// 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) {
@MockMethod(targetClass = BlackBox.class)
private BlackBox secretBox() {
return new BlackBox("not_secret_box");
}
```
@ -151,3 +157,11 @@ For complete code examples, see the `should_able_to_get_source_method_name()` an
In test cases, you can use the `TestableTool.verify()` method, and cooperate with `with()`, `withInOrder()`, `without()`, `withTimes()` and other methods to verify the mock call situation.
For details, please refer to the [Check Mock Call](en-us/doc/matcher.md) document.
#### Additional note
> **Mock convention in version 0.4.x**:
> - The name of the test class must 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`.
> - Do NOT access any non-`static` members in mock methods. Currently, methods that is decorated by `@MockMethod` or `@MockContructor` annotations will be automatically modified to `static` methods during runtime.
>
> These constraints will be removed in `0.5` versions of `TestableMock`.

View File

@ -1,8 +1,9 @@
- Quick Start
- [Use TestableMock](en-us/doc/setup.md)
- [Private Accessor](en-us/doc/private-accessor.md)
- [Fast Mocking](en-us/doc/use-mock.md)
- [Private Accessor](en-us/doc/private-accessor.md)
- [Test Void Method](en-us/doc/test-void-method.md)
- [Parameter constructor](en-us/doc/parameter-constructor.md)
- Usage Guide
- [Verify Mock Invocation](en-us/doc/invoke-matcher.md)

View File

@ -1,6 +1,6 @@
快速构造复杂入参
---
生成任意多层嵌套的对象实例,并简化其内部成员赋值方式,解决被测方法参数初始化代码冗长的问题。
不论被测方法所需的参数结构多么错综复杂、甚至没有合适的构造方法、甚至需要私有内部类对象... 呼唤TestableMock马上递给您~
计划在`0.6`版本中推出。
计划在`0.5.x`版本中推出。

View File

@ -3,9 +3,10 @@
`TestableMock`是基于源码和字节码增强的Java单元测试辅助工具包含以下功能
- [访问被测类私有成员](zh-cn/doc/private-accessor.md):使单元测试能直接调用和访问被测类的私有成员,解决私有成员初始化和私有方法测试的问题
- [快速Mock任意调用](zh-cn/doc/use-mock.md)使被测类的任意方法调用快速替换为Mock方法实现"指哪换哪"解决传统Mock工具使用繁琐的问题
- [访问被测类私有成员](zh-cn/doc/private-accessor.md):使单元测试能直接调用和访问被测类的私有成员,解决私有成员初始化和私有方法测试的问题
- [辅助测试void方法](zh-cn/doc/test-void-method.md)利用Mock校验器对方法的内部逻辑进行检查解决无返回值方法难以实施单元测试的问题
- [快速构造参数对象](zh-cn/doc/parameter-constructor.md):生成任意多层嵌套的对象实例,并简化其内部成员赋值方式,解决被测方法参数初始化代码冗长的问题
## 在Maven项目中使用
@ -58,7 +59,7 @@
## 在Gradle项目中使用
在`build.gradle`文件中添加TestableMock依赖
在`build.gradle`文件中添加`TestableMock`依赖:
```groovy
dependencies {
@ -76,3 +77,19 @@ test {
```
参见项目`java-demo`的[build.gradle](https://github.com/alibaba/testable-mock/blob/master/demo/java-demo/build.gradle)和`kotlin-demo`的[build.gradle.kts](https://github.com/alibaba/testable-mock/blob/master/demo/kotlin-demo/build.gradle.kts)文件。
> 若是基于`Robolectric`框架的Android项目则添加`TestableMock`依赖方法同上添加javaagent配置方法如下
>
> ```groovy
> android {
> testOptions {
> unitTests {
> all {
> jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
> }
> }
> }
> }
> ```
>
> 完整示例参考[issue-43](https://github.com/alibaba/testable-mock/issues/43)

View File

@ -19,13 +19,15 @@
</plugin>
```
> 当使用`testable-maven-plugin`插件时,应该移除`maven-surefire-plugin`插件上的TestableMock相关配置
**注意:**当使用`testable-maven-plugin`插件时,应该移除`maven-surefire-plugin`插件上的TestableMock相关配置
`testable-maven-plugin`插件能够与Jacoco插件直接同时使用无需额外适配因此能使`pom.xml`文件编写起来更简单且美观。
但需要注意的是当通过IDE运行单个测试用例时Mock功能会失效
> 还有一种特殊情况,当`jacoco`插件是通过`maven`命令行参数引入的时候,若要使用`TestableMock`功能,则也必须通过命令行参数引入`testable-maven-plugin`插件。详见[issue-14](https://github.com/alibaba/testable-mock/issues/14)
这是由于IDE运行单个测试用例时通常都只会运行`maven-surefire-plugin`插件,跳过了`testable-maven-plugin`插件执行导致Mock功能所需的JavaAgent没有随测试注入。
但需要注意的是,使用`testable-maven-plugin`插件后通过IntelliJ IDE运行单个测试用例时Mock功能会失效。
这是由于IntelliJ IDE运行单个测试用例时只会运行`maven-surefire-plugin`插件,跳过了`testable-maven-plugin`插件执行导致Mock功能所需的JavaAgent未随测试注入。
该问题可以通过额外配置IDE的测试参数绕过。以IntelliJ为例打开运行菜单的"编辑配置..."选型,如图中位置①

View File

@ -7,12 +7,6 @@
> - 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方法的定义中访问任何非静态成员。当Mock方法内容较复杂包含Lambda语句、构造块、匿名类等编译器会在构建期生成额外的非静态临时方法导致"Bad type in operand stack"错误。如果有遇到此类错误请将Mock方法显式加上`static`修饰即可解决。这个问题会在`0.5`版本中彻底解决。
具体的Mock方法定义约定如下
#### 1. 覆写任意类的方法调用
@ -163,3 +157,13 @@ private Data mockDemo() {
在测试用例中可用通过`TestableTool.verify()`方法,配合`with()`、`withInOrder()`、`without()`、`withTimes()`等方法实现对Mock调用情况的验证。
详见[校验Mock调用](zh-cn/doc/matcher.md)文档。
#### 特别说明
> **0.4.x 版本的Mock约定**
> - 测试类与被测类的包路径应相同,且名称为`被测类名+Test`(通常采用`Maven`或`Gradle`构建的Java项目均符合这种惯例
> - Mock方法即包含`@MockMethod`或`@MockContructor`注解的方法)会在运行期被自动修改为`static`方法请勿在Mock方法的定义中访问任何非静态成员。
>
> 这两项约束会在`0.5`版本中去除
>
> 当Mock方法内容较复杂包含Lambda语句、构造块、匿名类等编译器会在构建期生成额外的非静态临时方法导致"Bad type in operand stack"错误。如果有遇到此类错误请将Mock方法显式加上`static`修饰即可解决。这个问题会在`0.5`版本中彻底解决。

View File

@ -1,8 +1,9 @@
- 快速上手
- [使用TestableMock](zh-cn/doc/setup.md)
- [直接访问私有成员](zh-cn/doc/private-accessor.md)
- [快速Mock任意方法](zh-cn/doc/use-mock.md)
- [直接访问私有成员](zh-cn/doc/private-accessor.md)
- [测试无返回值的方法](zh-cn/doc/test-void-method.md)
- [快速构造复杂入参](zh-cn/doc/parameter-constructor.md)
- 使用指南
- [校验Mock调用](zh-cn/doc/invoke-matcher.md)

View File

@ -15,7 +15,8 @@ public class TypeUtil {
public static Class<?>[] getClassesFromObjects(Object[] parameterObjects) {
Class<?>[] cs = new Class[parameterObjects.length];
for (int i = 0; i < cs.length; i++) {
cs[i] = parameterObjects[i].getClass();
Object pObj = parameterObjects[i];
cs[i] = (pObj == null) ? null : pObj.getClass();
}
return cs;
}
@ -41,7 +42,7 @@ public class TypeUtil {
/**
* type equals
* @param classesLeft class to be compared
* @param classesRight class to compare
* @param classesRight class to compare (item can be null)
* @return whether all class equals
*/
private static boolean typeEquals(Class<?>[] classesLeft, Class<?>[] classesRight) {
@ -49,6 +50,9 @@ public class TypeUtil {
return false;
}
for (int i = 0; i < classesLeft.length; i++) {
if (classesRight[i] == null) {
return !classesLeft[i].isPrimitive();
}
if (!classesLeft[i].isAssignableFrom(classesRight[i]) && !fuzzyEqual(classesLeft[i], classesRight[i])) {
return false;
}