more english docs

This commit is contained in:
金戟
2020-12-23 20:19:15 +08:00
parent 703aa319ba
commit 459fea37c3
5 changed files with 255 additions and 1 deletions

View File

@@ -10,4 +10,4 @@ However, when the current mainstream Mock framework implements the Mock function
Therefore, we developed `TestableMock`, **a maverick and lightweight mock tool**.
!#[mock](https://testable-code.oss-cn-beijing.aliyuncs.com/en-us/mock-simpson.png)
![mock](https://testable-code.oss-cn-beijing.aliyuncs.com/en-us/mock-simpson.png)

78
docs/en-us/doc/setup.md Normal file
View File

@@ -0,0 +1,78 @@
Use TestableMock
---
`TestableMock` is an assist tool for Java unit testing based on source code and bytecode enhancement, including the following functions:
- [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
## Use in Maven project
In the project `pom.xml` file, add `testable-all` dependency and `maven-surefire-plugin` configuration, the specific method is as follows.
It is recommended to add a `property` field that identifies the TestableMock version, in order to simplify version management:
```xml
<properties>
<testable.version>0.4.3</testable.version>
</properties>
```
Add dependence of `TestableMock` inside `dependencies` field:
```xml
<dependencies>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
```
Finally, add the `maven-surefire-plugin` plugin to the `plugins` list in the `build` area (if this plugin is already included, just add the `<argLine>` part of the configuration):
```xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
</plugins>
</build>
```
If the project also uses the `on-the-fly` mode of `Jacoco` (default mode) to calculate the unit test coverage, you need to add a `@{argLine}` parameter in the `<argLine>` configuration, after adding it The configuration is as follows:
```xml
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
```
See the [pom.xml](https://github.com/alibaba/testable-mock/blob/master/demo/java-demo/pom.xml) file of project `java-demo` and the [pom.xml](https://github.com/alibaba/testable-mock/blob/master/demo/kotlin-demo/pom.xml) file of project `kotlin-demo`.
## Use in Gradle project
Add dependence of `TestableMock` in `build.gradle` file:
```groovy
dependencies {
testImplementation('com.alibaba.testable:testable-all:0.4.3')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.3')
}
```
Then add javaagent to `test` configuration
```groovy
test {
jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
}
```
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`.

View File

@@ -0,0 +1,94 @@
Test Void Method
---
"How to test void type methods" has always been a topic that many unit testing frameworks are quietly avoiding. Since the existing unit testing methods are mainly to verify the returned results of the tested unit, when the method has no return value, there is no way to test.
From a functional point of view, although the void method does not return any value, its execution will definitely have some potential impact on the outside world. We call it the "side effect" of the method, such as:
1. Initialize external variables (private member variables or global static variables)
2. Assign value to external object
3. Print logs
4. Invoke other external methods
5. ... ...
> A method that does not return any value and does not produce any "side effects" has no meaning to exist.
The essence of these "side effects" can be summarized into two categories: **modify external variables** and **invoke external methods**.
Through the private field accessor and the mock validator of `TestableMock`, the "side effects" can be easily checked.
### 1. Void type method which modify external variables
For example, the following method will modify the private member variable `hashCache` based on the input:
```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));
}
... // Other methods omitted
}
```
To test this method, you can use `TestableMock` directly read the value of the private member variable and verify the result:
```java
@EnablePrivateAccess // Enable private member access functionality of TestableMock
class DemoTest {
private Demo demo = new Demo();
@Test
public void testSaveToCache() {
Integer firstVal = demo.initHash("hello"); // Invoke private method
Integer nextVal = demo.nextHash(firstVal, "hello"); // Invoke private method
demo.saveToCache("demo", "hello");
assertEquals(firstVal, demo.hashCache.get("demo::hello")); // Access private variable
demo.saveToCache("demo", "hello");
assertEquals(nextVal, demo.hashCache.get("demo::hello")); // Access private variable
}
}
```
### 2. Void type method which invoke external method
For example, the following method will print information to the console based on input:
```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());
}
}
```
To test this method, you can use `TestableMock` to quickly mock out the `System.out.println` method. In the Mock method body, you can simply call the original method (equivalent to not affecting the original method function, only used for call recording), or leave it blank (equivalent to removing the side effects of the original method).
After executing the void type method under test, use `InvokeVerifier.verify()` to verify whether the incoming print content meets expectations:
```java
class DemoTest {
private Demo demo = new Demo();
// Intercept `System.out.println` invocation
@MockMethod
public void println(PrintStream ps, String msg) {
// Execute the original call
ps.println(msg);
}
@Test
public void testRecordAction() {
Action action = new Action("click", ":download");
demo.recordAction();
// Verify mock method `println` is invoked, and passing parameters in line with expectations
verify("println").with(matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} \\[click\\] :download"));
}
}
```

View File

@@ -0,0 +1,40 @@
Self-Help Troubleshooting
---
Compared with `Mockito` and other mock tools where developers have to manually inject mock classes, `TestableMock` uses method name and parameter type matching to automatically find invocations that require mock. While this mechanism brings convenience, it may also cause unexpected mock replacement.
To troubleshoot mock-related issues, just add the `@MockWith` annotation to the test class, and configure the parameter `diagnose` to `MockDiagnose.ENABLE`, so the detailed Mock method replacement process will be printed when the test is run.
```java
@MockWith(diagnose = MockDiagnose.ENABLE)
class DemoTest {
...
}
```
The output log example is as follows:
```text
[DIAGNOSE] Handling test class com/alibaba/testable/demo/DemoMockTest
[DIAGNOSE] Handling source class com/alibaba/testable/demo/DemoMock
[DIAGNOSE] Found 7 mock methods
[DIAGNOSE] Handling method <init>
[DIAGNOSE] Handling method newFunc
[DIAGNOSE] Line 14, mock method createBlackBox used
[DIAGNOSE] Handling method outerFunc
[DIAGNOSE] Line 22, mock method innerFunc used
[DIAGNOSE] Handling method commonFunc
[DIAGNOSE] Line 29, mock method trim used
[DIAGNOSE] Line 29, mock method sub used
[DIAGNOSE] Line 29, mock method startsWith used
[DIAGNOSE] Handling method getBox
[DIAGNOSE] Line 36, mock method secretBox used
[DIAGNOSE] Handling method callerOne
[DIAGNOSE] Line 43, mock method callFromDifferentMethod used
[DIAGNOSE] Handling method callerTwo
[DIAGNOSE] Line 47, mock method callFromDifferentMethod used
[DIAGNOSE] Handling method innerFunc
[DIAGNOSE] Handling method callFromDifferentMethod
```
The log shows all the mocked invocation and corresponding code line numbers in the class under test.

View File

@@ -0,0 +1,42 @@
Testable Maven Plugin
---
For projects built with Maven, in addition to directly modifying the argument of the `maven-surefire-plugin` plugin, the same effect can also be obtained through the `testable-maven-plugin` plugin:
```xml
<plugin>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-maven-plugin</artifactId>
<version>${testable.version}</version>
<executions>
<execution>
<id>prepare</id>
<goals>
<goal>prepare</goal>
</goals>
</execution>
</executions>
</plugin>
```
> When using `testable-maven-plugin`, the `TestableMock` related configuration on `maven-surefire-plugin` should be removed.
The `testable-maven-plugin` can be used with the Jacoco plugin without additional adaptation, so it can make the writing of the `pom.xml` file easier and more readable.
However, it should be noted that when running a single test case through the IDE, those mock function may fail to work.
This is because the IDE usually only runs the `maven-surefire-plugin` plugin when running a single test case, skipping the execution of the `testable-maven-plugin` plugin, resulting in the JavaAgent required for the mock function not being injected into the context.
This problem can be bypassed by configuring the test parameters of the IDE additionally. Take IntelliJ as an example, open the "Edit Configuration..." option of the run menu, as shown in the position ①
![modify-run-configuration](https://testable-code.oss-cn-beijing.aliyuncs.com/modify-run-configuration.png)
Add JavaAgent startup parameters at the end of the "virtual machine parameters" attribute value: `-javaagent:${HOME}/.m2/repository/com/alibaba/testable/testable-agent/xyz/testable-agent-xyzjar`, as shown in the figure position ②
> PS: Please replace `x.y.z` in the path with the actual version number
![add-testable-javaagent](https://testable-code.oss-cn-beijing.aliyuncs.com/add-testable-javaagent.png)
Finally, click to run the unit test, as shown in the position ③
In general, the additional complexity of using `testable-maven-plugin` is still higher than its simplified configuration. At present, modify the configuration of the `maven-surefire-plugin` directly in the `pom.xml` file is still the preferred solution.