Compare commits

..

2 Commits

Author SHA1 Message Date
金戟
d57dedb6fc release v0.4.12 2021-02-07 10:41:07 +08:00
金戟
d860684e91 add switch to disable private access target existence check 2021-02-06 21:56:36 +08:00
23 changed files with 117 additions and 78 deletions

View File

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

View File

@@ -11,8 +11,8 @@
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.13.1</junit.version>
<testable.version>0.4.11</testable.version>
<junit.version>5.6.2</junit.version>
<testable.version>0.4.12</testable.version>
</properties>
<dependencies>
@@ -23,8 +23,8 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>

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.Test;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示父类变量引用子类对象时的Mock场景
* Demonstrate scenario of mocking method from sub-type object referred by parent-type variable
*/
public class DemoInheritTest {
class DemoInheritTest {
private DemoInherit demoInherit = new DemoInherit();
@@ -49,42 +49,42 @@ public class DemoInheritTest {
@Test
public void should_able_to_mock_call_sub_object_method_by_parent_object() {
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
public void should_able_to_mock_call_sub_object_method_by_sub_object() {
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
public void should_able_to_mock_call_parent_object_method_by_parent_object() {
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
public void should_able_to_mock_call_parent_object_method_by_sub_object() {
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
public void should_able_to_mock_call_interface_method_by_interface_object() {
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
public void should_able_to_mock_call_interface_method_by_sub_class_object() {
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.Test;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeMatcher.*;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.fail;
/**
* 演示Mock方法调用校验器
* Demonstrate mock method invocation verifier
*/
public class DemoMatcherTest {
class DemoMatcherTest {
private DemoMatcher demoMatcher = new DemoMatcher();
@@ -28,7 +28,7 @@ public class DemoMatcherTest {
@Test
public void should_match_no_argument() {
void should_match_no_argument() {
demoMatcher.callMethodWithoutArgument();
verify("methodWithoutArgument").withTimes(1);
demoMatcher.callMethodWithoutArgument();
@@ -36,7 +36,7 @@ public class DemoMatcherTest {
}
@Test
public void should_match_number_arguments() {
void should_match_number_arguments() {
demoMatcher.callMethodWithNumberArguments();
verify("methodWithArguments").without(anyString(), 2);
verify("methodWithArguments").withInOrder(anyInt(), 2);
@@ -49,7 +49,7 @@ public class DemoMatcherTest {
}
@Test
public void should_match_string_arguments() {
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 @@ public class DemoMatcherTest {
}
@Test
public void should_match_object_arguments() {
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 @@ public class DemoMatcherTest {
}
@Test
public void should_match_with_times() {
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.Test;
import org.junit.jupiter.api.Test;
import java.util.concurrent.Executors;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
import static com.alibaba.testable.core.tool.TestableTool.MOCK_CONTEXT;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示基本的Mock功能
* Demonstrate basic mock functionality
*/
public class DemoMockTest {
class DemoMockTest {
private DemoMock demoMock = new DemoMock();
@@ -68,20 +68,20 @@ public class DemoMockTest {
@Test
public void should_able_to_mock_new_object() {
void should_able_to_mock_new_object() {
assertEquals("mock_something", demoMock.newFunc());
verify("createBlackBox").with("something");
}
@Test
public void should_able_to_mock_member_method() throws Exception {
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
public void should_able_to_mock_common_method() {
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 @@ public class DemoMockTest {
}
@Test
public void should_able_to_mock_static_method() {
void should_able_to_mock_static_method() {
assertEquals("not_secret_box", demoMock.getBox().get());
verify("secretBox").withTimes(1);
}
@Test
public void should_able_to_get_source_method_name() throws Exception {
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 @@ public class DemoMockTest {
}
@Test
public void should_able_to_get_test_case_name() throws Exception {
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.Test;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
/**
* 演示私有成员访问功能
* Demonstrate private member access functionality
*/
@EnablePrivateAccess
public class DemoPrivateAccessTest {
class DemoPrivateAccessTest {
private DemoPrivateAccess demoPrivateAccess = new DemoPrivateAccess();
@Test
public void should_able_to_access_private_method() {
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 @@ public class DemoPrivateAccessTest {
}
@Test
public void should_able_to_access_private_field() {
void should_able_to_access_private_field() {
demoPrivateAccess.count = 2;
assertEquals(Integer.valueOf(2), demoPrivateAccess.count);
@@ -38,7 +38,7 @@ public class DemoPrivateAccessTest {
}
@Test
public void should_able_to_access_private_static_method() {
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 @@ public class DemoPrivateAccessTest {
}
@Test
public void should_able_to_access_private_static_field() {
void should_able_to_access_private_static_field() {
DemoPrivateAccess.staticCount = 2;
assertEquals(Integer.valueOf(2), DemoPrivateAccess.staticCount);
@@ -55,7 +55,7 @@ public class DemoPrivateAccessTest {
}
@Test
public void should_able_to_update_final_field() {
void should_able_to_update_final_field() {
demoPrivateAccess.pi = 4.13;
assertEquals(Double.valueOf(4.13), demoPrivateAccess.pi);
@@ -64,7 +64,7 @@ public class DemoPrivateAccessTest {
}
@Test
public void should_able_to_use_null_parameter() {
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.Test;
import org.junit.jupiter.api.Test;
import java.util.*;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 演示模板方法的Mock场景
* Demonstrate scenario of mocking template method
*/
public class DemoTemplateTest {
class DemoTemplateTest {
private DemoTemplate demoTemplate = new DemoTemplate();
@@ -70,19 +70,19 @@ public class DemoTemplateTest {
@Test
public void should_able_to_mock_single_template_method() {
void should_able_to_mock_single_template_method() {
String res = demoTemplate.singleTemplateMethod();
assertEquals("demo_mock_list", res);
}
@Test
public void should_able_to_mock_double_template_method() {
void should_able_to_mock_double_template_method() {
String res = demoTemplate.doubleTemplateMethod();
assertEquals("testable_mock_map", res);
}
@Test
public void should_able_to_mock_new_template_method() {
void should_able_to_mock_new_template_method() {
Set<?> res = demoTemplate.newTemplateMethod();
assertEquals(2, res.size());
Iterator<?> iterator = res.stream().iterator();

View File

@@ -16,8 +16,8 @@ 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")
testImplementation("com.alibaba.testable:testable-all:0.4.12")
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.12")
}
tasks.withType<KotlinCompile> {

View File

@@ -14,7 +14,7 @@
<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>
<testable.version>0.4.12</testable.version>
</properties>
<dependencies>

View File

@@ -1,5 +1,11 @@
# Release Note
## 0.4.12
- support verbose diagnose log for better self-troubleshooting
- support disable private access target existence check
- support specify mock scanning packages
- fix an ArrayIndexOutOfBoundsException issue when transforming native method
## 0.4.11
- support accessing private members of class under test in different package path
- validate the number of private method parameters accessed by `PrivateAccessor`

View File

@@ -16,7 +16,7 @@ It is recommended to add a `property` field that identifies the TestableMock ver
```xml
<properties>
<testable.version>0.4.11</testable.version>
<testable.version>0.4.12</testable.version>
</properties>
```
@@ -63,8 +63,8 @@ Add dependence of `TestableMock` in `build.gradle` file:
```groovy
dependencies {
testImplementation('com.alibaba.testable:testable-all:0.4.11')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.11')
testImplementation('com.alibaba.testable:testable-all:0.4.12')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.12')
}
```

View File

@@ -1,5 +1,11 @@
# Release Note
## 0.4.12
- 支持`VERBOSE`级别的Mocking过程日志增强错误自助排查能力
- 支持使用`verifyTargetOnCompile`参数禁用编译期私有目标校验功能
- 支持通过agent参数指定Mock目标的扫描包范围
- 修复一处`ArrayIndexOutOfBoundsException`异常 (issue-52
## 0.4.11
- 支持测试类访问与自身包路径不同的被测类的私有成员
- 增加`PrivateAccessor`访问的私有方法参数数目检查,提高抗代码重构能力

View File

@@ -16,7 +16,7 @@
```xml
<properties>
<testable.version>0.4.11</testable.version>
<testable.version>0.4.12</testable.version>
</properties>
```
@@ -63,8 +63,8 @@
```groovy
dependencies {
testImplementation('com.alibaba.testable:testable-all:0.4.11')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.11')
testImplementation('com.alibaba.testable:testable-all:0.4.12')
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.12')
}
```

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-agent</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-all</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-core</artifactId>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-maven-plugin</artifactId>

View File

@@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<packaging>pom</packaging>
<name>testable-parent</name>
<description>Unit test enhancement toolkit</description>
@@ -42,7 +42,7 @@
<plugin.gpg.version>1.6</plugin.gpg.version>
<plugin.staging.version>1.6.8</plugin.staging.version>
<plugin.maven.version>3.6.0</plugin.maven.version>
<testable.version>0.4.11</testable.version>
<testable.version>0.4.12</testable.version>
</properties>
<profiles>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.testable</groupId>
<artifactId>testable-parent</artifactId>
<version>0.4.11</version>
<version>0.4.12</version>
<relativePath>../testable-parent</relativePath>
</parent>
<artifactId>testable-processor</artifactId>

View File

@@ -2,6 +2,7 @@ package com.alibaba.testable.processor;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
import com.alibaba.testable.processor.constant.ConstPool;
import com.alibaba.testable.processor.model.Parameters;
import com.alibaba.testable.processor.model.TestableContext;
import com.alibaba.testable.processor.translator.EnablePrivateAccessTranslator;
import com.alibaba.testable.processor.util.JavacUtil;
@@ -31,6 +32,7 @@ import java.util.Set;
public class EnablePrivateAccessProcessor extends AbstractProcessor {
private static final String SRC_CLASS = "srcClass";
private static final String VERIFY_ON_COMPILE = "verifyTargetOnCompile";
private TestableContext cx;
@@ -58,8 +60,8 @@ public class EnablePrivateAccessProcessor extends AbstractProcessor {
for (Element element : elements) {
if (element.getKind().isClass()) {
Symbol.ClassSymbol testClass = (Symbol.ClassSymbol)element;
String sourceClassName = getSourceClassName(testClass);
processClassElement(testClass, sourceClassName);
Parameters parameters = getAnnotationParameters(testClass);
processClassElement(testClass, parameters);
}
}
return true;
@@ -71,17 +73,20 @@ public class EnablePrivateAccessProcessor extends AbstractProcessor {
return SourceVersion.values()[SourceVersion.values().length - 1];
}
private String getSourceClassName(Symbol.ClassSymbol testClass) {
private Parameters getAnnotationParameters(Symbol.ClassSymbol testClass) {
Parameters parameters = new Parameters();
for (Attribute.Compound annotation : testClass.getMetadata().getDeclarationAttributes()) {
if (ConstPool.ENABLE_PRIVATE_ACCESS.equals(annotation.type.tsym.toString())) {
for (Pair<Symbol.MethodSymbol, Attribute> p : annotation.values) {
if (SRC_CLASS.equals(p.fst.name.toString())) {
return p.snd.getValue().toString();
parameters.sourceClassName = p.snd.getValue().toString();
} else if (VERIFY_ON_COMPILE.equals(p.fst.name.toString())) {
parameters.verifyTargetExistence = (Boolean)p.snd.getValue();
}
}
}
}
return null;
return parameters;
}
private JavacProcessingEnvironment getJavacProcessingEnvironment(ProcessingEnvironment processingEnv) {
@@ -92,10 +97,10 @@ public class EnablePrivateAccessProcessor extends AbstractProcessor {
}
}
private void processClassElement(Symbol.ClassSymbol testClass, String sourceClassName) {
private void processClassElement(Symbol.ClassSymbol testClass, Parameters parameters) {
if (cx.trees != null) {
JCTree tree = cx.trees.getTree(testClass);
tree.accept(new EnablePrivateAccessTranslator(cx, testClass, sourceClassName));
tree.accept(new EnablePrivateAccessTranslator(cx, testClass, parameters));
}
}

View File

@@ -19,4 +19,10 @@ public @interface EnablePrivateAccess {
*/
Class<?> srcClass() default NullType.class;
/**
* whether enable compile-time existence verification for the private members accessed
* @return
*/
boolean verifyTargetOnCompile() default true;
}

View File

@@ -0,0 +1,12 @@
package com.alibaba.testable.processor.model;
/**
* @author flin
*/
public class Parameters {
public String sourceClassName;
public Boolean verifyTargetExistence;
}

View File

@@ -3,6 +3,7 @@ package com.alibaba.testable.processor.translator;
import com.alibaba.testable.processor.generator.PrivateAccessStatementGenerator;
import com.alibaba.testable.processor.model.MemberRecord;
import com.alibaba.testable.processor.model.MemberType;
import com.alibaba.testable.processor.model.Parameters;
import com.alibaba.testable.processor.model.TestableContext;
import com.alibaba.testable.processor.util.PathUtil;
import com.sun.tools.javac.code.Symbol;
@@ -51,13 +52,13 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
private final PrivateAccessStatementGenerator privateAccessStatementGenerator;
private final PrivateAccessChecker privateAccessChecker;
public EnablePrivateAccessTranslator(TestableContext cx, Symbol.ClassSymbol clazz, String srcClassName) {
public EnablePrivateAccessTranslator(TestableContext cx, Symbol.ClassSymbol clazz, Parameters p) {
String sourceClassFullName;
if (srcClassName == null) {
if (p.sourceClassName == null) {
String testClassFullName = clazz.fullname.toString();
sourceClassFullName = testClassFullName.substring(0, testClassFullName.length() - TEST_POSTFIX.length());
} else {
sourceClassFullName = srcClassName;
sourceClassFullName = p.sourceClassName;
}
String sourceClassShortName = sourceClassFullName.substring(sourceClassFullName.lastIndexOf('.') + 1);
this.privateAccessStatementGenerator = new PrivateAccessStatementGenerator(cx);
@@ -72,7 +73,8 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
} catch (Exception e) {
e.printStackTrace();
}
this.privateAccessChecker = new PrivateAccessChecker(cx, sourceClassShortName, memberRecord);
this.privateAccessChecker = (p.verifyTargetExistence == null || p.verifyTargetExistence) ?
new PrivateAccessChecker(cx, sourceClassShortName, memberRecord) : null;
}
/**
@@ -157,7 +159,9 @@ public class EnablePrivateAccessTranslator extends BaseTranslator {
} else if (memberType.equals(MemberType.STATIC_PRIVATE)) {
expr = privateAccessStatementGenerator.fetchStaticInvokeStatement(invocation);
}
privateAccessChecker.validate((JCMethodInvocation)expr);
if (privateAccessChecker != null) {
privateAccessChecker.validate((JCMethodInvocation)expr);
}
}
// check the casted expression
if (expr instanceof JCTypeCast) {