rename annotations to reflect actual use

This commit is contained in:
金戟 2020-10-24 09:30:36 +08:00
parent a85a4daf9e
commit ee21025dbb
12 changed files with 43 additions and 42 deletions

View File

@ -1,46 +1,45 @@
package com.alibaba.testable.demo;
import com.alibaba.testable.core.accessor.PrivateAccessor;
import com.alibaba.testable.core.annotation.EnableTestable;
import com.alibaba.testable.core.annotation.TestableInject;
import com.alibaba.testable.core.annotation.EnablePrivateAccess;
import com.alibaba.testable.core.annotation.TestableMock;
import org.junit.jupiter.api.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import static com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD;
import static com.alibaba.testable.core.tool.TestableTool.TEST_CASE;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnableTestable
@EnablePrivateAccess
class DemoServiceTest {
@TestableInject
@TestableMock
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
@TestableInject
@TestableMock
private String innerFunc(String text) {
return "mock_" + text;
}
@TestableInject(targetClass = String.class)
@TestableMock(targetClass = String.class)
private String trim(String self) {
return "trim_string";
}
@TestableInject(targetClass = String.class, targetMethod = "substring")
@TestableMock(targetClass = String.class, targetMethod = "substring")
private String sub(String self, int i, int j) {
return "sub_string";
}
@TestableInject(targetClass = String.class)
@TestableMock(targetClass = String.class)
private boolean startsWith(String self, String s) {
return false;
}
@TestableInject
@TestableMock
private String callFromDifferentMethod() {
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
return "mock_special";

View File

@ -1,8 +1,8 @@
package com.alibaba.testable.demo
import com.alibaba.testable.core.accessor.PrivateAccessor
import com.alibaba.testable.core.annotation.EnableTestable
import com.alibaba.testable.core.annotation.TestableInject
import com.alibaba.testable.core.annotation.EnablePrivateAccess
import com.alibaba.testable.core.annotation.TestableMock
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
import com.alibaba.testable.core.tool.TestableTool.TEST_CASE
import org.junit.jupiter.api.Assertions.assertEquals
@ -10,25 +10,25 @@ import org.junit.jupiter.api.Test
import java.util.concurrent.Executors
@EnableTestable
@EnablePrivateAccess
internal class DemoServiceTest {
@TestableInject
@TestableMock
private fun createBlackBox(text: String) = BlackBox("mock_$text")
@TestableInject
@TestableMock
private fun innerFunc(text: String) = "mock_$text"
@TestableInject(targetClass = BlackBox::class)
@TestableMock(targetClass = BlackBox::class)
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"
@TestableInject(targetClass = BlackBox::class)
@TestableMock(targetClass = BlackBox::class)
private fun startsWith(self: BlackBox, s: String) = false
@TestableInject
@TestableMock
private fun callFromDifferentMethod(): String {
return if (TEST_CASE == "should_able_to_get_test_case_name") {
"mock_special"

View File

@ -3,6 +3,8 @@
## v0.2.0
- use `TestableTool` class to expose test context
- add `testable-maven-plugin` module
- remove dependence on EnableTestable annotation in `testable-agent`
- rename annotations to reflect the actual use
## v0.1.0
- move generated agent jar to class folder

View File

@ -40,7 +40,7 @@
### 访问私有成员字段和方法
在测试类上添加`@EnableTestable`注解然后即可直接在单元测试里像访问公有成员一样调用被测类的私有方法、读写私有成员变量了虽然IDE可能会提升语法有误但编译器将会正常运行测试。
在测试类上添加`@EnablePrivateAccess`注解然后即可直接在单元测试里像访问公有成员一样调用被测类的私有方法、读写私有成员变量了虽然IDE可能会提升语法有误但编译器将会正常运行测试。
若不希望看到IDE的语法错误提醒或是在基于JVM的非Java语言项目里譬如Kotlin语言也可以借助`PrivateAccessor`工具类来实现私有成员的访问。
@ -50,17 +50,17 @@
**【1】覆写任意类的方法调用**
定义一个普通方法,使它与待覆写方法名称和返回值类型完全一致,仅比待覆写方法在首位多一个该方法所属对象类型的参数,然后为这个方法加上`@TestableInject`注解,并设置`targetClass`属性值为被Mock方法的所属类型。
定义一个普通方法,使它与待覆写方法名称和返回值类型完全一致,仅比待覆写方法在首位多一个该方法所属对象类型的参数,然后为这个方法加上`@TestableMock`注解,并设置`targetClass`属性值为被Mock方法的所属类型。
此时被测类中所有对该类指定方法的调用将在单元测试运行时自动被替换为对上述自定义Mock方法的调用。
`@TestableInject`注解还有一个很少需要用到的`targetMethod`属性用于指定Mock的目标方法名称。使用此参数后被注释修饰的方法名称就可以随意命名了通常仅在遇到极其罕见的Mock方法签名重名情况时才需要使用。
`@TestableMock`注解还有一个很少需要用到的`targetMethod`属性用于指定Mock的目标方法名称。使用此参数后被注释修饰的方法名称就可以随意命名了通常仅在遇到极其罕见的Mock方法签名重名情况时才需要使用。
示例项目文件`DemoServiceTest.java`中的`should_able_to_test_common_method()`用例详细展示了这几种用法。
**【2】覆写任意类的new操作**
同样还是定义一个普通方法,然后加上`@TestableInject`注解。方法名称随意只需让方法的返回值为要覆写new操作的目标类型且参数与指定类构造方法完全一致。
同样还是定义一个普通方法,然后加上`@TestableMock`注解。方法名称随意只需让方法的返回值为要覆写new操作的目标类型且参数与指定类构造方法完全一致。
此时被测类中所有用`new`创建指定类的操作将被替换为对该自定义方法的调用。
@ -70,7 +70,7 @@
有时候被测类自身的某个成员方法访问了外部系统在进行单元测试的时候就需要将这个备查样自己的成员方法Mock掉。
在测试类中声明一个名称、参数和返回值类型都与要覆写的目标方法完全一致的普通方法,同样加上`@TestableInject`注解,不配置`targetClass`属性,即可实现对被测类私有成员方法的覆写。
在测试类中声明一个名称、参数和返回值类型都与要覆写的目标方法完全一致的普通方法,同样加上`@TestableMock`注解,不配置`targetClass`属性,即可实现对被测类私有成员方法的覆写。
详见示例项目文件`DemoServiceTest.java`中的`should_able_to_test_member_method()`用例。

View File

@ -12,5 +12,5 @@ public class ConstPool {
public static final String TEST_POSTFIX = "Test";
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";
}

View File

@ -52,7 +52,7 @@ public class TestClassHandler extends BaseClassHandler {
for (AnnotationNode n : mn.visibleAnnotations) {
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_PROTECTED;
mn.access |= ACC_PUBLIC;

View File

@ -44,7 +44,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
if (shouldTransformAsSourceClass(className)) {
// it's a source class with testable enabled
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);
} else if (shouldTransformAsTestClass(className)) {
// it's a test class with testable enabled
@ -58,19 +58,19 @@ public class TestableClassTransformer implements ClassFileTransformer {
}
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) {
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) {
return !(loader instanceof URLClassLoader) || null == className || className.startsWith("jdk/");
}
private List<MethodInfo> getTestableInjectMethods(String className) {
private List<MethodInfo> getTestableMockMethods(String className) {
try {
List<MethodInfo> methodInfos = new ArrayList<MethodInfo>();
ClassNode cn = new ClassNode();
@ -89,7 +89,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
return;
}
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 targetClass = getAnnotationParameter(an, TARGET_CLASS, sourceClassName);
String targetMethod = getAnnotationParameter(an, TARGET_METHOD, mn.name);

View File

@ -10,5 +10,5 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface EnableTestable {
public @interface EnablePrivateAccess {
}

View File

@ -10,7 +10,7 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface TestableInject {
public @interface TestableMock {
/**
* mock method of specified class instead of the class under test

View File

@ -1,9 +1,9 @@
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.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.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Symbol;
@ -26,8 +26,8 @@ import java.util.Set;
/**
* @author flin
*/
@SupportedAnnotationTypes("com.alibaba.testable.core.annotation.EnableTestable")
public class EnableTestableProcessor extends AbstractProcessor {
@SupportedAnnotationTypes("com.alibaba.testable.core.annotation.EnablePrivateAccess")
public class EnablePrivateAccessProcessor extends AbstractProcessor {
private TestableContext cx;
@ -59,7 +59,7 @@ public class EnableTestableProcessor extends AbstractProcessor {
if (cx.names == null) {
return true;
}
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(EnableTestable.class);
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(EnablePrivateAccess.class);
for (Element element : elements) {
if (element.getKind().isClass() && isTestClass(element.getSimpleName())) {
processClassElement((Symbol.ClassSymbol)element);
@ -81,7 +81,7 @@ public class EnableTestableProcessor extends AbstractProcessor {
private void processClassElement(Symbol.ClassSymbol clazz) {
JCTree tree = cx.trees.getTree(clazz);
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));
}
}

View File

@ -16,7 +16,7 @@ import java.lang.reflect.Modifier;
*
* @author flin
*/
public class EnableTestableTranslator extends BaseTranslator {
public class EnablePrivateAccessTranslator extends BaseTranslator {
private final String sourceClassName;
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 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.privateAccessStatementGenerator = new PrivateAccessStatementGenerator(cx);
try {

View File

@ -1 +1 @@
com.alibaba.testable.core.processor.EnableTestableProcessor
com.alibaba.testable.core.processor.EnablePrivateAccessProcessor