diff --git a/core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java b/core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java index 45f3d6b..91d0001 100644 --- a/core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java +++ b/core/src/main/java/com/alibaba/testable/core/util/TestableUtil.java @@ -7,11 +7,8 @@ import com.alibaba.testable.core.constant.ConstPool; */ public class TestableUtil { - public static String sourceMemberMethodName(Object testClassRef) { - return sourceMemberMethodName(testClassRef.getClass()); - } - - public static String sourceMemberMethodName(Class testClass) { + public static String currentSourceMethodName(Object testClassRef) { + Class testClass = testClassRef.getClass(); StackTraceElement[] stack = getMainThread().getStackTrace(); String testClassName = getRealClassName(testClass); String sourceClassName = testClassName.substring(0, testClassName.length() - ConstPool.TEST_POSTFIX.length()); @@ -24,10 +21,7 @@ public class TestableUtil { } public static String currentTestCaseName(Object testClassRef) { - return currentTestCaseName(testClassRef.getClass()); - } - - public static String currentTestCaseName(Class testClass) { + Class testClass = testClassRef.getClass(); StackTraceElement[] stack = getMainThread().getStackTrace(); String testClassName = getRealClassName(testClass); for (int i = stack.length - 1; i >= 0; i--) { diff --git a/demo/java-demo/pom.xml b/demo/java-demo/pom.xml new file mode 100644 index 0000000..9583949 --- /dev/null +++ b/demo/java-demo/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.4.RELEASE + + + com.alibaba.testable + java-demo + 1.0.0-SNAPSHOT + java-demo + Demo project for Spring Boot + + + 1.8 + 0.1.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.testable + core + ${testable.version} + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + org.ow2.asm + asm + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + @{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/agent/${testable.version}/agent-${testable.version}.jar + + + + org.jacoco + jacoco-maven-plugin + 0.8.6 + + + prepare-agent + + prepare-agent + + + + report + prepare-package + + report + + + target/jacoco.exec + target/jacoco-ut + + + + + + + + diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/BlackBox.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/BlackBox.java new file mode 100644 index 0000000..9fa953c --- /dev/null +++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/BlackBox.java @@ -0,0 +1,15 @@ +package com.alibaba.testable.demo; + +public class BlackBox { + + private String data; + + public BlackBox(String data) { + this.data = data; + } + + public String callMe() { + return data; + } + +} diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/DemoApplication.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/DemoApplication.java new file mode 100644 index 0000000..993f22d --- /dev/null +++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/DemoApplication.java @@ -0,0 +1,12 @@ +package com.alibaba.testable.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/DemoService.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/DemoService.java new file mode 100644 index 0000000..467be9c --- /dev/null +++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/DemoService.java @@ -0,0 +1,66 @@ +package com.alibaba.testable.demo; + +import org.springframework.stereotype.Service; +import sun.net.www.http.HttpClient; + +import java.net.URL; + +@Service +public class DemoService { + + private int count; + + /** + * Target 1 - private method + */ + private String privateFunc(String s, int i) { + return s + " - " + i; + } + + /** + * Target 2 - method with private field access + */ + public String privateFieldAccessFunc() { + count += 2; + return String.valueOf(count); + } + + /** + * Target 3 - method with new operation + */ + public String newFunc() { + BlackBox component = new BlackBox("something"); + return component.callMe(); + } + + /** + * Target 4 - method with member method invoke + */ + public String outerFunc(String s) throws Exception { + return "{ \"res\": \"" + innerFunc(s) + "\"}"; + } + + /** + * Target 5 - method with common method invoke + */ + public String commonFunc() { + return "anything".trim() + "__" + "anything".substring(1, 2) + "__" + "abc".startsWith("ab"); + } + + public String callerOne() { + return callFromDifferentMethod(); + } + + public String callerTwo() { + return callFromDifferentMethod(); + } + + private String innerFunc(String s) throws Exception { + return HttpClient.New(new URL("http:/xxx/" + s)).getURLFile(); + } + + private String callFromDifferentMethod() { + return "realOne"; + } + +} diff --git a/demo/java-demo/src/main/resources/application.properties b/demo/java-demo/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/demo/java-demo/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoApplicationTests.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoApplicationTests.java new file mode 100644 index 0000000..763c918 --- /dev/null +++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoApplicationTests.java @@ -0,0 +1,13 @@ +package com.alibaba.testable.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DemoApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoServiceTest.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoServiceTest.java new file mode 100644 index 0000000..204fb6f --- /dev/null +++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoServiceTest.java @@ -0,0 +1,104 @@ +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 org.junit.jupiter.api.Assertions.assertEquals; + +@EnableTestable +class DemoServiceTest { + + @TestableInject + private BlackBox createBlackBox(String text) { + return new BlackBox("mock_" + text); + } + + @TestableInject + private String innerFunc(String text) { + return "mock_" + text; + } + + @TestableInject(targetClass="java.lang.String") + private String trim(String self) { + return "trim_string"; + } + + @TestableInject(targetClass="java.lang.String", targetMethod = "substring") + private String sub(String self, int i, int j) { + return "sub_string"; + } + + @TestableInject(targetClass="java.lang.String") + private boolean startsWith(String self, String s) { + return false; + } + + @TestableInject + private String callFromDifferentMethod() { + switch (TestableUtil.currentSourceMethodName(this)) { + case "callerOne": return "mock_one"; + default: return "mock_others"; + } + } + + private DemoService demoService = new DemoService(); + + @Test + void should_able_to_test_private_method() throws Exception { + assertEquals("hello - 1", demoService.privateFunc("hello", 1)); + assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1)); + } + + @Test + void should_able_to_test_private_field() throws Exception { + demoService.count = 2; + assertEquals("4", demoService.privateFieldAccessFunc()); + PrivateAccessor.set(demoService, "count", 3); + assertEquals("5", demoService.privateFieldAccessFunc()); + assertEquals(new Integer(5), PrivateAccessor.get(demoService, "count")); + } + + @Test + void should_able_to_test_new_object() throws Exception { + assertEquals("mock_something", demoService.newFunc()); + } + + @Test + void should_able_to_test_member_method() throws Exception { + assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello")); + } + + @Test + void should_able_to_test_common_method() throws Exception { + assertEquals("trim_string__sub_string__false", demoService.commonFunc()); + } + + @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() { + @Override + public String call() { + return 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() { + @Override + public String call() { + return TestableUtil.currentTestCaseName(this); + } + }.call()); + } + +} diff --git a/demo/kotlin-demo/pom.xml b/demo/kotlin-demo/pom.xml new file mode 100644 index 0000000..0a5116c --- /dev/null +++ b/demo/kotlin-demo/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.4.RELEASE + + + com.alibaba.testable + kotlin-demo + 0.0.1-SNAPSHOT + kotlin-demo + Demo project for Spring Boot + + + 1.8 + 1.3.72 + 0.1.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter + + + org.jetbrains.kotlin + kotlin-reflect + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.testable + core + ${testable.version} + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + @{argLine} -javaagent:${settings.localRepository}/com/alibaba/testable/agent/${testable.version}/agent-${testable.version}.jar + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + -Xjsr305=strict + + + spring + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + org.jacoco + jacoco-maven-plugin + 0.8.6 + + + prepare-agent + + prepare-agent + + + + report + prepare-package + + report + + + target/jacoco.exec + target/jacoco-ut + + + + + + + + diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/BlackBox.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/BlackBox.kt new file mode 100644 index 0000000..f7b7295 --- /dev/null +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/BlackBox.kt @@ -0,0 +1,22 @@ +package com.alibaba.testable.demo + + +class BlackBox(private val data: String) { + + fun callMe(): String { + return data + } + + fun trim(): String { + return data.trim() + } + + fun substring(from: Int, to: Int): String { + return data.substring(from, to) + } + + fun startsWith(prefix: String): Boolean { + return data.startsWith(prefix) + } + +} diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoApplication.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoApplication.kt new file mode 100644 index 0000000..e2a3ca6 --- /dev/null +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoApplication.kt @@ -0,0 +1,11 @@ +package com.alibaba.testable.demo + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication + +@SpringBootApplication +class DemoApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoService.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoService.kt new file mode 100644 index 0000000..61125b3 --- /dev/null +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoService.kt @@ -0,0 +1,61 @@ +package com.alibaba.testable.demo + +import org.springframework.stereotype.Service +import sun.net.www.http.HttpClient +import java.net.URL + + +@Service +class DemoService { + + private var count = 0 + + /** + * Target 1 - private method + */ + private fun privateFunc(s: String, i: Int): String { + return "$s - $i" + } + + /** + * Target 2 - method with private field access + */ + fun privateFieldAccessFunc(): String { + count += 2 + return count.toString() + } + + /** + * Target 3 - method with new operation + */ + fun newFunc(): String { + return BlackBox("something").callMe() + } + + /** + * Target 4 - method with member method invoke + */ + fun outerFunc(s: String): String { + return "{ \"res\": \"" + innerFunc(s) + "\"}" + } + + /** + * Target 5 - method with common method invoke + */ + fun commonFunc(): String { + val box = BlackBox("anything") + return box.trim() + "__" + box.substring(1, 2) + "__" + box.startsWith("any") + } + + fun callerOne(): String { + return callFromDifferentMethod() + } + + fun callerTwo(): String { + return callFromDifferentMethod() + } + + private fun innerFunc(s: String) = HttpClient.New(URL("http:/xxx/$s")).urlFile + + private fun callFromDifferentMethod() = "realOne" +} diff --git a/demo/kotlin-demo/src/main/resources/application.properties b/demo/kotlin-demo/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/demo/kotlin-demo/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoApplicationTests.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoApplicationTests.kt new file mode 100644 index 0000000..b64004c --- /dev/null +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoApplicationTests.kt @@ -0,0 +1,13 @@ +package com.alibaba.testable.demo + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest +class DemoApplicationTests { + + @Test + fun contextLoads() { + } + +} diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoServiceTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoServiceTest.kt new file mode 100644 index 0000000..de9bbe8 --- /dev/null +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoServiceTest.kt @@ -0,0 +1,81 @@ +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 org.junit.jupiter.api.Test +import java.util.concurrent.Callable + + +@EnableTestable +internal class DemoServiceTest { + + @TestableInject + private fun createBlackBox(text: String) = BlackBox("mock_$text") + + @TestableInject + private fun innerFunc(text: String) = "mock_$text" + + @TestableInject(targetClass = "com.alibaba.testable.demo.BlackBox") + private fun trim(self: BlackBox) = "trim_string" + + @TestableInject(targetClass = "com.alibaba.testable.demo.BlackBox", targetMethod = "substring") + private fun sub(self: BlackBox, i: Int, j: Int) = "sub_string" + + @TestableInject(targetClass = "com.alibaba.testable.demo.BlackBox") + private fun startsWith(self: BlackBox, s: String) = false + + @TestableInject + private fun callFromDifferentMethod() = when (TestableUtil.currentSourceMethodName(this)) { + "callerOne" -> "mock_one" + else -> "mock_others" + } + + private val demoService = DemoService() + + @Test + fun should_able_to_test_private_method() { + Assertions.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")) + } + + @Test + fun should_able_to_test_new_object() { + Assertions.assertEquals("mock_something", demoService.newFunc()) + } + + @Test + fun should_able_to_test_member_method() { + Assertions.assertEquals("{ \"res\": \"mock_hello\"}", demoService.outerFunc("hello")) + } + + @Test + fun should_able_to_test_common_method() { + Assertions.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 { + 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 { + TestableUtil.currentTestCaseName(this) + }.call()) + } +} diff --git a/demo/pom.xml b/demo/pom.xml new file mode 100755 index 0000000..bee3c09 --- /dev/null +++ b/demo/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + com.alibaba.testable + demo + 1.0.0-SNAPSHOT + pom + demos + + + java-demo + kotlin-demo + + + diff --git a/pom.xml b/pom.xml index 9323b88..1c30c15 100755 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ agent core + demo