use TEST_CASE and SOURCE_METHOD instead of method call

This commit is contained in:
金戟
2020-10-20 22:36:50 +08:00
parent 770aca8a00
commit d9a7e91b65
4 changed files with 73 additions and 33 deletions

View File

@@ -5,13 +5,26 @@ import com.alibaba.testable.agent.util.ClassUtil;
import org.objectweb.asm.tree.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author flin
*/
public class TestClassHandler extends BaseClassHandler {
private static final String CLASS_TESTABLE_UTIL = "com/alibaba/testable/core/util/TestableUtil";
private static final String FIELD_TEST_CASE = "TEST_CASE";
private static final String FIELD_SOURCE_METHOD = "SOURCE_METHOD";
private static final String METHOD_CURRENT_TEST_CASE_NAME = "currentTestCaseName";
private static final String METHOD_CURRENT_SOURCE_METHOD_NAME = "currentSourceMethodName";
private static final String SIGNATURE_TESTABLE_UTIL_METHOD = "(Ljava/lang/Object;)Ljava/lang/String;";
private static final Map<String, String> FIELD_TO_METHOD_MAPPING = new HashMap<String, String>() {{
put(FIELD_TEST_CASE, METHOD_CURRENT_TEST_CASE_NAME);
put(FIELD_SOURCE_METHOD, METHOD_CURRENT_SOURCE_METHOD_NAME);
}};
/**
* Handle bytecode of test class
* @param cn original class node
@@ -26,6 +39,11 @@ public class TestClassHandler extends BaseClassHandler {
}
private void transformMethod(ClassNode cn, MethodNode mn) {
handleAnnotation(cn, mn);
handleInstruction(cn, mn);
}
private void handleAnnotation(ClassNode cn, MethodNode mn) {
List<String> visibleAnnotationNames = new ArrayList<String>();
if (mn.visibleAnnotations == null) {
return;
@@ -42,6 +60,36 @@ public class TestClassHandler extends BaseClassHandler {
}
}
private void handleInstruction(ClassNode cn, MethodNode mn) {
AbstractInsnNode[] instructions = mn.instructions.toArray();
int i = 0;
do {
if (instructions[i].getOpcode() == GETSTATIC) {
FieldInsnNode fieldInsnNode = (FieldInsnNode)instructions[i];
if (isTestableUtilField(fieldInsnNode)) {
instructions = replaceTestableUtilField(mn, instructions, fieldInsnNode.name, i);
}
}
i++;
} while (i < instructions.length);
}
private boolean isTestableUtilField(FieldInsnNode fieldInsnNode) {
return fieldInsnNode.owner.equals(CLASS_TESTABLE_UTIL) &&
(fieldInsnNode.name.equals(FIELD_TEST_CASE) || fieldInsnNode.name.equals(FIELD_SOURCE_METHOD));
}
private AbstractInsnNode[] replaceTestableUtilField(MethodNode mn, AbstractInsnNode[] instructions,
String fieldName, int pos) {
InsnList insnNodes = new InsnList();
insnNodes.insert(new VarInsnNode(ALOAD, 0));
insnNodes.insert(new MethodInsnNode(INVOKESTATIC, CLASS_TESTABLE_UTIL, FIELD_TO_METHOD_MAPPING.get(fieldName),
SIGNATURE_TESTABLE_UTIL_METHOD, false));
mn.instructions.insertBefore(instructions[pos], insnNodes);
mn.instructions.remove(instructions[pos]);
return mn.instructions.toArray();
}
private void injectTestableRef(ClassNode cn, MethodNode mn) {
InsnList il = new InsnList();
il.add(new VarInsnNode(ALOAD, 0));

View File

@@ -7,6 +7,9 @@ import com.alibaba.testable.core.constant.ConstPool;
*/
public class TestableUtil {
public static String TEST_CASE;
public static String SOURCE_METHOD;
public static String currentSourceMethodName(Object testClassRef) {
Class<?> testClass = testClassRef.getClass();
StackTraceElement[] stack = getMainThread().getStackTrace();

View File

@@ -3,11 +3,12 @@ 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.util.TestableUtil;
import org.junit.jupiter.api.Test;
import java.util.concurrent.Callable;
import static com.alibaba.testable.core.util.TestableUtil.SOURCE_METHOD;
import static com.alibaba.testable.core.util.TestableUtil.TEST_CASE;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnableTestable
@@ -40,7 +41,7 @@ class DemoServiceTest {
@TestableInject
private String callFromDifferentMethod() {
switch (TestableUtil.currentSourceMethodName(this)) {
switch (SOURCE_METHOD) {
case "callerOne": return "mock_one";
default: return "mock_others";
}
@@ -80,25 +81,15 @@ class DemoServiceTest {
@Test
void should_able_to_get_source_method_name() throws Exception {
assertEquals("mock_one", demoService.callerOne());
assertEquals("mock_others", demoService.callerTwo());
assertEquals("mock_one_mock_others", new Callable<String>() {
@Override
public String call() {
return demoService.callerOne() + "_" + demoService.callerTwo();
}
}.call());
assertEquals("mock_one_mock_others", demoService.callerTwo() + "_" + demoService.callerOne());
assertEquals("mock_one_mock_others", ((Callable<String>)() ->
demoService.callerOne() + "_" + demoService.callerTwo()).call());
}
@Test
void should_able_to_get_test_case_name() throws Exception {
assertEquals("should_able_to_get_test_case_name", TestableUtil.currentTestCaseName(this));
assertEquals("should_able_to_get_test_case_name", new Callable<String>() {
@Override
public String call() {
return TestableUtil.currentTestCaseName(this);
}
}.call());
assertEquals("should_able_to_get_test_case_name", TEST_CASE);
assertEquals("should_able_to_get_test_case_name", ((Callable<String>)() -> TEST_CASE).call());
}
}

View File

@@ -3,8 +3,9 @@ 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.util.TestableUtil
import org.junit.jupiter.api.Assertions
import com.alibaba.testable.core.util.TestableUtil.SOURCE_METHOD
import com.alibaba.testable.core.util.TestableUtil.TEST_CASE
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.concurrent.Callable
@@ -28,7 +29,7 @@ internal class DemoServiceTest {
private fun startsWith(self: BlackBox, s: String) = false
@TestableInject
private fun callFromDifferentMethod() = when (TestableUtil.currentSourceMethodName(this)) {
private fun callFromDifferentMethod() = when (SOURCE_METHOD) {
"callerOne" -> "mock_one"
else -> "mock_others"
}
@@ -37,45 +38,42 @@ internal class DemoServiceTest {
@Test
fun should_able_to_test_private_method() {
Assertions.assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1))
assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1))
}
@Test
fun should_able_to_test_private_field() {
PrivateAccessor.set(demoService, "count", 3)
Assertions.assertEquals("5", demoService.privateFieldAccessFunc())
Assertions.assertEquals(5, PrivateAccessor.get(demoService, "count"))
assertEquals("5", demoService.privateFieldAccessFunc())
assertEquals(5, PrivateAccessor.get(demoService, "count"))
}
@Test
fun should_able_to_test_new_object() {
Assertions.assertEquals("mock_something", demoService.newFunc())
assertEquals("mock_something", demoService.newFunc())
}
@Test
fun should_able_to_test_member_method() {
Assertions.assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello"))
assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello"))
}
@Test
fun should_able_to_test_common_method() {
Assertions.assertEquals("trim_string__sub_string__false", demoService.commonFunc())
assertEquals("trim_string__sub_string__false", demoService.commonFunc())
}
@Test
fun should_able_to_get_source_method_name() {
Assertions.assertEquals("mock_one", demoService.callerOne())
Assertions.assertEquals("mock_others", demoService.callerTwo())
Assertions.assertEquals("mock_one_mock_others", Callable<String> {
assertEquals("mock_one_mock_others", demoService.callerTwo() + "_" + demoService.callerOne())
assertEquals("mock_one_mock_others", Callable<String> {
demoService.callerOne() + "_" + demoService.callerTwo()
}.call())
}
@Test
fun should_able_to_get_test_case_name() {
Assertions.assertEquals("should_able_to_get_test_case_name", TestableUtil.currentTestCaseName(this))
Assertions.assertEquals("should_able_to_get_test_case_name", Callable<String> {
TestableUtil.currentTestCaseName(this)
}.call())
assertEquals("should_able_to_get_test_case_name", TEST_CASE)
assertEquals("should_able_to_get_test_case_name", Callable<String> { TEST_CASE }.call())
}
}