Compare commits

..

8 Commits

Author SHA1 Message Date
金戟
02ba7ad83c follow up to 0.4.11 2021-02-06 21:11:48 +08:00
金戟
7bbdf82a41 support specify package prefix of class to be mocked 2021-02-06 11:31:51 +08:00
金戟
126c7257d8 fix out of index exception when mocking native method 2021-02-06 10:44:51 +08:00
金戟
adfba4bac0 support verbose log for self diagnosing 2021-02-05 22:30:04 +08:00
金戟
2c18ea12ed use space instead of tab 2021-02-04 17:59:26 +08:00
金戟
d4bce13918 remove note about 0.4.5 version 2021-02-03 18:03:07 +08:00
金戟
b23b8b98b3 add self troubleshooting information 2021-02-02 14:24:12 +08:00
金戟
064926e952 use junit 4 in java demo 2021-01-12 19:46:23 +08:00
22 changed files with 294 additions and 225 deletions

View File

@@ -12,7 +12,7 @@ repositories {
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
testImplementation('junit:junit:4.13.1')
testImplementation('com.alibaba.testable:testable-all:0.4.11')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.11')
}
@@ -23,5 +23,5 @@ tasks.withType(JavaCompile) {
test {
jvmArgs "-javaagent:${classpath.find { it.name.contains("testable-agent") }.absolutePath}"
useJUnitPlatform()
useJUnit()
}

View File

@@ -11,7 +11,7 @@
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<junit.version>4.13.1</junit.version>
<testable.version>0.4.11</testable.version>
</properties>
@@ -23,8 +23,8 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
@@ -33,14 +33,14 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
<configuration>
<argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>
</configuration>
</plugin>
<!-- <plugin>-->
<!-- <groupId>com.alibaba.testable</groupId>-->
<!-- <artifactId>testable-maven-plugin</artifactId>-->

View File

@@ -4,16 +4,16 @@ import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.demo.model.BlackBox;
import com.alibaba.testable.demo.model.Box;
import com.alibaba.testable.demo.model.Color;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertEquals;
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
class DemoInheritTest {
public class DemoInheritTest {
private DemoInherit demoInherit = new DemoInherit();
@@ -49,42 +49,42 @@ class DemoInheritTest {
@Test
void should_able_to_mock_call_sub_object_method_by_parent_object() {
public void should_able_to_mock_call_sub_object_method_by_parent_object() {
BlackBox box = (BlackBox)demoInherit.putIntoBox();
verify("put_into_box").withTimes(1);
assertEquals("put_data_into_box", box.get());
}
@Test
void should_able_to_mock_call_sub_object_method_by_sub_object() {
public void should_able_to_mock_call_sub_object_method_by_sub_object() {
BlackBox box = demoInherit.putIntoBlackBox();
verify("put_into_blackbox").withTimes(1);
assertEquals("put_data_into_blackbox", box.get());
}
@Test
void should_able_to_mock_call_parent_object_method_by_parent_object() {
public void should_able_to_mock_call_parent_object_method_by_parent_object() {
String content = demoInherit.getFromBox();
verify("get_from_box").withTimes(1);
assertEquals("get_from_box", content);
}
@Test
void should_able_to_mock_call_parent_object_method_by_sub_object() {
public void should_able_to_mock_call_parent_object_method_by_sub_object() {
String content = demoInherit.getFromBlackBox();
verify("get_from_blackbox").withTimes(1);
assertEquals("get_from_blackbox", content);
}
@Test
void should_able_to_mock_call_interface_method_by_interface_object() {
public void should_able_to_mock_call_interface_method_by_interface_object() {
String color = demoInherit.getColorViaColor();
verify("get_color_from_color").withTimes(1);
assertEquals("color_from_color", color);
}
@Test
void should_able_to_mock_call_interface_method_by_sub_class_object() {
public void should_able_to_mock_call_interface_method_by_sub_class_object() {
String color = demoInherit.getColorViaBox();
verify("get_color_from_blackbox").withTimes(1);
assertEquals("color_from_blackbox", color);

View File

@@ -3,17 +3,17 @@ package com.alibaba.testable.demo;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.error.VerifyFailedError;
import com.alibaba.testable.demo.model.BlackBox;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import static com.alibaba.testable.core.matcher.InvokeMatcher.*;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.Assert.fail;
/**
* 演示Mock方法调用校验器
* Demonstrate mock method invocation verifier
*/
class DemoMatcherTest {
public class DemoMatcherTest {
private DemoMatcher demoMatcher = new DemoMatcher();
@@ -28,7 +28,7 @@ class DemoMatcherTest {
@Test
void should_match_no_argument() {
public void should_match_no_argument() {
demoMatcher.callMethodWithoutArgument();
verify("methodWithoutArgument").withTimes(1);
demoMatcher.callMethodWithoutArgument();
@@ -36,7 +36,7 @@ class DemoMatcherTest {
}
@Test
void should_match_number_arguments() {
public void should_match_number_arguments() {
demoMatcher.callMethodWithNumberArguments();
verify("methodWithArguments").without(anyString(), 2);
verify("methodWithArguments").withInOrder(anyInt(), 2);
@@ -49,7 +49,7 @@ class DemoMatcherTest {
}
@Test
void should_match_string_arguments() {
public void should_match_string_arguments() {
demoMatcher.callMethodWithStringArgument();
verify("methodWithArguments").with(startsWith("he"), endsWith("ld"));
verify("methodWithArguments").with(contains("stab"), matches("m.[cd]k"));
@@ -57,7 +57,7 @@ class DemoMatcherTest {
}
@Test
void should_match_object_arguments() {
public void should_match_object_arguments() {
demoMatcher.callMethodWithObjectArgument();
verify("methodWithArguments").withInOrder(any(BlackBox.class), any(BlackBox.class));
verify("methodWithArguments").withInOrder(nullable(BlackBox.class), nullable(BlackBox.class));
@@ -65,7 +65,7 @@ class DemoMatcherTest {
}
@Test
void should_match_with_times() {
public void should_match_with_times() {
demoMatcher.callMethodWithNumberArguments();
verify("methodWithArguments").with(anyNumber(), any()).times(3);

View File

@@ -3,20 +3,20 @@ package com.alibaba.testable.demo;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.demo.model.BlackBox;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.util.concurrent.Executors;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static com.alibaba.testable.core.tool.TestableTool.MOCK_CONTEXT;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static org.junit.Assert.assertEquals;
/**
* 演示基本的Mock功能
* Demonstrate basic mock functionality
*/
class DemoMockTest {
public class DemoMockTest {
private DemoMock demoMock = new DemoMock();
@@ -68,20 +68,20 @@ class DemoMockTest {
@Test
void should_able_to_mock_new_object() {
public void should_able_to_mock_new_object() {
assertEquals("mock_something", demoMock.newFunc());
verify("createBlackBox").with("something");
}
@Test
void should_able_to_mock_member_method() throws Exception {
public void should_able_to_mock_member_method() throws Exception {
assertEquals("{ \"res\": \"mock_hello_MOCK_TAIL\"}", demoMock.outerFunc("hello"));
verify("innerFunc").with("hello");
verify("staticFunc").with();
}
@Test
void should_able_to_mock_common_method() {
public void should_able_to_mock_common_method() {
assertEquals("trim_string__sub_string__false", demoMock.commonFunc());
verify("trim").withTimes(1);
verify("sub").withTimes(1);
@@ -89,13 +89,13 @@ class DemoMockTest {
}
@Test
void should_able_to_mock_static_method() {
public void should_able_to_mock_static_method() {
assertEquals("not_secret_box", demoMock.getBox().get());
verify("secretBox").withTimes(1);
}
@Test
void should_able_to_get_source_method_name() throws Exception {
public void should_able_to_get_source_method_name() throws Exception {
// synchronous
assertEquals("mock_one_mock_others", demoMock.callerOne() + "_" + demoMock.callerTwo());
// asynchronous
@@ -105,7 +105,7 @@ class DemoMockTest {
}
@Test
void should_able_to_get_test_case_name() throws Exception {
public void should_able_to_get_test_case_name() throws Exception {
MOCK_CONTEXT.put("case", "special_case");
// synchronous
assertEquals("mock_special", demoMock.callerOne());

View File

@@ -2,25 +2,25 @@ package com.alibaba.testable.demo;
import com.alibaba.testable.core.accessor.PrivateAccessor;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
* 演示私有成员访问功能
* Demonstrate private member access functionality
*/
@EnablePrivateAccess
class DemoPrivateAccessTest {
public class DemoPrivateAccessTest {
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
@Test
void should_able_to_access_private_method() {
public void should_able_to_access_private_method() {
List<String> list = new ArrayList<String>() {{ add("a"); add("b"); add("c"); }};
assertEquals("member", demoPrivateAccess.privateFunc());
assertEquals("member", PrivateAccessor.invoke(demoPrivateAccess, "privateFunc"));
@@ -29,7 +29,7 @@ class DemoPrivateAccessTest {
}
@Test
void should_able_to_access_private_field() {
public void should_able_to_access_private_field() {
demoPrivateAccess.count = 2;
assertEquals(Integer.valueOf(2), demoPrivateAccess.count);
@@ -38,7 +38,7 @@ class DemoPrivateAccessTest {
}
@Test
void should_able_to_access_private_static_method() {
public void should_able_to_access_private_static_method() {
assertEquals("static", DemoPrivateAccess.privateStaticFunc());
assertEquals("static", PrivateAccessor.invokeStatic(DemoPrivateAccess.class, "privateStaticFunc"));
assertEquals("hello + 1", DemoPrivateAccess.privateStaticFuncWithArgs("hello", 1));
@@ -46,7 +46,7 @@ class DemoPrivateAccessTest {
}
@Test
void should_able_to_access_private_static_field() {
public void should_able_to_access_private_static_field() {
DemoPrivateAccess.staticCount = 2;
assertEquals(Integer.valueOf(2), DemoPrivateAccess.staticCount);
@@ -55,7 +55,7 @@ class DemoPrivateAccessTest {
}
@Test
void should_able_to_update_final_field() {
public void should_able_to_update_final_field() {
demoPrivateAccess.pi = 4.13;
assertEquals(Double.valueOf(4.13), demoPrivateAccess.pi);
@@ -64,7 +64,7 @@ class DemoPrivateAccessTest {
}
@Test
void should_able_to_use_null_parameter() {
public void should_able_to_use_null_parameter() {
demoPrivateAccess.pi = null;
assertNull(demoPrivateAccess.pi);
assertEquals("null + 1", DemoPrivateAccess.privateStaticFuncWithArgs(null, 1));

View File

@@ -2,17 +2,17 @@ package com.alibaba.testable.demo;
import com.alibaba.testable.core.annotation.MockConstructor;
import com.alibaba.testable.core.annotation.MockMethod;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertEquals;
/**
* 演示模板方法的Mock场景
* Demonstrate scenario of mocking template method
*/
class DemoTemplateTest {
public class DemoTemplateTest {
private DemoTemplate demoTemplate = new DemoTemplate();
@@ -70,19 +70,19 @@ class DemoTemplateTest {
@Test
void should_able_to_mock_single_template_method() {
public void should_able_to_mock_single_template_method() {
String res = demoTemplate.singleTemplateMethod();
assertEquals("demo_mock_list", res);
}
@Test
void should_able_to_mock_double_template_method() {
public void should_able_to_mock_double_template_method() {
String res = demoTemplate.doubleTemplateMethod();
assertEquals("testable_mock_map", res);
}
@Test
void should_able_to_mock_new_template_method() {
public void should_able_to_mock_new_template_method() {
Set<?> res = demoTemplate.newTemplateMethod();
assertEquals(2, res.size());
Iterator<?> iterator = res.stream().iterator();

View File

@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.4.10"
kotlin("jvm") version "1.4.10"
}
group = "com.alibaba.testable"
@@ -9,25 +9,25 @@ version = "1.0.0-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
mavenCentral()
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
testImplementation("com.alibaba.testable:testable-all:0.4.11")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.11")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
testImplementation("com.alibaba.testable:testable-all:0.4.11")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.11")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
tasks.withType<Test> {
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
useJUnitPlatform()
jvmArgs("-javaagent:${classpath.find { it.name.contains("testable-agent") }!!.absolutePath}")
useJUnitPlatform()
}

View File

@@ -1,119 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>kotlin-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>kotlin-demo</name>
<description>Demo project for TestableMock</description>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>kotlin-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>kotlin-demo</name>
<description>Demo project for TestableMock</description>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.3.72</kotlin.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.11</testable.version>
</properties>
<properties>
<java.version>1.8</java.version>
<kotlin.version>1.3.72</kotlin.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.11</testable.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-all</artifactId>
<version>${testable.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<!-- <configuration>-->
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
<!-- </configuration>-->
</plugin>
<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>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>target/jacoco.exec</dataFile>
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<!-- <configuration>-->
<!-- <argLine>@{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/testable-agent/${testable.version}/testable-agent-${testable.version}.jar</argLine>-->
<!-- </configuration>-->
</plugin>
<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>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>target/jacoco.exec</dataFile>
<outputDirectory>target/jacoco-ut</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -38,3 +38,9 @@ The output log example is as follows:
```
The log shows all the mocked invocation and corresponding code line numbers in the class under test.
- Self troubleshooting:
- If there is no output, please check whether the `pom.xml` or `build.gradle` configuration correctly introduces `TestableMock` dependencies
- If only the first line of `Handling test class` is output, please check whether the test class is in the same package of the class under test, and the name is "<ClassUnderTest>+Test" (required for `0.4.x` version)
- If `Handling source class` and `Handling method xxx` are output, but there is no mock replacement happen at the expected code line, please check whether the mock method definition matches the target method

View File

@@ -5,10 +5,6 @@ Use TestableMock In 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.

View File

@@ -38,3 +38,9 @@ class DemoTest {
```
该日志展示了被测类中所有发生了Mock替换的调用和相应代码行号。
简单排查方法:
- 若没有任何输出,请检查`pom.xml``build.gradle`配置是否正确引入了TestableMock依赖
- 若只输出了第一行`Handling test class`,请检查被测类与测试类是否包路径相同,且名称为"被测类+Test"`0.4.x`版本要求)
- 若输出了`Handling source class`以及`Handling method xxx`但预期的代码行位置没有发生Mock替换请检查Mock方法定义是否未与目标方法匹配

View File

@@ -5,10 +5,6 @@
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`工具类访问被测类私有成员的功能不会受编译器差异影响。

View File

@@ -14,6 +14,7 @@ public class PreMain {
private static final String AND = "&";
private static final String LOG_LEVEL = "logLevel";
private static final String DUMP_PATH = "dumpPath";
private static final String PKG_PREFIX = "pkgPrefix";
private static final String EQUAL = "=";
public static void premain(String agentArgs, Instrumentation inst) {
@@ -34,6 +35,8 @@ public class PreMain {
GlobalConfig.setLogLevel(v);
} else if (k.equals(DUMP_PATH)) {
GlobalConfig.setDumpPath(v);
} else if (k.equals(PKG_PREFIX)) {
GlobalConfig.setPkgPrefix(v);
}
} else {
GlobalConfig.setLogLevel(a);

View File

@@ -60,29 +60,21 @@ public class SourceClassHandler extends BaseClassHandler {
Set<MethodInfo> newOperatorInjectMethods) {
LogUtil.diagnose(" Handling method %s", mn.name);
AbstractInsnNode[] instructions = mn.instructions.toArray();
if (instructions.length == 0) {
// native method (issue-52)
return;
}
int i = 0;
int maxStackDiff = 0;
do {
if (invokeOps.contains(instructions[i].getOpcode())) {
MethodInsnNode node = (MethodInsnNode)instructions[i];
MethodInfo mockMethod = getMemberInjectMethodName(memberInjectMethods, node);
if (mockMethod != null) {
// it's a member or static method and an inject method for it exist
int rangeStart = getMemberMethodStart(instructions, i);
if (rangeStart >= 0) {
ModifiedInsnNodes modifiedInsnNodes = replaceMemberCallOps(cn, mn, mockMethod,
instructions, node.owner, node.getOpcode(), rangeStart, i);
instructions = modifiedInsnNodes.nodes;
maxStackDiff = Math.max(maxStackDiff, modifiedInsnNodes.stackDiff);
i = rangeStart;
} else {
LogUtil.warn("Potential missed mocking at %s:%s", mn.name, getLineNum(instructions, i));
}
} else if (ConstPool.CONSTRUCTOR.equals(node.name)) {
// it's a new operation
if (ConstPool.CONSTRUCTOR.equals(node.name)) {
LogUtil.verbose(" Line %d, constructing \"%s\" as \"%s\"", getLineNum(instructions, i),
node.owner, node.desc);
String newOperatorInjectMethodName = getNewOperatorInjectMethodName(newOperatorInjectMethods, node);
if (newOperatorInjectMethodName != null) {
// and an inject method for it exist
// it's a new operation and an inject method for it exist
int rangeStart = getConstructorStart(instructions, node.owner, i);
if (rangeStart >= 0) {
ModifiedInsnNodes modifiedInsnNodes = replaceNewOps(cn, mn, newOperatorInjectMethodName,
@@ -92,6 +84,23 @@ public class SourceClassHandler extends BaseClassHandler {
i = rangeStart;
}
}
} else {
LogUtil.verbose(" Line %d, invoking \"%s\" as \"%s\"", getLineNum(instructions, i),
node.name, node.desc);
MethodInfo mockMethod = getMemberInjectMethodName(memberInjectMethods, node);
if (mockMethod != null) {
// it's a member or static method and an inject method for it exist
int rangeStart = getMemberMethodStart(instructions, i);
if (rangeStart >= 0) {
ModifiedInsnNodes modifiedInsnNodes = replaceMemberCallOps(cn, mn, mockMethod,
instructions, node.owner, node.getOpcode(), rangeStart, i);
instructions = modifiedInsnNodes.nodes;
maxStackDiff = Math.max(maxStackDiff, modifiedInsnNodes.stackDiff);
i = rangeStart;
} else {
LogUtil.warn("Potential missed mocking at %s:%s", mn.name, getLineNum(instructions, i));
}
}
}
}
i++;
@@ -194,7 +203,7 @@ public class SourceClassHandler extends BaseClassHandler {
private ModifiedInsnNodes replaceNewOps(ClassNode cn, MethodNode mn, String newOperatorInjectMethodName,
AbstractInsnNode[] instructions, int start, int end) {
LogUtil.diagnose(" Line %d, mock method %s used", getLineNum(instructions, start),
LogUtil.diagnose(" Line %d, mock method \"%s\" used", getLineNum(instructions, start),
newOperatorInjectMethodName);
String classType = ((TypeInsnNode)instructions[start]).desc;
String constructorDesc = ((MethodInsnNode)instructions[end]).desc;
@@ -224,7 +233,7 @@ public class SourceClassHandler extends BaseClassHandler {
private ModifiedInsnNodes replaceMemberCallOps(ClassNode cn, MethodNode mn, MethodInfo mockMethod,
AbstractInsnNode[] instructions, String ownerClass,
int opcode, int start, int end) {
LogUtil.diagnose(" Line %d, mock method %s used", getLineNum(instructions, start),
LogUtil.diagnose(" Line %d, mock method \"%s\" used", getLineNum(instructions, start),
mockMethod.getMockName());
boolean shouldAppendTypeParameter = !mockMethod.getDesc().equals(mockMethod.getMockDesc());
String testClassName = ClassUtil.getTestClassName(cn.name);

View File

@@ -36,6 +36,7 @@ import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassNa
public class TestableClassTransformer implements ClassFileTransformer {
private static final String FIELD_DIAGNOSE = "diagnose";
private static final String COMMA = ",";
/**
* Just avoid spend time to scan those surely non-user classes
@@ -57,21 +58,21 @@ public class TestableClassTransformer implements ClassFileTransformer {
try {
if (shouldTransformAsSourceClass(className)) {
// it's a source class with testable enabled
LogUtil.diagnose("Handling source class %s", className);
List<MethodInfo> injectMethods = getTestableMockMethods(ClassUtil.getTestClassName(className));
LogUtil.diagnose("Handling source class %s", className);
bytes = new SourceClassHandler(injectMethods).getBytes(classFileBuffer);
dumpByte(className, bytes);
resetMockContext();
} else if (shouldTransformAsTestClass(className)) {
// it's a test class with testable enabled
LogUtil.diagnose("Handling test class %s", className);
bytes = new TestClassHandler().getBytes(classFileBuffer);
dumpByte(className, bytes);
resetMockContext();
}
} catch (Throwable t) {
LogUtil.warn("Failed to transform class " + className);
LogUtil.diagnose(t.toString());
} finally {
LogUtil.resetLogLevel();
}
return bytes;
}
@@ -105,14 +106,25 @@ public class TestableClassTransformer implements ClassFileTransformer {
if (null == className) {
return true;
}
for (String prefix : WHITELIST_PREFIXES) {
if (className.startsWith(prefix)) {
return false;
String whitePrefix = GlobalConfig.getPkgPrefix();
if (whitePrefix != null) {
for (String prefix : whitePrefix.split(COMMA)) {
if (className.startsWith(prefix)) {
// Only consider package in provided list as non-system class
return false;
}
}
}
for (String prefix : BLACKLIST_PREFIXES) {
if (className.startsWith(prefix)) {
return true;
return true;
} else {
for (String prefix : WHITELIST_PREFIXES) {
if (className.startsWith(prefix)) {
return false;
}
}
for (String prefix : BLACKLIST_PREFIXES) {
if (className.startsWith(prefix)) {
return true;
}
}
}
return false;
@@ -140,9 +152,12 @@ public class TestableClassTransformer implements ClassFileTransformer {
for (AnnotationNode an : mn.visibleAnnotations) {
String fullClassName = toDotSeparateFullClassName(an.desc);
if (fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
LogUtil.verbose(" Mock constructor \"%s\" as \"(%s)V\" for \"%s\"", mn.name,
ClassUtil.extractParameters(mn.desc), ClassUtil.getReturnType(mn.desc));
addMockConstructor(methodInfos, cn, mn);
} else if (fullClassName.equals(ConstPool.MOCK_METHOD) ||
fullClassName.equals(ConstPool.TESTABLE_MOCK)) {
LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, mn.desc);
String targetMethod = AnnotationUtil.getAnnotationParameter(
an, ConstPool.FIELD_TARGET_METHOD, mn.name, String.class);
if (ConstPool.CONSTRUCTOR.equals(targetMethod)) {
@@ -218,14 +233,11 @@ public class TestableClassTransformer implements ClassFileTransformer {
private void setupMockContext(AnnotationNode an) {
MockDiagnose diagnose = AnnotationUtil.getAnnotationParameter(an, FIELD_DIAGNOSE, null, MockDiagnose.class);
if (diagnose != null) {
LogUtil.enableDiagnose(diagnose == MockDiagnose.ENABLE);
LogUtil.setLevel(diagnose == MockDiagnose.ENABLE ? LogUtil.LogLevel.LEVEL_DIAGNOSE :
(diagnose == MockDiagnose.VERBOSE ? LogUtil.LogLevel.LEVEL_VERBOSE : LogUtil.LogLevel.LEVEL_MUTE));
}
}
private void resetMockContext() {
LogUtil.resetLogLevel();
}
/**
* Split desc to "first parameter" and "desc of rest parameters"
* @param desc method desc

View File

@@ -140,6 +140,16 @@ public class ClassUtil {
return parameterTypes;
}
/**
* extract parameter part of method desc
* @param desc method description
* @return parameter value
*/
public static String extractParameters(String desc) {
int returnTypeEdge = desc.lastIndexOf(PARAM_END);
return desc.substring(1, returnTypeEdge);
}
/**
* parse method desc, fetch return value types
* @param desc method description

View File

@@ -12,6 +12,7 @@ public class GlobalConfig {
private static final String VERBOSE = "verbose";
private static String dumpPath = null;
private static String pkgPrefix = null;
public static boolean setLogLevel(String level) {
if (level.equals(MUTE)) {
@@ -35,4 +36,11 @@ public class GlobalConfig {
return dumpPath;
}
public static String getPkgPrefix() {
return pkgPrefix;
}
public static void setPkgPrefix(String pkgPrefix) {
GlobalConfig.pkgPrefix = pkgPrefix;
}
}

View File

@@ -15,6 +15,12 @@ class ClassUtilTest {
assertEquals(3, ClassUtil.getParameterTypes("(Ljava/lang/String;[I[Ljava/lang/String;)V").size());
}
@Test
void should_able_to_extract_parameter() {
assertEquals("", ClassUtil.extractParameters("()I"));
assertEquals("Ljava/lang/String;", ClassUtil.extractParameters("(Ljava/lang/String;)I"));
}
@Test
void should_able_to_get_return_type() {
assertEquals("", ClassUtil.getReturnType("(Ljava/lang/String;)V"));

View File

@@ -14,6 +14,11 @@ public enum MockDiagnose {
/**
* Print diagnose logs
*/
ENABLE
ENABLE,
/**
* Print verbose log
*/
VERBOSE
}

View File

@@ -54,8 +54,8 @@ public class LogUtil {
System.err.println(String.format("[ERROR] " + msg, args));
}
public static void enableDiagnose(boolean enable) {
currentLogLevel = enable ? LogLevel.LEVEL_DIAGNOSE : LogLevel.LEVEL_MUTE;
public static void setLevel(LogLevel level) {
currentLogLevel = level;
}
public static void setDefaultLevel(LogLevel level) {

View File

@@ -1,5 +1,6 @@
package com.alibaba.testable;
import com.google.common.base.Strings;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
@@ -45,6 +46,12 @@ public class TestableMojo extends AbstractMojo
@Parameter
private String dumpPath;
/**
* Package prefixes of class need to be mocked (comma split)
*/
@Parameter
private String pkgPrefix;
/**
* Name of the Testable Agent artifact.
*/
@@ -71,13 +78,17 @@ public class TestableMojo extends AbstractMojo
getLog().error("failed to fetch project properties");
return;
}
String extraArgs = "";
if (logLevel != null && !logLevel.isEmpty()) {
if (!Strings.isNullOrEmpty(logLevel)) {
extraArgs += "&logLevel=" + logLevel;
}
if (dumpPath != null && !dumpPath.isEmpty()) {
if (!Strings.isNullOrEmpty(dumpPath)) {
extraArgs += "&dumpPath=" + dumpPath;
}
if (!Strings.isNullOrEmpty(pkgPrefix)) {
extraArgs += "&pkgPrefix=" + pkgPrefix;
}
final String oldArgs = projectProperties.getProperty(testArgsPropertyKey);
String newArgs = (oldArgs == null) ? getAgentJarArgs().trim() : (oldArgs + getAgentJarArgs());
if (!extraArgs.isEmpty()) {
@@ -91,6 +102,7 @@ public class TestableMojo extends AbstractMojo
final Artifact testableAgentArtifact = pluginArtifactMap.get(AGENT_ARTIFACT_NAME);
if (testableAgentArtifact == null) {
getLog().error("failed to find testable agent jar");
return "";
}
return " -javaagent:" + testableAgentArtifact.getFile().getAbsolutePath();
}