mirror of
https://github.com/alibaba/testable-mock.git
synced 2025-01-25 11:51:15 +08:00
rename annotations to reflect actual use
This commit is contained in:
parent
a85a4daf9e
commit
ee21025dbb
@ -1,46 +1,45 @@
|
|||||||
package com.alibaba.testable.demo;
|
package com.alibaba.testable.demo;
|
||||||
|
|
||||||
import com.alibaba.testable.core.accessor.PrivateAccessor;
|
import com.alibaba.testable.core.accessor.PrivateAccessor;
|
||||||
import com.alibaba.testable.core.annotation.EnableTestable;
|
import com.alibaba.testable.core.annotation.EnablePrivateAccess;
|
||||||
import com.alibaba.testable.core.annotation.TestableInject;
|
import com.alibaba.testable.core.annotation.TestableMock;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
|
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
|
||||||
import static com.alibaba.testable.core.tool.TestableTool.TEST_CASE;
|
import static com.alibaba.testable.core.tool.TestableTool.TEST_CASE;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
@EnableTestable
|
@EnablePrivateAccess
|
||||||
class DemoServiceTest {
|
class DemoServiceTest {
|
||||||
|
|
||||||
@TestableInject
|
@TestableMock
|
||||||
private BlackBox createBlackBox(String text) {
|
private BlackBox createBlackBox(String text) {
|
||||||
return new BlackBox("mock_" + text);
|
return new BlackBox("mock_" + text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestableInject
|
@TestableMock
|
||||||
private String innerFunc(String text) {
|
private String innerFunc(String text) {
|
||||||
return "mock_" + text;
|
return "mock_" + text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestableInject(targetClass = String.class)
|
@TestableMock(targetClass = String.class)
|
||||||
private String trim(String self) {
|
private String trim(String self) {
|
||||||
return "trim_string";
|
return "trim_string";
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestableInject(targetClass = String.class, targetMethod = "substring")
|
@TestableMock(targetClass = String.class, targetMethod = "substring")
|
||||||
private String sub(String self, int i, int j) {
|
private String sub(String self, int i, int j) {
|
||||||
return "sub_string";
|
return "sub_string";
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestableInject(targetClass = String.class)
|
@TestableMock(targetClass = String.class)
|
||||||
private boolean startsWith(String self, String s) {
|
private boolean startsWith(String self, String s) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestableInject
|
@TestableMock
|
||||||
private String callFromDifferentMethod() {
|
private String callFromDifferentMethod() {
|
||||||
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
|
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
|
||||||
return "mock_special";
|
return "mock_special";
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package com.alibaba.testable.demo
|
package com.alibaba.testable.demo
|
||||||
|
|
||||||
import com.alibaba.testable.core.accessor.PrivateAccessor
|
import com.alibaba.testable.core.accessor.PrivateAccessor
|
||||||
import com.alibaba.testable.core.annotation.EnableTestable
|
import com.alibaba.testable.core.annotation.EnablePrivateAccess
|
||||||
import com.alibaba.testable.core.annotation.TestableInject
|
import com.alibaba.testable.core.annotation.TestableMock
|
||||||
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
|
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
|
||||||
import com.alibaba.testable.core.tool.TestableTool.TEST_CASE
|
import com.alibaba.testable.core.tool.TestableTool.TEST_CASE
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
@ -10,25 +10,25 @@ import org.junit.jupiter.api.Test
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
|
||||||
@EnableTestable
|
@EnablePrivateAccess
|
||||||
internal class DemoServiceTest {
|
internal class DemoServiceTest {
|
||||||
|
|
||||||
@TestableInject
|
@TestableMock
|
||||||
private fun createBlackBox(text: String) = BlackBox("mock_$text")
|
private fun createBlackBox(text: String) = BlackBox("mock_$text")
|
||||||
|
|
||||||
@TestableInject
|
@TestableMock
|
||||||
private fun innerFunc(text: String) = "mock_$text"
|
private fun innerFunc(text: String) = "mock_$text"
|
||||||
|
|
||||||
@TestableInject(targetClass = BlackBox::class)
|
@TestableMock(targetClass = BlackBox::class)
|
||||||
private fun trim(self: BlackBox) = "trim_string"
|
private fun trim(self: BlackBox) = "trim_string"
|
||||||
|
|
||||||
@TestableInject(targetClass = BlackBox::class, targetMethod = "substring")
|
@TestableMock(targetClass = BlackBox::class, targetMethod = "substring")
|
||||||
private fun sub(self: BlackBox, i: Int, j: Int) = "sub_string"
|
private fun sub(self: BlackBox, i: Int, j: Int) = "sub_string"
|
||||||
|
|
||||||
@TestableInject(targetClass = BlackBox::class)
|
@TestableMock(targetClass = BlackBox::class)
|
||||||
private fun startsWith(self: BlackBox, s: String) = false
|
private fun startsWith(self: BlackBox, s: String) = false
|
||||||
|
|
||||||
@TestableInject
|
@TestableMock
|
||||||
private fun callFromDifferentMethod(): String {
|
private fun callFromDifferentMethod(): String {
|
||||||
return if (TEST_CASE == "should_able_to_get_test_case_name") {
|
return if (TEST_CASE == "should_able_to_get_test_case_name") {
|
||||||
"mock_special"
|
"mock_special"
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
## v0.2.0
|
## v0.2.0
|
||||||
- use `TestableTool` class to expose test context
|
- use `TestableTool` class to expose test context
|
||||||
- add `testable-maven-plugin` module
|
- add `testable-maven-plugin` module
|
||||||
|
- remove dependence on EnableTestable annotation in `testable-agent`
|
||||||
|
- rename annotations to reflect the actual use
|
||||||
|
|
||||||
## v0.1.0
|
## v0.1.0
|
||||||
- move generated agent jar to class folder
|
- move generated agent jar to class folder
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
### 访问私有成员字段和方法
|
### 访问私有成员字段和方法
|
||||||
|
|
||||||
在测试类上添加`@EnableTestable`注解,然后即可直接在单元测试里像访问公有成员一样调用被测类的私有方法、读写私有成员变量了,虽然IDE可能会提升语法有误,但编译器将会正常运行测试。
|
在测试类上添加`@EnablePrivateAccess`注解,然后即可直接在单元测试里像访问公有成员一样调用被测类的私有方法、读写私有成员变量了,虽然IDE可能会提升语法有误,但编译器将会正常运行测试。
|
||||||
|
|
||||||
若不希望看到IDE的语法错误提醒,或是在基于JVM的非Java语言项目里(譬如Kotlin语言),也可以借助`PrivateAccessor`工具类来实现私有成员的访问。
|
若不希望看到IDE的语法错误提醒,或是在基于JVM的非Java语言项目里(譬如Kotlin语言),也可以借助`PrivateAccessor`工具类来实现私有成员的访问。
|
||||||
|
|
||||||
@ -50,17 +50,17 @@
|
|||||||
|
|
||||||
**【1】覆写任意类的方法调用**
|
**【1】覆写任意类的方法调用**
|
||||||
|
|
||||||
定义一个普通方法,使它与待覆写方法名称和返回值类型完全一致,仅比待覆写方法在首位多一个该方法所属对象类型的参数,然后为这个方法加上`@TestableInject`注解,并设置`targetClass`属性值为被Mock方法的所属类型。
|
定义一个普通方法,使它与待覆写方法名称和返回值类型完全一致,仅比待覆写方法在首位多一个该方法所属对象类型的参数,然后为这个方法加上`@TestableMock`注解,并设置`targetClass`属性值为被Mock方法的所属类型。
|
||||||
|
|
||||||
此时被测类中所有对该类指定方法的调用,将在单元测试运行时,自动被替换为对上述自定义Mock方法的调用。
|
此时被测类中所有对该类指定方法的调用,将在单元测试运行时,自动被替换为对上述自定义Mock方法的调用。
|
||||||
|
|
||||||
`@TestableInject`注解还有一个很少需要用到的`targetMethod`属性,用于指定Mock的目标方法名称。使用此参数后被注释修饰的方法名称就可以随意命名了,通常仅在遇到极其罕见的Mock方法签名重名情况时才需要使用。
|
`@TestableMock`注解还有一个很少需要用到的`targetMethod`属性,用于指定Mock的目标方法名称。使用此参数后被注释修饰的方法名称就可以随意命名了,通常仅在遇到极其罕见的Mock方法签名重名情况时才需要使用。
|
||||||
|
|
||||||
示例项目文件`DemoServiceTest.java`中的`should_able_to_test_common_method()`用例详细展示了这几种用法。
|
示例项目文件`DemoServiceTest.java`中的`should_able_to_test_common_method()`用例详细展示了这几种用法。
|
||||||
|
|
||||||
**【2】覆写任意类的new操作**
|
**【2】覆写任意类的new操作**
|
||||||
|
|
||||||
同样还是定义一个普通方法,然后加上`@TestableInject`注解。方法名称随意,只需让方法的返回值为要覆写new操作的目标类型,且参数与指定类构造方法完全一致。
|
同样还是定义一个普通方法,然后加上`@TestableMock`注解。方法名称随意,只需让方法的返回值为要覆写new操作的目标类型,且参数与指定类构造方法完全一致。
|
||||||
|
|
||||||
此时被测类中所有用`new`创建指定类的操作将被替换为对该自定义方法的调用。
|
此时被测类中所有用`new`创建指定类的操作将被替换为对该自定义方法的调用。
|
||||||
|
|
||||||
@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
有时候,被测类自身的某个成员方法访问了外部系统,在进行单元测试的时候就需要将这个备查样自己的成员方法Mock掉。
|
有时候,被测类自身的某个成员方法访问了外部系统,在进行单元测试的时候就需要将这个备查样自己的成员方法Mock掉。
|
||||||
|
|
||||||
在测试类中声明一个名称、参数和返回值类型都与要覆写的目标方法完全一致的普通方法,同样加上`@TestableInject`注解,不配置`targetClass`属性,即可实现对被测类私有成员方法的覆写。
|
在测试类中声明一个名称、参数和返回值类型都与要覆写的目标方法完全一致的普通方法,同样加上`@TestableMock`注解,不配置`targetClass`属性,即可实现对被测类私有成员方法的覆写。
|
||||||
|
|
||||||
详见示例项目文件`DemoServiceTest.java`中的`should_able_to_test_member_method()`用例。
|
详见示例项目文件`DemoServiceTest.java`中的`should_able_to_test_member_method()`用例。
|
||||||
|
|
||||||
|
@ -12,5 +12,5 @@ public class ConstPool {
|
|||||||
public static final String TEST_POSTFIX = "Test";
|
public static final String TEST_POSTFIX = "Test";
|
||||||
public static final String TESTABLE_INJECT_REF = "_testableInternalRef";
|
public static final String TESTABLE_INJECT_REF = "_testableInternalRef";
|
||||||
|
|
||||||
public static final String TESTABLE_INJECT = "com.alibaba.testable.core.annotation.TestableInject";
|
public static final String TESTABLE_MOCK = "com.alibaba.testable.core.annotation.TestableMock";
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ public class TestClassHandler extends BaseClassHandler {
|
|||||||
for (AnnotationNode n : mn.visibleAnnotations) {
|
for (AnnotationNode n : mn.visibleAnnotations) {
|
||||||
visibleAnnotationNames.add(n.desc);
|
visibleAnnotationNames.add(n.desc);
|
||||||
}
|
}
|
||||||
if (visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.TESTABLE_INJECT))) {
|
if (visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.TESTABLE_MOCK))) {
|
||||||
mn.access &= ~ACC_PRIVATE;
|
mn.access &= ~ACC_PRIVATE;
|
||||||
mn.access &= ~ACC_PROTECTED;
|
mn.access &= ~ACC_PROTECTED;
|
||||||
mn.access |= ACC_PUBLIC;
|
mn.access |= ACC_PUBLIC;
|
||||||
|
@ -44,7 +44,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
if (shouldTransformAsSourceClass(className)) {
|
if (shouldTransformAsSourceClass(className)) {
|
||||||
// it's a source class with testable enabled
|
// it's a source class with testable enabled
|
||||||
loadedClassNames.add(new ComparableWeakRef<String>(className));
|
loadedClassNames.add(new ComparableWeakRef<String>(className));
|
||||||
List<MethodInfo> injectMethods = getTestableInjectMethods(ClassUtil.getTestClassName(className));
|
List<MethodInfo> injectMethods = getTestableMockMethods(ClassUtil.getTestClassName(className));
|
||||||
return new SourceClassHandler(injectMethods).getBytes(classFileBuffer);
|
return new SourceClassHandler(injectMethods).getBytes(classFileBuffer);
|
||||||
} else if (shouldTransformAsTestClass(className)) {
|
} else if (shouldTransformAsTestClass(className)) {
|
||||||
// it's a test class with testable enabled
|
// it's a test class with testable enabled
|
||||||
@ -58,19 +58,19 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldTransformAsSourceClass(String className) {
|
private boolean shouldTransformAsSourceClass(String className) {
|
||||||
return ClassUtil.anyMethodHasAnnotation(ClassUtil.getTestClassName(className), ConstPool.TESTABLE_INJECT);
|
return ClassUtil.anyMethodHasAnnotation(ClassUtil.getTestClassName(className), ConstPool.TESTABLE_MOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldTransformAsTestClass(String className) {
|
private boolean shouldTransformAsTestClass(String className) {
|
||||||
return className.endsWith(ConstPool.TEST_POSTFIX) &&
|
return className.endsWith(ConstPool.TEST_POSTFIX) &&
|
||||||
ClassUtil.anyMethodHasAnnotation(className, ConstPool.TESTABLE_INJECT);
|
ClassUtil.anyMethodHasAnnotation(className, ConstPool.TESTABLE_MOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSystemClass(ClassLoader loader, String className) {
|
private boolean isSystemClass(ClassLoader loader, String className) {
|
||||||
return !(loader instanceof URLClassLoader) || null == className || className.startsWith("jdk/");
|
return !(loader instanceof URLClassLoader) || null == className || className.startsWith("jdk/");
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MethodInfo> getTestableInjectMethods(String className) {
|
private List<MethodInfo> getTestableMockMethods(String className) {
|
||||||
try {
|
try {
|
||||||
List<MethodInfo> methodInfos = new ArrayList<MethodInfo>();
|
List<MethodInfo> methodInfos = new ArrayList<MethodInfo>();
|
||||||
ClassNode cn = new ClassNode();
|
ClassNode cn = new ClassNode();
|
||||||
@ -89,7 +89,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.TESTABLE_INJECT)) {
|
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.TESTABLE_MOCK)) {
|
||||||
String sourceClassName = ClassUtil.getSourceClassName(cn.name);
|
String sourceClassName = ClassUtil.getSourceClassName(cn.name);
|
||||||
String targetClass = getAnnotationParameter(an, TARGET_CLASS, sourceClassName);
|
String targetClass = getAnnotationParameter(an, TARGET_CLASS, sourceClassName);
|
||||||
String targetMethod = getAnnotationParameter(an, TARGET_METHOD, mn.name);
|
String targetMethod = getAnnotationParameter(an, TARGET_METHOD, mn.name);
|
||||||
|
@ -10,5 +10,5 @@ import java.lang.annotation.*;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Documented
|
@Documented
|
||||||
public @interface EnableTestable {
|
public @interface EnablePrivateAccess {
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ import java.lang.annotation.*;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@Documented
|
@Documented
|
||||||
public @interface TestableInject {
|
public @interface TestableMock {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mock method of specified class instead of the class under test
|
* mock method of specified class instead of the class under test
|
@ -1,9 +1,9 @@
|
|||||||
package com.alibaba.testable.core.processor;
|
package com.alibaba.testable.core.processor;
|
||||||
|
|
||||||
import com.alibaba.testable.core.annotation.EnableTestable;
|
import com.alibaba.testable.core.annotation.EnablePrivateAccess;
|
||||||
import com.alibaba.testable.core.constant.ConstPool;
|
import com.alibaba.testable.core.constant.ConstPool;
|
||||||
import com.alibaba.testable.core.model.TestableContext;
|
import com.alibaba.testable.core.model.TestableContext;
|
||||||
import com.alibaba.testable.core.translator.EnableTestableTranslator;
|
import com.alibaba.testable.core.translator.EnablePrivateAccessTranslator;
|
||||||
import com.alibaba.testable.core.util.TestableLogger;
|
import com.alibaba.testable.core.util.TestableLogger;
|
||||||
import com.sun.tools.javac.api.JavacTrees;
|
import com.sun.tools.javac.api.JavacTrees;
|
||||||
import com.sun.tools.javac.code.Symbol;
|
import com.sun.tools.javac.code.Symbol;
|
||||||
@ -26,8 +26,8 @@ import java.util.Set;
|
|||||||
/**
|
/**
|
||||||
* @author flin
|
* @author flin
|
||||||
*/
|
*/
|
||||||
@SupportedAnnotationTypes("com.alibaba.testable.core.annotation.EnableTestable")
|
@SupportedAnnotationTypes("com.alibaba.testable.core.annotation.EnablePrivateAccess")
|
||||||
public class EnableTestableProcessor extends AbstractProcessor {
|
public class EnablePrivateAccessProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
private TestableContext cx;
|
private TestableContext cx;
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ public class EnableTestableProcessor extends AbstractProcessor {
|
|||||||
if (cx.names == null) {
|
if (cx.names == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(EnableTestable.class);
|
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(EnablePrivateAccess.class);
|
||||||
for (Element element : elements) {
|
for (Element element : elements) {
|
||||||
if (element.getKind().isClass() && isTestClass(element.getSimpleName())) {
|
if (element.getKind().isClass() && isTestClass(element.getSimpleName())) {
|
||||||
processClassElement((Symbol.ClassSymbol)element);
|
processClassElement((Symbol.ClassSymbol)element);
|
||||||
@ -81,7 +81,7 @@ public class EnableTestableProcessor extends AbstractProcessor {
|
|||||||
private void processClassElement(Symbol.ClassSymbol clazz) {
|
private void processClassElement(Symbol.ClassSymbol clazz) {
|
||||||
JCTree tree = cx.trees.getTree(clazz);
|
JCTree tree = cx.trees.getTree(clazz);
|
||||||
String pkgName = ((Symbol.PackageSymbol)clazz.owner).fullname.toString();
|
String pkgName = ((Symbol.PackageSymbol)clazz.owner).fullname.toString();
|
||||||
tree.accept(new EnableTestableTranslator(pkgName, clazz.getSimpleName().toString(), cx));
|
tree.accept(new EnablePrivateAccessTranslator(pkgName, clazz.getSimpleName().toString(), cx));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -16,7 +16,7 @@ import java.lang.reflect.Modifier;
|
|||||||
*
|
*
|
||||||
* @author flin
|
* @author flin
|
||||||
*/
|
*/
|
||||||
public class EnableTestableTranslator extends BaseTranslator {
|
public class EnablePrivateAccessTranslator extends BaseTranslator {
|
||||||
|
|
||||||
private final String sourceClassName;
|
private final String sourceClassName;
|
||||||
private final ListBuffer<Name> sourceClassIns = new ListBuffer<Name>();
|
private final ListBuffer<Name> sourceClassIns = new ListBuffer<Name>();
|
||||||
@ -24,7 +24,7 @@ public class EnableTestableTranslator extends BaseTranslator {
|
|||||||
private final ListBuffer<String> privateMethods = new ListBuffer<String>();
|
private final ListBuffer<String> privateMethods = new ListBuffer<String>();
|
||||||
private final PrivateAccessStatementGenerator privateAccessStatementGenerator;
|
private final PrivateAccessStatementGenerator privateAccessStatementGenerator;
|
||||||
|
|
||||||
public EnableTestableTranslator(String pkgName, String testClassName, TestableContext cx) {
|
public EnablePrivateAccessTranslator(String pkgName, String testClassName, TestableContext cx) {
|
||||||
this.sourceClassName = testClassName.substring(0, testClassName.length() - ConstPool.TEST_POSTFIX.length());
|
this.sourceClassName = testClassName.substring(0, testClassName.length() - ConstPool.TEST_POSTFIX.length());
|
||||||
this.privateAccessStatementGenerator = new PrivateAccessStatementGenerator(cx);
|
this.privateAccessStatementGenerator = new PrivateAccessStatementGenerator(cx);
|
||||||
try {
|
try {
|
@ -1 +1 @@
|
|||||||
com.alibaba.testable.core.processor.EnableTestableProcessor
|
com.alibaba.testable.core.processor.EnablePrivateAccessProcessor
|
||||||
|
Loading…
Reference in New Issue
Block a user