implement verify mock invoke times method

This commit is contained in:
金戟 2020-10-25 10:23:43 +08:00
parent ee21025dbb
commit e2d9c754e0
10 changed files with 120 additions and 8 deletions

View File

@ -7,8 +7,7 @@ import org.junit.jupiter.api.Test;
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 com.alibaba.testable.core.tool.TestableTool.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnablePrivateAccess
@ -70,16 +69,21 @@ class DemoServiceTest {
@Test
void should_able_to_test_new_object() throws Exception {
assertEquals("mock_something", demoService.newFunc());
verify("createBlackBox").times(1);
}
@Test
void should_able_to_test_member_method() throws Exception {
assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello"));
verify("innerFunc").times(1);
}
@Test
void should_able_to_test_common_method() throws Exception {
assertEquals("trim_string__sub_string__false", demoService.commonFunc());
verify("trim").times(1);
verify("sub").times(1);
verify("startsWith").times(1);
}
@Test
@ -89,6 +93,7 @@ class DemoServiceTest {
// asynchronous
assertEquals("mock_one_mock_others",
Executors.newSingleThreadExecutor().submit(() -> demoService.callerOne() + "_" + demoService.callerTwo()).get());
verify("callFromDifferentMethod").times(4);
}
@Test
@ -97,6 +102,7 @@ class DemoServiceTest {
assertEquals("mock_special", demoService.callerOne());
// asynchronous
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit(() -> demoService.callerOne()).get());
verify("callFromDifferentMethod").times(2);
}
}

View File

@ -3,8 +3,7 @@ package com.alibaba.testable.demo
import com.alibaba.testable.core.accessor.PrivateAccessor
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 com.alibaba.testable.core.tool.TestableTool.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import java.util.concurrent.Executors
@ -57,16 +56,21 @@ internal class DemoServiceTest {
@Test
fun should_able_to_test_new_object() {
assertEquals("mock_something", demoService.newFunc())
verify("createBlackBox").times(1)
}
@Test
fun should_able_to_test_member_method() {
assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello"))
verify("innerFunc").times(1)
}
@Test
fun should_able_to_test_common_method() {
assertEquals("trim_string__sub_string__false", demoService.commonFunc())
verify("trim").times(1)
verify("sub").times(1)
verify("startsWith").times(1)
}
@Test
@ -77,6 +81,7 @@ internal class DemoServiceTest {
assertEquals("mock_one_mock_others", Executors.newSingleThreadExecutor().submit<String> {
demoService.callerOne() + "_" + demoService.callerTwo()
}.get())
verify("callFromDifferentMethod").times(4)
}
@Test
@ -87,5 +92,6 @@ internal class DemoServiceTest {
assertEquals("mock_special", Executors.newSingleThreadExecutor().submit<String> {
demoService.callerOne()
}.get())
verify("callFromDifferentMethod").times(2)
}
}

View File

@ -9,8 +9,8 @@
<packaging>pom</packaging>
<modules>
<module>testable-agent</module>
<module>testable-core</module>
<module>testable-agent</module>
<module>testable-maven-plugin</module>
<module>demo</module>
</modules>

View File

@ -20,7 +20,9 @@ public class TestClassHandler extends BaseClassHandler {
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 METHOD_COUNT_MOCK_INVOKE = "countMockInvoke";
private static final String SIGNATURE_TESTABLE_UTIL_METHOD = "(Ljava/lang/Object;)Ljava/lang/String;";
private static final String SIGNATURE_INVOKE_COUNTER_METHOD = "()V";
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);
@ -56,6 +58,7 @@ public class TestClassHandler extends BaseClassHandler {
mn.access &= ~ACC_PRIVATE;
mn.access &= ~ACC_PROTECTED;
mn.access |= ACC_PUBLIC;
injectInvokeCounter(mn);
} else if (couldBeTestMethod(mn)) {
injectTestableRef(cn, mn);
}
@ -92,6 +95,12 @@ public class TestClassHandler extends BaseClassHandler {
return mn.instructions.toArray();
}
private void injectInvokeCounter(MethodNode mn) {
MethodInsnNode node = new MethodInsnNode(INVOKESTATIC, CLASS_TESTABLE_UTIL, METHOD_COUNT_MOCK_INVOKE,
SIGNATURE_INVOKE_COUNTER_METHOD, false);
mn.instructions.insertBefore(mn.instructions.get(0), node);
}
private void injectTestableRef(ClassNode cn, MethodNode mn) {
InsnList il = new InsnList();
il.add(new VarInsnNode(ALOAD, 0));

View File

@ -1,4 +1,4 @@
package com.alibaba.testable.agent.model;
package com.alibaba.testable.agent.tool;
/**

View File

@ -3,7 +3,7 @@ package com.alibaba.testable.agent.transformer;
import com.alibaba.testable.agent.constant.ConstPool;
import com.alibaba.testable.agent.handler.SourceClassHandler;
import com.alibaba.testable.agent.handler.TestClassHandler;
import com.alibaba.testable.agent.model.ImmutablePair;
import com.alibaba.testable.agent.tool.ImmutablePair;
import com.alibaba.testable.agent.model.MethodInfo;
import com.alibaba.testable.agent.tool.ComparableWeakRef;
import com.alibaba.testable.agent.util.ClassUtil;

View File

@ -0,0 +1,16 @@
package com.alibaba.testable.core.error;
/**
* @author flin
*/
public class VerifyFailedError extends AssertionError {
public VerifyFailedError(int actualCount, int expectedCount) {
super(getErrorMessage(actualCount, expectedCount));
}
private static String getErrorMessage(int actualCount, int expectedCount) {
return "\nExpected times : " + expectedCount + "\nActual times : " + actualCount;
}
}

View File

@ -0,0 +1,22 @@
package com.alibaba.testable.core.tool;
import com.alibaba.testable.core.error.VerifyFailedError;
/**
* @author flin
*/
public class InvokeCounter {
private final int actualCount;
public InvokeCounter(int actualCount) {
this.actualCount = actualCount;
}
public void times(int expectedCount) {
if (expectedCount != actualCount) {
throw new VerifyFailedError(actualCount, expectedCount);
}
}
}

View File

@ -1,5 +1,7 @@
package com.alibaba.testable.core.tool;
import com.alibaba.testable.core.util.TestableUtil;
/**
* @author flin
*/
@ -15,4 +17,14 @@ public class TestableTool {
*/
public static String SOURCE_METHOD;
/**
* Get counter to check whether specified mock method invoked
* @param mockMethodName name of a mock method
*/
public static InvokeCounter verify(String mockMethodName) {
String testClass = Thread.currentThread().getStackTrace()[TestableUtil.INDEX_OF_TEST_CLASS].getClassName();
String testCaseName = TestableUtil.currentTestCaseName(testClass);
return new InvokeCounter(TestableUtil.getInvokeCount(mockMethodName, testCaseName));
}
}

View File

@ -2,11 +2,34 @@ package com.alibaba.testable.core.util;
import com.alibaba.testable.core.constant.ConstPool;
import java.util.HashMap;
import java.util.Map;
/**
* @author flin
*/
public class TestableUtil {
private static final Map<String, Integer> INVOKE_RECORDS = new HashMap<String, Integer>();
private final static String JOINER = "->";
/**
* [0]Thread -> [1]TestableUtil/TestableTool -> [2]TestClass
*/
public static final int INDEX_OF_TEST_CLASS = 2;
/**
* Record mock method invoke event
*/
public static void countMockInvoke() {
StackTraceElement mockMethodTraceElement = Thread.currentThread().getStackTrace()[INDEX_OF_TEST_CLASS];
String mockMethodName = mockMethodTraceElement.getMethodName();
String testClass = mockMethodTraceElement.getClassName();
String testCaseName = TestableUtil.currentTestCaseName(testClass);
String key = testCaseName + JOINER + mockMethodName;
int count = getInvokeCount(mockMethodName, testCaseName);
INVOKE_RECORDS.put(key, count + 1);
}
/**
* Get the last visit method in source file
* @param testClassRef usually `this` variable of the test class
@ -30,8 +53,17 @@ public class TestableUtil {
*/
public static String currentTestCaseName(Object testClassRef) {
Class<?> testClass = testClassRef.getClass();
StackTraceElement[] stack = getMainThread().getStackTrace();
String testClassName = getRealClassName(testClass);
return currentTestCaseName(testClassName);
}
/**
* Get current test case method
* @param testClassName name of current test class
* @return method name
*/
public static String currentTestCaseName(String testClassName) {
StackTraceElement[] stack = getMainThread().getStackTrace();
for (int i = stack.length - 1; i >= 0; i--) {
if (stack[i].getClassName().equals(testClassName)) {
return stack[i].getMethodName();
@ -40,6 +72,15 @@ public class TestableUtil {
return "";
}
public static int getInvokeCount(String mockMethodName, String testCaseName) {
String key = testCaseName + JOINER + mockMethodName;
Integer count = INVOKE_RECORDS.get(key);
if (count == null) {
count = 0;
}
return count;
}
private static String findLastMethodFromSourceClass(String sourceClassName, StackTraceElement[] stack) {
for (StackTraceElement element : stack) {
if (element.getClassName().equals(sourceClassName)) {