mirror of
https://github.com/alibaba/testable-mock.git
synced 2026-08-23 11:43:29 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf4c263d93 | ||
|
|
28bde0b85a | ||
|
|
179e71c7c2 | ||
|
|
f624c9131e | ||
|
|
83974cfb2a | ||
|
|
abb6d04c16 | ||
|
|
16ec3b9444 | ||
|
|
180dfe9da8 | ||
|
|
92a2c69e05 | ||
|
|
f40834eb94 | ||
|
|
2dea6f9480 | ||
|
|
2bdad3c1f6 | ||
|
|
3f672e45da | ||
|
|
328c8540a8 |
@@ -2,7 +2,7 @@
|
||||
|
||||
换种思路写Mock,让单元测试更简单。
|
||||
|
||||
无需初始化,不挑测试框架,甭管要换的方法是被测类的私有方法、静态方法还是其他任何类的成员方法,也甭管要换的对象是怎么创建的。写好Mock方法,加个`@TestableMock`注解,一切统统搞定。
|
||||
无需初始化,不挑测试框架,甭管要换的方法是被测类的私有方法、静态方法还是其他任何类的成员方法,也甭管要换的对象是怎么创建的。写好Mock方法,加个`@MockMethod`注解,一切统统搞定。
|
||||
|
||||
文档:https://alibaba.github.io/testable-mock/
|
||||
|
||||
@@ -35,5 +35,5 @@ mvn clean install
|
||||
docsify serve docs
|
||||
```
|
||||
|
||||
> Testable文档使用`docsify`工具生成,构建前请安装[nodejs](https://nodejs.org/en/download/)运行时,并使用`npm install -g docsify`命令安装文档生成工具。
|
||||
> TestableMock文档使用`docsify`工具生成,构建前请安装[nodejs](https://nodejs.org/en/download/)运行时,并使用`npm install -g docsify`命令安装文档生成工具。
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.4.0')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.0')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.4.2')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.2')
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<testable.version>0.4.0</testable.version>
|
||||
<testable.version>0.4.2</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.alibaba.testable.demo;
|
||||
|
||||
import com.alibaba.testable.core.annotation.TestableMock;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import com.alibaba.testable.demo.model.BlackBox;
|
||||
import com.alibaba.testable.demo.model.Box;
|
||||
import com.alibaba.testable.demo.model.Color;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* 演示父类变量引用子类对象时的Mock场景
|
||||
@@ -17,32 +17,32 @@ class DemoInheritTest {
|
||||
|
||||
private DemoInherit demoInherit = new DemoInherit();
|
||||
|
||||
@TestableMock(targetMethod = "put")
|
||||
@MockMethod(targetMethod = "put")
|
||||
private void put_into_box(Box self, String something) {
|
||||
self.put("put_" + something + "_into_box");
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "put")
|
||||
@MockMethod(targetMethod = "put")
|
||||
private void put_into_blackbox(BlackBox self, String something) {
|
||||
self.put("put_" + something + "_into_blackbox");
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "get")
|
||||
@MockMethod(targetMethod = "get")
|
||||
private String get_from_box(Box self) {
|
||||
return "get_from_box";
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "get")
|
||||
@MockMethod(targetMethod = "get")
|
||||
private String get_from_blackbox(BlackBox self) {
|
||||
return "get_from_blackbox";
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "getColor")
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private String get_color_from_color(Color self) {
|
||||
return "color_from_color";
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "getColor")
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private String get_color_from_blackbox(BlackBox self) {
|
||||
return "color_from_blackbox";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.alibaba.testable.demo;
|
||||
|
||||
import com.alibaba.testable.core.annotation.TestableMock;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import com.alibaba.testable.core.error.VerifyFailedError;
|
||||
import com.alibaba.testable.demo.model.BlackBox;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -17,15 +17,16 @@ class DemoMatcherTest {
|
||||
|
||||
private DemoMatcher demoMatcher = new DemoMatcher();
|
||||
|
||||
@TestableMock(targetMethod = "methodToBeMocked")
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithoutArgument(DemoMatcher self) {}
|
||||
|
||||
@TestableMock(targetMethod = "methodToBeMocked")
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithArguments(DemoMatcher self, Object a1, Object a2) {}
|
||||
|
||||
@TestableMock(targetMethod = "methodToBeMocked")
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private void methodWithArrayArgument(DemoMatcher self, Object[] a) {}
|
||||
|
||||
|
||||
@Test
|
||||
void should_match_no_argument() {
|
||||
demoMatcher.callMethodWithoutArgument();
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package com.alibaba.testable.demo;
|
||||
|
||||
import com.alibaba.testable.core.annotation.TestableMock;
|
||||
import com.alibaba.testable.core.tool.TestableConst;
|
||||
import com.alibaba.testable.core.annotation.MockConstructor;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import com.alibaba.testable.demo.model.BlackBox;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
|
||||
import static com.alibaba.testable.core.tool.TestableTool.*;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -19,37 +20,37 @@ class DemoMockTest {
|
||||
|
||||
private DemoMock demoMock = new DemoMock();
|
||||
|
||||
@TestableMock(targetMethod = TestableConst.CONSTRUCTOR)
|
||||
@MockConstructor
|
||||
private BlackBox createBlackBox(String text) {
|
||||
return new BlackBox("mock_" + text);
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private String innerFunc(DemoMock self, String text) {
|
||||
return "mock_" + text;
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private String trim(String self) {
|
||||
return "trim_string";
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "substring")
|
||||
@MockMethod(targetMethod = "substring")
|
||||
private String sub(String self, int i, int j) {
|
||||
return "sub_string";
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private boolean startsWith(String self, String s) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private BlackBox secretBox(BlackBox ignore) {
|
||||
return new BlackBox("not_secret_box");
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private String callFromDifferentMethod(DemoMock self) {
|
||||
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
|
||||
return "mock_special";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.alibaba.testable.demo;
|
||||
|
||||
import com.alibaba.testable.core.annotation.TestableMock;
|
||||
import com.alibaba.testable.core.tool.TestableConst;
|
||||
import com.alibaba.testable.core.annotation.MockConstructor;
|
||||
import com.alibaba.testable.core.annotation.MockMethod;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
@@ -19,24 +19,24 @@ class DemoTemplateTest {
|
||||
/* 第一种写法:使用泛型定义 */
|
||||
/* First solution: use generics type */
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private <T> List<T> getList(DemoTemplate self, T value) {
|
||||
return new ArrayList<T>() {{ add((T)(value.toString() + "_mock_list")); }};
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private <K, V> Map<K, V> getMap(DemoTemplate self, K key, V value) {
|
||||
return new HashMap<K, V>() {{ put(key, (V)(value.toString() + "_mock_map")); }};
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = TestableConst.CONSTRUCTOR)
|
||||
@MockConstructor
|
||||
private <T> HashSet<T> newHashSet() {
|
||||
HashSet<T> set = new HashSet<>();
|
||||
set.add((T)"insert_mock");
|
||||
return set;
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private <E> boolean add(Set s, E e) {
|
||||
s.add(e.toString() + "_mocked");
|
||||
return true;
|
||||
@@ -45,24 +45,24 @@ class DemoTemplateTest {
|
||||
/* 第二种写法:使用Object类型 */
|
||||
/* Second solution: use object type */
|
||||
|
||||
//@TestableMock
|
||||
//@MockMethod
|
||||
//private List<Object> getList(DemoTemplate self, Object value) {
|
||||
// return new ArrayList<Object>() {{ add(value.toString() + "_mock_list"); }};
|
||||
//}
|
||||
//
|
||||
//@TestableMock
|
||||
//@MockMethod
|
||||
//private Map<Object, Object> getMap(DemoTemplate self, Object key, Object value) {
|
||||
// return new HashMap<Object, Object>() {{ put(key, value.toString() + "_mock_map"); }};
|
||||
//}
|
||||
//
|
||||
//@TestableMock(targetMethod = TestableConst.CONSTRUCTOR)
|
||||
//@MockConstructor
|
||||
//private HashSet newHashSet() {
|
||||
// HashSet<Object> set = new HashSet<>();
|
||||
// set.add("insert_mock");
|
||||
// return set;
|
||||
//}
|
||||
//
|
||||
//@TestableMock
|
||||
//@MockMethod
|
||||
//private boolean add(Set s, Object e) {
|
||||
// s.add(e.toString() + "_mocked");
|
||||
// return true;
|
||||
|
||||
@@ -16,8 +16,8 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
|
||||
testImplementation("com.alibaba.testable:testable-all:0.4.0")
|
||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.0")
|
||||
testImplementation("com.alibaba.testable:testable-all:0.4.2")
|
||||
testAnnotationProcessor("com.alibaba.testable:testable-processor:0.4.2")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<junit.version>5.6.2</junit.version>
|
||||
<testable.version>0.4.0</testable.version>
|
||||
<testable.version>0.4.2</testable.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -41,8 +41,24 @@ class DemoMatcher {
|
||||
val longArray = arrayOf(1L, 2L)
|
||||
methodToBeMocked(1, 2)
|
||||
methodToBeMocked(1L, 2.0)
|
||||
|
||||
// below two invocations are equivalent
|
||||
methodToBeMocked(listOf(1), setOf(1.0f))
|
||||
// multiple lines method invocation
|
||||
methodToBeMocked(object : ArrayList<Int?>() {
|
||||
init {
|
||||
add(1)
|
||||
}
|
||||
}, object : HashSet<Float?>() {
|
||||
init {
|
||||
add(1.0f)
|
||||
}
|
||||
})
|
||||
|
||||
// below two invocations are equivalent
|
||||
methodToBeMocked(1.0, mapOf(1 to 1.0f))
|
||||
methodToBeMocked(1.0, object : HashMap<Int?, Float?>(2) { init { put(1, 1.0f) } })
|
||||
|
||||
methodToBeMocked(floatList, floatList)
|
||||
methodToBeMocked(longArray)
|
||||
methodToBeMocked(arrayOf(1.0, 2.0))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.alibaba.testable.demo
|
||||
|
||||
import com.alibaba.testable.core.annotation.TestableMock
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier
|
||||
import com.alibaba.testable.demo.model.BlackBox
|
||||
import com.alibaba.testable.demo.model.Box
|
||||
@@ -14,37 +14,38 @@ import org.junit.jupiter.api.Test
|
||||
*/
|
||||
internal class DemoInheritTest {
|
||||
|
||||
@TestableMock(targetMethod = "put")
|
||||
private val demoInherit = DemoInherit()
|
||||
|
||||
@MockMethod(targetMethod = "put")
|
||||
private fun put_into_box(self: Box, something: String) {
|
||||
self.put("put_" + something + "_into_box")
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "put")
|
||||
@MockMethod(targetMethod = "put")
|
||||
private fun put_into_blackbox(self: BlackBox, something: String) {
|
||||
self.put("put_" + something + "_into_blackbox")
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "get")
|
||||
@MockMethod(targetMethod = "get")
|
||||
private fun get_from_box(self: Box): String {
|
||||
return "get_from_box"
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "get")
|
||||
@MockMethod(targetMethod = "get")
|
||||
private fun get_from_blackbox(self: BlackBox): String {
|
||||
return "get_from_blackbox"
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "getColor")
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private fun get_color_from_color(self: Color): String {
|
||||
return "color_from_color"
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "getColor")
|
||||
@MockMethod(targetMethod = "getColor")
|
||||
private fun get_color_from_blackbox(self: BlackBox): String {
|
||||
return "color_from_blackbox"
|
||||
}
|
||||
|
||||
private val demoInherit = DemoInherit()
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_call_sub_object_method_by_parent_object() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.alibaba.testable.demo
|
||||
|
||||
import com.alibaba.testable.core.annotation.TestableMock
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.error.VerifyFailedError
|
||||
import com.alibaba.testable.core.matcher.InvokeMatcher
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier
|
||||
@@ -14,19 +14,20 @@ import org.junit.jupiter.api.Test
|
||||
*/
|
||||
internal class DemoMatcherTest {
|
||||
|
||||
@TestableMock(targetMethod = "methodToBeMocked")
|
||||
private val demoMatcher = DemoMatcher()
|
||||
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithoutArgument(self: DemoMatcher) {
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "methodToBeMocked")
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithArguments(self: DemoMatcher, a1: Any, a2: Any) {
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = "methodToBeMocked")
|
||||
@MockMethod(targetMethod = "methodToBeMocked")
|
||||
private fun methodWithArrayArgument(self: DemoMatcher, a: Array<Any>) {
|
||||
}
|
||||
|
||||
private val demoMatcher = DemoMatcher()
|
||||
|
||||
@Test
|
||||
fun should_match_no_argument() {
|
||||
@@ -43,8 +44,8 @@ internal class DemoMatcherTest {
|
||||
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.anyInt(), 2)
|
||||
InvokeVerifier.verify("methodWithArguments").withInOrder(InvokeMatcher.anyLong(), InvokeMatcher.anyNumber())
|
||||
// Note: Must use `::class.javaObjectType` for primary types check in Kotlin
|
||||
InvokeVerifier.verify("methodWithArguments").with(1.0, InvokeMatcher.anyMapOf(Int::class.javaObjectType, Float::class.javaObjectType))
|
||||
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anySetOf(Float::class.javaObjectType))
|
||||
InvokeVerifier.verify("methodWithArguments").with(1.0, InvokeMatcher.anyMapOf(Int::class.javaObjectType, Float::class.javaObjectType)).times(2)
|
||||
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anySetOf(Float::class.javaObjectType)).times(2)
|
||||
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyList(), InvokeMatcher.anyListOf(Float::class.javaObjectType))
|
||||
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArrayOf(Long::class.javaObjectType))
|
||||
InvokeVerifier.verify("methodWithArrayArgument").with(InvokeMatcher.anyArray())
|
||||
@@ -69,12 +70,12 @@ internal class DemoMatcherTest {
|
||||
@Test
|
||||
fun should_match_with_times() {
|
||||
demoMatcher.callMethodWithNumberArguments()
|
||||
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(3)
|
||||
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(4)
|
||||
|
||||
demoMatcher.callMethodWithNumberArguments()
|
||||
var gotError = false
|
||||
try {
|
||||
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(4)
|
||||
InvokeVerifier.verify("methodWithArguments").with(InvokeMatcher.anyNumber(), InvokeMatcher.any()).times(5)
|
||||
} catch (e: VerifyFailedError) {
|
||||
gotError = true
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.alibaba.testable.demo
|
||||
|
||||
import com.alibaba.testable.core.annotation.TestableMock
|
||||
import com.alibaba.testable.core.annotation.MockConstructor
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
|
||||
import com.alibaba.testable.core.tool.TestableConst
|
||||
import com.alibaba.testable.core.tool.TestableTool.*
|
||||
import com.alibaba.testable.core.tool.TestableTool.SOURCE_METHOD
|
||||
import com.alibaba.testable.core.tool.TestableTool.TEST_CASE
|
||||
import com.alibaba.testable.demo.model.BlackBox
|
||||
import com.alibaba.testable.demo.model.ColorBox
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@@ -16,32 +17,34 @@ import java.util.concurrent.Executors
|
||||
*/
|
||||
internal class DemoMockTest {
|
||||
|
||||
@TestableMock(targetMethod = TestableConst.CONSTRUCTOR)
|
||||
private val demoMock = DemoMock()
|
||||
|
||||
@MockConstructor
|
||||
private fun createBlackBox(text: String) = BlackBox("mock_$text")
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun innerFunc(self: DemoMock, text: String) = "mock_$text"
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun trim(self: BlackBox) = "trim_string"
|
||||
|
||||
@TestableMock(targetMethod = "substring")
|
||||
@MockMethod(targetMethod = "substring")
|
||||
private fun sub(self: BlackBox, i: Int, j: Int) = "sub_string"
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun startsWith(self: BlackBox, s: String) = false
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun secretBox(ignore: BlackBox): BlackBox {
|
||||
return BlackBox("not_secret_box")
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun createBox(ignore: ColorBox, color: String, box: BlackBox): BlackBox {
|
||||
return BlackBox("White_${box.get()}")
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun callFromDifferentMethod(self: DemoMock): String {
|
||||
return if (TEST_CASE == "should_able_to_get_test_case_name") {
|
||||
"mock_special"
|
||||
@@ -53,7 +56,6 @@ internal class DemoMockTest {
|
||||
}
|
||||
}
|
||||
|
||||
private val demoMock = DemoMock()
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_new_object() {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.alibaba.testable.demo
|
||||
|
||||
import com.alibaba.testable.core.annotation.MockWith
|
||||
import com.alibaba.testable.core.annotation.TestableMock
|
||||
import com.alibaba.testable.core.model.MockDiagnose
|
||||
import com.alibaba.testable.core.tool.TestableConst
|
||||
import com.alibaba.testable.core.annotation.MockConstructor
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.util.*
|
||||
@@ -12,34 +10,34 @@ import java.util.*
|
||||
* 演示模板方法可以被Mock
|
||||
* Demonstrate template method can be mocked
|
||||
*/
|
||||
@MockWith(diagnose = MockDiagnose.ENABLE)
|
||||
internal class DemoTemplateTest {
|
||||
|
||||
private val demoTemplate = DemoTemplate()
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun <T> getList(self: DemoTemplate, value: T): List<T> {
|
||||
return mutableListOf((value.toString() + "_mock_list") as T)
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun <K, V> getMap(self: DemoTemplate, key: K, value: V): Map<K, V> {
|
||||
return mutableMapOf(key to (value.toString() + "_mock_map") as V)
|
||||
}
|
||||
|
||||
@TestableMock(targetMethod = TestableConst.CONSTRUCTOR)
|
||||
@MockConstructor
|
||||
private fun newHashSet(): HashSet<*> {
|
||||
val set = HashSet<Any>()
|
||||
set.add("insert_mock")
|
||||
return set
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private fun <E> add(s: MutableSet<E>, e: E): Boolean {
|
||||
s.add((e.toString() + "_mocked") as E)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun should_able_to_mock_single_template_method() {
|
||||
val res = demoTemplate.singleTemplateMethod()
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.alibaba.testable.demo.util
|
||||
|
||||
import com.alibaba.testable.core.annotation.TestableMock
|
||||
import com.alibaba.testable.core.annotation.MockMethod
|
||||
import com.alibaba.testable.core.matcher.InvokeVerifier.verify
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.io.File
|
||||
|
||||
class PathUtilTest {
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
fun exists(f: File): Boolean {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b" -> true
|
||||
@@ -16,7 +16,7 @@ class PathUtilTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
fun isDirectory(f: File): Boolean {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b/c" -> true
|
||||
@@ -24,12 +24,12 @@ class PathUtilTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
fun delete(f: File): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
fun listFiles(f: File): Array<File>? {
|
||||
return when (f.absolutePath) {
|
||||
"/a/b" -> arrayOf(File("/a/b/c"), File("/a/b/d"))
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div id="app">Loading...</div>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'Testable',
|
||||
name: 'TestableMock',
|
||||
repo: 'https://github.com/alibaba/testable-mock',
|
||||
loadSidebar: "sidebar.md",
|
||||
loadNavbar: "navbar.md",
|
||||
|
||||
@@ -9,4 +9,4 @@ TestableMock简介
|
||||
|
||||
于是,我们开发了`TestableMock`,**一款特立独行的轻量Mock工具**。
|
||||
|
||||

|
||||

|
||||
|
||||
15
docs/zh-cn/doc/comparation.md
Normal file
15
docs/zh-cn/doc/comparation.md
Normal file
@@ -0,0 +1,15 @@
|
||||
主流Mock工具对比
|
||||
---
|
||||
|
||||
除`TestableMock`外,目前主要的Mock工具主要有`Mockito`、`PowerMock`和`JMockit`,基本差异如下:
|
||||
|
||||
| 工具 | 原理 | 最小Mock单元 | 对被Mock方法的限制 | 上手难度 | IDE支持 |
|
||||
| ---- | ---- | ---- | ---- | ---- | ---- |
|
||||
| Mockito | 动态代理 | 类 | 不能Mock私有/静态和构造方法 | **较容易** | **很好** |
|
||||
| PowerMock | 自定义类加载器 | 类 | **任何方法皆可** | 较繁琐 | **较好** |
|
||||
| JMockit | 运行时字节码修改 | 类 | 不能Mock构造方法(new操作符) | 较繁琐 | 一般 |
|
||||
| TestableMock | 运行时字节码修改 | 方法 | **任何方法皆可** | **很容易** | 一般 |
|
||||
|
||||
相比之下,`TestabledMock`的功能与`PowerMock`基本平齐,且极易上手,只需掌握`@MockMethod`注解就可以完成绝大多数任务。
|
||||
|
||||
当前`TestableMock`的主要不足在于,编写Mock方法时IDE尚无法即时提示方法参数是否正确匹配。若发现匹配效果不符合预期,需要通过[自助问题排查](zh-cn/doc/troubleshooting.md)文档提供的方法在运行期进行校验。这个功能未来需要通过扩展主流IDE插件来提供。
|
||||
@@ -13,7 +13,7 @@ public test_case() {
|
||||
}
|
||||
```
|
||||
|
||||
这个用例会检查在执行被测方法`methodToTest()`时,名称是`mockMethod`的Mock方法应当被调用过,且调用时收到的参数值为123和"abc"(假设被Mock的`mockMethod`方法有两个参数)。
|
||||
这个用例会检查在执行被测方法`methodToTest()`时,名称是`mockMethod`的Mock方法是否有被调用过,且调用时收到的参数值是否为`123`和`"abc"`(假设被Mock的`mockMethod`方法有两个参数)。
|
||||
|
||||
除了这种简单校验以外,TestableMock当前已经支持了多种**校验器**,以及能够模糊匹配参数特征的**匹配器**。
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
如今关于私有方法是否应该做单元测试的争论正逐渐消停,开发者的普遍实践已经给出事实答案。通过公有方法间接测私有方法在很多情况下难以进行,开发者们更愿意通过修改方法可见性的办法来让原本私有的方法在测试用例中变得可测。
|
||||
|
||||
此外,在单元测试中时常会需要对被测对象进行特定的成员字段初始化,但有时由于被测类的构造方法限制,使得无法便捷的对这些字段进行赋值。那么,能否在不破坏被测类型封装的情况下,允许单元测试用例内的代码直接访问被测类的私有方法和成员变量呢?TestableMock提供了一种简单的解决方案。
|
||||
此外,在单元测试中时常会需要对被测对象进行特定的成员字段初始化,但有时由于被测类的构造方法限制,使得无法便捷的对这些字段进行赋值。那么,能否在不破坏被测类型封装的情况下,允许单元测试用例内的代码直接访问被测类的私有方法和成员变量呢?TestableMock提供了两种简单的解决方案。
|
||||
|
||||
### 方法一:使用`@EnablePrivateAccess`注解
|
||||
|
||||
只需为测试类添加`@EnablePrivateAccess`注解,即可在测试用例中获得以下增强能力:
|
||||
|
||||
@@ -14,6 +16,19 @@
|
||||
|
||||
访问和修改私有、常量成员时,IDE可能会提示语法有误,但编译器将能够正常运行测试。(使用编译期代码增强,目前仅实现了Java语言的适配)
|
||||
|
||||
若不希望看到IDE的语法错误提醒,或是在非Java语言的JVM工程(譬如Kotlin语言)里,也可以借助`PrivateAccessor`工具类来实现私有成员的访问。
|
||||
效果见`java-demo`示例项目`DemoPrivateAccessTest`测试类中的用例。
|
||||
|
||||
效果见`java-demo`和`kotlin-demo`示例项目`DemoPrivateAccessTest`测试类中的用例。
|
||||
### 方法二:使用`PrivateAccessor`工具类
|
||||
|
||||
若不希望看到IDE的语法错误提醒,或是在非Java语言的JVM工程(譬如Kotlin语言)里,也可以借助`PrivateAccessor`工具类来直接访问私有成员。
|
||||
|
||||
这个类提供了6个静态方法:
|
||||
|
||||
- `PrivateAccessor.get(被测对象, "私有字段名")` ➜ 读取被测类的私有成员
|
||||
- `PrivateAccessor.set(被测对象, "私有字段名", 新的值)` ➜ 修改被测类的私有成员
|
||||
- `PrivateAccessor.invoke(被测对象, "私有方法名", 调用参数..)` ➜ 调用被测类的私有方法
|
||||
- `PrivateAccessor.getStatic(被测类型, "私有字段名")` ➜ 读取被测类的**静态**私有成员
|
||||
- `PrivateAccessor.setStatic(被测类型, "私有字段名", 新的值)` ➜ 修改被测类的**静态**私有成员
|
||||
- `PrivateAccessor.invokeStatic(被测类型, "私有方法名", 调用参数..)` ➜ 调用被测类的**静态**私有方法
|
||||
|
||||
详见`java-demo`和`kotlin-demo`示例项目`DemoPrivateAccessTest`测试类中的用例。
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Release Note
|
||||
|
||||
## 0.4.2
|
||||
- support change javaagent global log level via maven plugin
|
||||
- fix an issue of duplicate test class injection
|
||||
- fix an issue cause multiple method invocation mock fail
|
||||
|
||||
## 0.4.1
|
||||
- deprecate @TestableMock annotation, use @MockMethod and @MockConstructor instead
|
||||
|
||||
## 0.4.0
|
||||
- fix a jvm 9+ compatibility issue cause by default classloader change
|
||||
- fix a conflict issue when testcase name duplicated between different class
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
|
||||
## 在Maven项目中使用
|
||||
|
||||
在项目`pom.xml`文件中,增加`testable-processor`依赖和`maven-surefire-plugin`配置,具体方法如下。
|
||||
在项目`pom.xml`文件中,增加`testable-all`依赖和`maven-surefire-plugin`配置,具体方法如下。
|
||||
|
||||
建议先添加一个标识TestableMock版本的`property`,便于统一管理:
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
<testable.version>0.4.0</testable.version>
|
||||
<testable.version>0.4.2</testable.version>
|
||||
</properties>
|
||||
```
|
||||
|
||||
@@ -25,20 +25,14 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-processor</artifactId>
|
||||
<version>${testable.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-agent</artifactId>
|
||||
<artifactId>testable-all</artifactId>
|
||||
<version>${testable.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
最后在`build`区域的`plugins`列表里添加`maven-surefire-plugin`插件(如果已有此插件则只需添加`<argLine>`部分配置):
|
||||
最后在`build`区域的`plugins`列表里添加`maven-surefire-plugin`插件(如果已包含此插件则只需添加`<argLine>`部分配置):
|
||||
|
||||
```xml
|
||||
<build>
|
||||
@@ -68,8 +62,8 @@
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
testImplementation('com.alibaba.testable:testable-all:0.4.0')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.0')
|
||||
testImplementation('com.alibaba.testable:testable-all:0.4.2')
|
||||
testAnnotationProcessor('com.alibaba.testable:testable-processor:0.4.2')
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@
|
||||
5. ... ...
|
||||
|
||||
|
||||
> 不返回任何值也不产生任何"副作用"的方法没有存在意义。
|
||||
> 不返回任何值也不产生任何"副作用"的方法没有存在的意义。
|
||||
|
||||
这些"副作用"归纳来说可分为两类:**修改外部变量**和**调用外部方法**。
|
||||
这些"副作用"的本质归纳来说可分为两类:**修改外部变量**和**调用外部方法**。
|
||||
|
||||
通过TestableMock的私有字段访问和Mock校验器可以很方便的实现对"副作用"的结果检查。
|
||||
|
||||
#### 修改外部变量的void方法
|
||||
### 1. 修改外部变量的void方法
|
||||
|
||||
例如,下面这个方法会根据输入修改私有成员变量`hashCache`:
|
||||
|
||||
@@ -55,7 +55,7 @@ class DemoTest {
|
||||
}
|
||||
```
|
||||
|
||||
#### 调用外部方法的void方法
|
||||
### 2. 调用外部方法的void方法
|
||||
|
||||
例如,下面这个方法会根据输入打印信息到控制台:
|
||||
|
||||
@@ -70,6 +70,7 @@ class Demo {
|
||||
```
|
||||
|
||||
若要测试此方法,可以利用TestableMock快速Mock掉`System.out.println`方法。在Mock方法体里可以继续执行原调用(相当于并不影响本来方法功能,仅用于做调用记录),也可以直接留空(相当于去除了原方法的副作用)。
|
||||
|
||||
在执行完被测的void类型方法以后,用`InvokeVerifier.verify()`校验传入的打印内容是否符合预期:
|
||||
|
||||
```java
|
||||
@@ -77,7 +78,7 @@ class DemoTest {
|
||||
private Demo demo = new Demo();
|
||||
|
||||
// 拦截`System.out.println`调用
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
public void println(PrintStream ps, String msg) {
|
||||
// 执行原调用
|
||||
ps.println(msg);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
自助问题排查
|
||||
---
|
||||
|
||||
相比Mockito等由开发者手工放置Mock类的做法,TestableMock使用方法名和参数类型匹配自动寻找需Mock的调用。这种机制在带来方便的同时也有可能发生预料之外的Mock替换。
|
||||
相比`Mockito`等由开发者手工放置Mock类的做法,`TestableMock`使用方法名和参数类型匹配自动寻找需Mock的调用。这种机制在带来方便的同时也有可能发生预料之外的Mock替换。
|
||||
|
||||
若要排查Mock相关的问题,只需在测试类上添加`@MockWith`注解,并配置参数`diagnose`值为`MockDiagnose.ENABLE`,在运行测试时就会打印出详细的Mock方法替换过程。
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
快速Mock被测类的任意方法调用
|
||||
---
|
||||
|
||||
相比以往Mock工具以类为粒度的Mock方式,TestableMock允许用户直接定义需要Mock的单个方法,并遵循约定优于配置的原则,按照规则自动在测试运行时替换被测方法中的指定方法调用。
|
||||
相比以往Mock工具以类为粒度的Mock方式,`TestableMock`允许用户直接定义需要Mock的单个方法,并遵循约定优于配置的原则,按照规则自动在测试运行时替换被测方法中的指定方法调用。
|
||||
|
||||
具体的Mock方法定义约定如下:
|
||||
|
||||
#### 1. 覆写任意类的方法调用
|
||||
|
||||
在测试类里定义一个有`@TestableMock`注解的普通方法,使它与需覆写的方法名称、参数、返回值类型完全一致,然后在其参数列表首位再增加一个类型为该方法原本所属对象类型的参数。
|
||||
在测试类里定义一个有`@MockMethod`注解的普通方法,使它与需覆写的方法名称、参数、返回值类型完全一致,然后在其参数列表首位再增加一个类型为该方法原本所属对象类型的参数。
|
||||
|
||||
此时被测类中所有对该需覆写方法的调用,将在单元测试运行时,将自动被替换为对上述自定义Mock方法的调用。
|
||||
|
||||
**注意**:当遇到待覆写方法有重名时,可以将需覆写的方法名写到`@TestableMock`注解的`targetMethod`参数里,这样Mock方法自身就可以随意命名了。
|
||||
**注意**:当遇到待覆写方法有重名时,可以将需覆写的方法名写到`@MockMethod`注解的`targetMethod`参数里,这样Mock方法自身就可以随意命名了。
|
||||
|
||||
例如,被测类中有一处`"anything".substring(1, 2)`调用,我们希望在运行测试的时候将它换成一个固定字符串,则只需在测试类定义如下方法:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
// 调用此方法的对象`"anything"`类型为`String`
|
||||
// 则Mock方法签名在其参数列表首位增加一个类型为`String`的参数(名字随意)
|
||||
// 此参数可用于获得当时的实际调用者的值和上下文
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private String substring(String self, int i, int j) {
|
||||
return "sub_string";
|
||||
}
|
||||
@@ -31,7 +31,7 @@ private String substring(String self, int i, int j) {
|
||||
```java
|
||||
// 使用`targetMethod`指定需Mock的方法名
|
||||
// 此方法本身现在可以随意命名,但方法参数依然需要遵循相同的匹配规则
|
||||
@TestableMock(targetMethod = "substring")
|
||||
@MockMethod(targetMethod = "substring")
|
||||
private String use_any_mock_method_name(String self, int i, int j) {
|
||||
return "sub_string";
|
||||
}
|
||||
@@ -50,7 +50,7 @@ private String use_any_mock_method_name(String self, int i, int j) {
|
||||
```java
|
||||
// 被测类型是`DemoMock`
|
||||
// 因此在定义Mock方法时,在目标方法参数首位加一个类型为`DemoMock`的参数(名字随意)
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private String innerFunc(DemoMock self, String text) {
|
||||
return "mock_" + text;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ private String innerFunc(DemoMock self, String text) {
|
||||
|
||||
#### 3. 覆写任意类的静态方法
|
||||
|
||||
对于静态方法的Mock与普通方法相同。但需要注意的是,对于静态方法,传入Mock方法的第一个参数实际值始终是`null`。
|
||||
对于静态方法的Mock与普通方法相同。但需要注意的是,静态方法的Mock方法被调用时,传入的第一个参数实际值始终是`null`。
|
||||
|
||||
例如,在被测类中调用了`BlackBox`类型中的静态方法`secretBox()`,改方法签名为`BlackBox secretBox()`,则Mock方法如下:
|
||||
|
||||
@@ -68,7 +68,7 @@ private String innerFunc(DemoMock self, String text) {
|
||||
// 目标静态方法定义在`BlackBox`类型中
|
||||
// 在定义Mock方法时,在目标方法参数首位加一个类型为`BlackBox`的参数(名字随意)
|
||||
// 此参数仅用于标识目标类型,实际传入值将始终为`null`
|
||||
@TestableMock
|
||||
@MockMethod
|
||||
private BlackBox secretBox(BlackBox ignore) {
|
||||
return new BlackBox("not_secret_box");
|
||||
}
|
||||
@@ -78,7 +78,7 @@ private BlackBox secretBox(BlackBox ignore) {
|
||||
|
||||
#### 4. 覆写任意类的new操作
|
||||
|
||||
在测试类里定义一个有`@TestableMock`注解的普通方法,将注解的`targetMethod`参数写为"<init>",然后使该方法与要被创建类型的构造函数参数、返回值类型完全一致,方法名称随意。
|
||||
在测试类里定义一个有`@MockContructor`注解的普通方法,使该方法返回值类型为要被创建的对象类型,且方法参数与要Mock的构造函数参数完全一致,方法名称随意。
|
||||
|
||||
此时被测类中所有用`new`创建指定类的操作(并使用了与Mock方法参数一致的构造函数)将被替换为对该自定义方法的调用。
|
||||
|
||||
@@ -86,14 +86,15 @@ private BlackBox secretBox(BlackBox ignore) {
|
||||
|
||||
```java
|
||||
// 要覆写的构造函数签名为`BlackBox(String)`
|
||||
// 无需在Mock方法参数列表增加额外参数,由于使用了`targetMethod`参数,Mock方法的名称随意起
|
||||
// 此处的`TestableConst.CONSTRUCTOR`为`TestableMock`提供的辅助常量,值为"<init>"
|
||||
@TestableMock(targetMethod = TestableConst.CONSTRUCTOR)
|
||||
// 无需在Mock方法参数列表增加额外参数,Mock方法的名称随意起
|
||||
@MockContructor
|
||||
private BlackBox createBlackBox(String text) {
|
||||
return new BlackBox("mock_" + text);
|
||||
}
|
||||
```
|
||||
|
||||
> 也可以依然使用`@MockMethod`注解,并配置`targetMethod`参数值为`"<init>"`,其余同上。效果与使用`@MockContructor`注解相同
|
||||
|
||||
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_new_object()`测试用例。
|
||||
|
||||
#### 5. 识别当前测试用例和调用来源
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
- [自助问题排查](zh-cn/doc/troubleshooting.md)
|
||||
- [Testable Maven插件](zh-cn/doc/use-maven-plugin.md)
|
||||
|
||||
- 其他文档
|
||||
- 技术参考
|
||||
- [主流Mock工具对比](zh-cn/doc/comparation.md)
|
||||
- [Release Note](zh-cn/doc/release-note.md)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.4.2</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-agent</artifactId>
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.alibaba.testable.agent;
|
||||
|
||||
import com.alibaba.testable.agent.transformer.TestableClassTransformer;
|
||||
import com.alibaba.testable.core.util.LogUtil;
|
||||
import com.alibaba.testable.core.model.MockDiagnose;
|
||||
|
||||
import java.lang.instrument.Instrumentation;
|
||||
|
||||
@@ -15,6 +14,8 @@ public class PreMain {
|
||||
private static final String AND = "&";
|
||||
private static final String DEBUG = "debug";
|
||||
private static final String VERBOSE = "verbose";
|
||||
private static final String LOG_LEVEL = "logLevel";
|
||||
private static final String EQUAL = "=";
|
||||
|
||||
public static void premain(String agentArgs, Instrumentation inst) {
|
||||
parseArgs(agentArgs);
|
||||
@@ -26,12 +27,28 @@ public class PreMain {
|
||||
return;
|
||||
}
|
||||
for (String a : args.split(AND)) {
|
||||
if (a.equals(DEBUG)) {
|
||||
LogUtil.setDefaultLevel(LogUtil.LEVEL_DIAGNOSE);
|
||||
} else if (a.equals(VERBOSE)) {
|
||||
LogUtil.setDefaultLevel(LogUtil.LEVEL_VERBOSE);
|
||||
int i = a.indexOf(EQUAL);
|
||||
if (i > 0) {
|
||||
String k = a.substring(0, i);
|
||||
String v = a.substring(i + 1);
|
||||
if (k.equals(LOG_LEVEL)) {
|
||||
setLogLevel(v);
|
||||
}
|
||||
} else {
|
||||
setLogLevel(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean setLogLevel(String level) {
|
||||
if (level.equals(DEBUG)) {
|
||||
LogUtil.setDefaultLevel(LogUtil.LogLevel.LEVEL_DIAGNOSE);
|
||||
return true;
|
||||
} else if (level.equals(VERBOSE)) {
|
||||
LogUtil.setDefaultLevel(LogUtil.LogLevel.LEVEL_VERBOSE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,5 +14,13 @@ public class ConstPool {
|
||||
public static final String FIELD_TARGET_METHOD = "targetMethod";
|
||||
|
||||
public static final String MOCK_WITH = "com.alibaba.testable.core.annotation.MockWith";
|
||||
public static final String MOCK_METHOD = "com.alibaba.testable.core.annotation.MockMethod";
|
||||
public static final String MOCK_CONSTRUCTOR = "com.alibaba.testable.core.annotation.MockConstructor";
|
||||
public static final String TESTABLE_MOCK = "com.alibaba.testable.core.annotation.TestableMock";
|
||||
|
||||
/**
|
||||
* Name of the constructor method
|
||||
*/
|
||||
public static final String CONSTRUCTOR = "<init>";
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import com.alibaba.testable.agent.model.MethodInfo;
|
||||
import com.alibaba.testable.agent.util.BytecodeUtil;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.core.tool.TestableConst;
|
||||
import com.alibaba.testable.core.util.LogUtil;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.*;
|
||||
@@ -40,7 +39,7 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
Set<MethodInfo> memberInjectMethods = new HashSet<MethodInfo>();
|
||||
Set<MethodInfo> newOperatorInjectMethods = new HashSet<MethodInfo>();
|
||||
for (MethodInfo mi : injectMethods) {
|
||||
if (mi.getName().equals(TestableConst.CONSTRUCTOR)) {
|
||||
if (mi.getName().equals(ConstPool.CONSTRUCTOR)) {
|
||||
newOperatorInjectMethods.add(mi);
|
||||
} else {
|
||||
memberInjectMethods.add(mi);
|
||||
@@ -68,8 +67,10 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
instructions = replaceMemberCallOps(cn, mn, memberInjectMethodName, instructions,
|
||||
node.owner, node.getOpcode(), rangeStart, i);
|
||||
i = rangeStart;
|
||||
} else {
|
||||
LogUtil.warn("Potential missed mocking at %s:%s", mn.name, getLineNum(instructions, i));
|
||||
}
|
||||
} else if (TestableConst.CONSTRUCTOR.equals(node.name)) {
|
||||
} else if (ConstPool.CONSTRUCTOR.equals(node.name)) {
|
||||
// it's a new operation
|
||||
String newOperatorInjectMethodName = getNewOperatorInjectMethodName(newOperatorInjectMethods, node);
|
||||
if (newOperatorInjectMethodName != null) {
|
||||
@@ -120,28 +121,9 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
}
|
||||
|
||||
private int getMemberMethodStart(AbstractInsnNode[] instructions, int rangeEnd) {
|
||||
int stackLevel = ClassUtil.getParameterTypes(((MethodInsnNode)instructions[rangeEnd]).desc).size();
|
||||
int stackLevel = getInitialStackLevel((MethodInsnNode)instructions[rangeEnd]);
|
||||
for (int i = rangeEnd - 1; i >= 0; i--) {
|
||||
switch (instructions[i].getOpcode()) {
|
||||
case Opcodes.INVOKESPECIAL:
|
||||
case Opcodes.INVOKEVIRTUAL:
|
||||
case Opcodes.INVOKEINTERFACE:
|
||||
stackLevel += stackEffectOfInvocation(instructions[i]) + 1;
|
||||
if (((MethodInsnNode)instructions[i]).name.equals(TestableConst.CONSTRUCTOR)) {
|
||||
// constructor must be INVOKESPECIAL and implicitly pop 1 more stack
|
||||
stackLevel++;
|
||||
}
|
||||
break;
|
||||
case Opcodes.INVOKESTATIC:
|
||||
case Opcodes.INVOKEDYNAMIC:
|
||||
stackLevel += stackEffectOfInvocation(instructions[i]);
|
||||
break;
|
||||
case -1:
|
||||
// reach LineNumberNode or LabelNode
|
||||
return i + 1;
|
||||
default:
|
||||
stackLevel -= BytecodeUtil.stackEffect(instructions[i].getOpcode());
|
||||
}
|
||||
stackLevel += getStackLevelChange(instructions[i]);
|
||||
if (stackLevel < 0) {
|
||||
return i;
|
||||
}
|
||||
@@ -149,6 +131,38 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int getInitialStackLevel(MethodInsnNode instruction) {
|
||||
int stackLevel = ClassUtil.getParameterTypes((instruction).desc).size();
|
||||
switch (instruction.getOpcode()) {
|
||||
case Opcodes.INVOKESPECIAL:
|
||||
case Opcodes.INVOKEVIRTUAL:
|
||||
case Opcodes.INVOKEINTERFACE:
|
||||
return stackLevel;
|
||||
case Opcodes.INVOKESTATIC:
|
||||
case Opcodes.INVOKEDYNAMIC:
|
||||
return stackLevel - 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int getStackLevelChange(AbstractInsnNode instruction) {
|
||||
switch (instruction.getOpcode()) {
|
||||
case Opcodes.INVOKESPECIAL:
|
||||
case Opcodes.INVOKEVIRTUAL:
|
||||
case Opcodes.INVOKEINTERFACE:
|
||||
return stackEffectOfInvocation(instruction) + 1;
|
||||
case Opcodes.INVOKESTATIC:
|
||||
case Opcodes.INVOKEDYNAMIC:
|
||||
return stackEffectOfInvocation(instruction);
|
||||
case -1:
|
||||
// either LabelNode or LineNumberNode
|
||||
return 0;
|
||||
default:
|
||||
return -BytecodeUtil.stackEffect(instruction.getOpcode());
|
||||
}
|
||||
}
|
||||
|
||||
private int stackEffectOfInvocation(AbstractInsnNode instruction) {
|
||||
String desc = ((MethodInsnNode)instruction).desc;
|
||||
return ClassUtil.getParameterTypes(desc).size() - (ClassUtil.getReturnType(desc).isEmpty() ? 0 : 1);
|
||||
@@ -202,7 +216,7 @@ public class SourceClassHandler extends BaseClassHandler {
|
||||
mn.instructions.remove(instructions[end - 1]);
|
||||
}
|
||||
}
|
||||
// method with @TestableMock will be modified as public access, so INVOKEVIRTUAL is used
|
||||
// method with @MockMethod will be modified as public access, so INVOKEVIRTUAL is used
|
||||
mn.instructions.insertBefore(instructions[end], new MethodInsnNode(INVOKEVIRTUAL, testClassName,
|
||||
substitutionMethod, addFirstParameter(method.desc, ClassUtil.fitCompanionClassName(ownerClass)), false));
|
||||
mn.instructions.remove(instructions[end]);
|
||||
|
||||
@@ -4,12 +4,15 @@ import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import com.alibaba.testable.agent.tool.ImmutablePair;
|
||||
import com.alibaba.testable.agent.util.AnnotationUtil;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.core.tool.TestableConst;
|
||||
import com.alibaba.testable.core.util.LogUtil;
|
||||
import org.objectweb.asm.tree.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassName;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
@@ -35,11 +38,19 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
*/
|
||||
@Override
|
||||
protected void transform(ClassNode cn) {
|
||||
for (MethodNode m : cn.methods) {
|
||||
transformMethod(cn, m);
|
||||
Iterator<FieldNode> iterator = cn.fields.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
if (ConstPool.TESTABLE_INJECT_REF.equals(iterator.next().name)) {
|
||||
// avoid duplicate injection
|
||||
LogUtil.verbose("Duplicate injection found, ignore " + cn.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
cn.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, ConstPool.TESTABLE_INJECT_REF,
|
||||
ClassUtil.toByteCodeClassName(cn.name), null, null));
|
||||
for (MethodNode m : cn.methods) {
|
||||
transformMethod(cn, m);
|
||||
}
|
||||
}
|
||||
|
||||
private void transformMethod(ClassNode cn, MethodNode mn) {
|
||||
@@ -56,7 +67,9 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
for (AnnotationNode n : mn.visibleAnnotations) {
|
||||
visibleAnnotationNames.add(n.desc);
|
||||
}
|
||||
if (visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.TESTABLE_MOCK))) {
|
||||
if (visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.MOCK_METHOD)) ||
|
||||
visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.TESTABLE_MOCK)) ||
|
||||
visibleAnnotationNames.contains(ClassUtil.toByteCodeClassName(ConstPool.MOCK_CONSTRUCTOR))) {
|
||||
mn.access &= ~ACC_PRIVATE;
|
||||
mn.access &= ~ACC_PROTECTED;
|
||||
mn.access |= ACC_PUBLIC;
|
||||
@@ -136,9 +149,12 @@ public class TestClassHandler extends BaseClassHandler {
|
||||
|
||||
private boolean isMockForConstructor(MethodNode mn) {
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_CONSTRUCTOR)) {
|
||||
return true;
|
||||
}
|
||||
String method = AnnotationUtil.getAnnotationParameter
|
||||
(an, ConstPool.FIELD_TARGET_METHOD, null, String.class);
|
||||
if (TestableConst.CONSTRUCTOR.equals(method)) {
|
||||
if (ConstPool.CONSTRUCTOR.equals(method)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.alibaba.testable.agent.model;
|
||||
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
|
||||
/**
|
||||
* Record parameter fetch from @MockWith annotation
|
||||
*
|
||||
* @author flin
|
||||
*/
|
||||
public class CachedMockParameter {
|
||||
|
||||
private final boolean classExist;
|
||||
private final AnnotationNode mockWith;
|
||||
|
||||
private CachedMockParameter(boolean classExist, AnnotationNode mockWith) {
|
||||
this.classExist = classExist;
|
||||
this.mockWith = mockWith;
|
||||
}
|
||||
|
||||
public static CachedMockParameter notExist() {
|
||||
return new CachedMockParameter(false, null);
|
||||
}
|
||||
|
||||
public static CachedMockParameter exist() {
|
||||
return new CachedMockParameter(true, null);
|
||||
}
|
||||
|
||||
public static CachedMockParameter exist(AnnotationNode mockWith) {
|
||||
return new CachedMockParameter(true, mockWith);
|
||||
}
|
||||
|
||||
public boolean isClassExist() {
|
||||
return classExist;
|
||||
}
|
||||
|
||||
public AnnotationNode getMockWith() {
|
||||
return mockWith;
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,10 @@ 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.CachedMockParameter;
|
||||
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.AnnotationUtil;
|
||||
import com.alibaba.testable.agent.util.ClassUtil;
|
||||
import com.alibaba.testable.core.tool.TestableConst;
|
||||
import com.alibaba.testable.core.util.LogUtil;
|
||||
import com.alibaba.testable.core.model.MockDiagnose;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
@@ -30,8 +27,6 @@ import static com.alibaba.testable.agent.util.ClassUtil.toDotSeparateFullClassNa
|
||||
public class TestableClassTransformer implements ClassFileTransformer {
|
||||
|
||||
private static final String FIELD_DIAGNOSE = "diagnose";
|
||||
private final Map<ComparableWeakRef<String>, CachedMockParameter> loadedClass =
|
||||
new WeakHashMap<ComparableWeakRef<String>, CachedMockParameter>();
|
||||
|
||||
/**
|
||||
* Just avoid spend time to scan those surely non-user classes
|
||||
@@ -44,7 +39,7 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
|
||||
ProtectionDomain protectionDomain, byte[] classFileBuffer) {
|
||||
if (isSystemClass(className) || loadedClass.containsKey(new ComparableWeakRef<String>(className))) {
|
||||
if (isSystemClass(className)) {
|
||||
// Ignore system class and reloaded class
|
||||
LogUtil.verbose("Ignore class: " + (className == null ? "<lambda>" : className));
|
||||
return null;
|
||||
@@ -117,32 +112,40 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
return;
|
||||
}
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.TESTABLE_MOCK)) {
|
||||
String targetClass = ClassUtil.toSlashSeparateFullClassName(methodDescPair.left);
|
||||
String fullClassName = toDotSeparateFullClassName(an.desc);
|
||||
if (fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
|
||||
addMockConstructor(cn, methodInfos, mn);
|
||||
} else if (fullClassName.equals(ConstPool.MOCK_METHOD) ||
|
||||
fullClassName.equals(ConstPool.TESTABLE_MOCK)) {
|
||||
String targetMethod = AnnotationUtil.getAnnotationParameter(
|
||||
an, ConstPool.FIELD_TARGET_METHOD, mn.name, String.class);
|
||||
if (targetMethod.equals(TestableConst.CONSTRUCTOR)) {
|
||||
String sourceClassName = ClassUtil.getSourceClassName(cn.name);
|
||||
methodInfos.add(new MethodInfo(sourceClassName, targetMethod, mn.name, mn.desc));
|
||||
if (targetMethod.equals(ConstPool.CONSTRUCTOR)) {
|
||||
addMockConstructor(cn, methodInfos, mn);
|
||||
} else {
|
||||
methodInfos.add(new MethodInfo(targetClass, targetMethod, mn.name, methodDescPair.right));
|
||||
addMockMethod(methodInfos, mn, methodDescPair, targetMethod);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addMockMethod(List<MethodInfo> methodInfos, MethodNode mn,
|
||||
ImmutablePair<String, String> methodDescPair, String targetMethod) {
|
||||
String targetClass = ClassUtil.toSlashSeparateFullClassName(methodDescPair.left);
|
||||
methodInfos.add(new MethodInfo(targetClass, targetMethod, mn.name, methodDescPair.right));
|
||||
}
|
||||
|
||||
private void addMockConstructor(ClassNode cn, List<MethodInfo> methodInfos, MethodNode mn) {
|
||||
String sourceClassName = ClassUtil.getSourceClassName(cn.name);
|
||||
methodInfos.add(new MethodInfo(sourceClassName, ConstPool.CONSTRUCTOR, mn.name, mn.desc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether any method in specified class has specified annotation
|
||||
* @param className class that need to explore
|
||||
* @return found annotation or not
|
||||
*/
|
||||
private boolean hasMockAnnotation(String className) {
|
||||
CachedMockParameter cache = loadedClass.get(new ComparableWeakRef<String>(className));
|
||||
if (cache != null) {
|
||||
setupMockContext(cache.getMockWith());
|
||||
return cache.isClassExist();
|
||||
}
|
||||
try {
|
||||
ClassNode cn = new ClassNode();
|
||||
new ClassReader(className).accept(cn, 0);
|
||||
@@ -150,7 +153,6 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
for (AnnotationNode an : cn.visibleAnnotations) {
|
||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.MOCK_WITH)) {
|
||||
setupMockContext(an);
|
||||
loadedClass.put(new ComparableWeakRef<String>(className), CachedMockParameter.exist(an));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -158,8 +160,10 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
for (MethodNode mn : cn.methods) {
|
||||
if (mn.visibleAnnotations != null) {
|
||||
for (AnnotationNode an : mn.visibleAnnotations) {
|
||||
if (toDotSeparateFullClassName(an.desc).equals(ConstPool.TESTABLE_MOCK)) {
|
||||
loadedClass.put(new ComparableWeakRef<String>(className), CachedMockParameter.exist());
|
||||
String fullClassName = toDotSeparateFullClassName(an.desc);
|
||||
if (fullClassName.equals(ConstPool.MOCK_METHOD) ||
|
||||
fullClassName.equals(ConstPool.TESTABLE_MOCK) ||
|
||||
fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -169,7 +173,6 @@ public class TestableClassTransformer implements ClassFileTransformer {
|
||||
// Usually class not found, return without record
|
||||
return false;
|
||||
}
|
||||
loadedClass.put(new ComparableWeakRef<String>(className), CachedMockParameter.notExist());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package com.alibaba.testable.agent.util;
|
||||
|
||||
import com.alibaba.testable.agent.constant.ConstPool;
|
||||
import com.alibaba.testable.agent.tool.ComparableWeakRef;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.4.2</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-all</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.4.2</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-core</artifactId>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.alibaba.testable.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Mark method as mock constructor
|
||||
*
|
||||
* @author flin
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface MockConstructor {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.alibaba.testable.core.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Mark method as mock method
|
||||
*
|
||||
* @author flin
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface MockMethod {
|
||||
|
||||
/**
|
||||
* mock specified method instead of method with same name
|
||||
* @return target method name
|
||||
*/
|
||||
String targetMethod() default "";
|
||||
|
||||
}
|
||||
@@ -3,13 +3,15 @@ package com.alibaba.testable.core.annotation;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Use marked method to replace the ones in source class
|
||||
* Mark method as mock method
|
||||
* @deprecated will be remove in v0.5.0, use @MockMethod or @MockConstructor instead
|
||||
*
|
||||
* @author flin
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
@Deprecated
|
||||
public @interface TestableMock {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.alibaba.testable.core.tool;
|
||||
|
||||
/**
|
||||
* @author flin
|
||||
*/
|
||||
public class TestableConst {
|
||||
|
||||
/**
|
||||
* Name of the constructor method
|
||||
*/
|
||||
public static final String CONSTRUCTOR = "<init>";
|
||||
|
||||
}
|
||||
@@ -5,43 +5,62 @@ package com.alibaba.testable.core.util;
|
||||
*/
|
||||
public class LogUtil {
|
||||
|
||||
public static final int LEVEL_ERROR = 0;
|
||||
public static final int LEVEL_WARN = 1;
|
||||
public static final int LEVEL_DIAGNOSE = 2;
|
||||
public static final int LEVEL_VERBOSE = 3;
|
||||
public enum LogLevel {
|
||||
/**
|
||||
* Mute
|
||||
*/
|
||||
LEVEL_MUTE(0),
|
||||
/**
|
||||
* Warn only
|
||||
*/
|
||||
LEVEL_WARN(1),
|
||||
/**
|
||||
* Show diagnose messages
|
||||
*/
|
||||
LEVEL_DIAGNOSE(2),
|
||||
/**
|
||||
* Show detail progress logs
|
||||
*/
|
||||
LEVEL_VERBOSE(3);
|
||||
|
||||
private static int defaultLogLevel = LEVEL_WARN;
|
||||
private static int level;
|
||||
int level;
|
||||
LogLevel(int l) {
|
||||
level = l;
|
||||
}
|
||||
}
|
||||
|
||||
private static LogLevel defaultLogLevel = LogLevel.LEVEL_WARN;
|
||||
private static LogLevel currentLogLevel = LogLevel.LEVEL_WARN;
|
||||
|
||||
public static void verbose(String msg, Object... args) {
|
||||
if (level >= LEVEL_VERBOSE) {
|
||||
if (currentLogLevel.level >= LogLevel.LEVEL_VERBOSE.level) {
|
||||
System.out.println(String.format("[VERBOSE] " + msg, args));
|
||||
}
|
||||
}
|
||||
|
||||
public static void diagnose(String msg, Object... args) {
|
||||
if (level >= LEVEL_DIAGNOSE) {
|
||||
if (currentLogLevel.level >= LogLevel.LEVEL_DIAGNOSE.level) {
|
||||
System.out.println(String.format("[DIAGNOSE] " + msg, args));
|
||||
}
|
||||
}
|
||||
|
||||
public static void warn(String msg, Object... args) {
|
||||
if (level >= LEVEL_WARN) {
|
||||
if (currentLogLevel.level >= LogLevel.LEVEL_WARN.level) {
|
||||
System.out.println(String.format("[WARN] " + msg, args));
|
||||
}
|
||||
}
|
||||
|
||||
public static void enableDiagnose(boolean enable) {
|
||||
level = enable ? LEVEL_DIAGNOSE : LEVEL_ERROR;
|
||||
currentLogLevel = enable ? LogLevel.LEVEL_DIAGNOSE : LogLevel.LEVEL_MUTE;
|
||||
}
|
||||
|
||||
public static void setDefaultLevel(int level) {
|
||||
public static void setDefaultLevel(LogLevel level) {
|
||||
defaultLogLevel = level;
|
||||
resetLogLevel();
|
||||
}
|
||||
|
||||
public static void resetLogLevel() {
|
||||
level = defaultLogLevel;
|
||||
currentLogLevel = defaultLogLevel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.4.2</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-maven-plugin</artifactId>
|
||||
|
||||
@@ -24,13 +24,21 @@ public class TestableMojo extends AbstractMojo
|
||||
/**
|
||||
* Maven project.
|
||||
*/
|
||||
@Parameter(property = "project", readonly = true)
|
||||
@Parameter(property = "project", required = true, readonly = true)
|
||||
private MavenProject project;
|
||||
|
||||
/**
|
||||
* Map of plugin artifacts.
|
||||
*/
|
||||
@Parameter(property = "plugin.artifactMap", required = true, readonly = true)
|
||||
private Map<String, Artifact> pluginArtifactMap;
|
||||
|
||||
/**
|
||||
* JavaAgent log level (mute/debug/verbose)
|
||||
*/
|
||||
@Parameter
|
||||
private String logLevel;
|
||||
|
||||
/**
|
||||
* Name of the Testable Agent artifact.
|
||||
*/
|
||||
@@ -57,9 +65,16 @@ public class TestableMojo extends AbstractMojo
|
||||
getLog().error("failed to fetch project properties");
|
||||
return;
|
||||
}
|
||||
String extraArgs = "";
|
||||
if (logLevel != null && !logLevel.isEmpty()) {
|
||||
extraArgs += logLevel;
|
||||
}
|
||||
final String oldArgs = projectProperties.getProperty(testArgsPropertyKey);
|
||||
final String newArgs = (oldArgs == null) ? getAgentJarArgs().trim() : (oldArgs + getAgentJarArgs());
|
||||
String newArgs = (oldArgs == null) ? getAgentJarArgs().trim() : (oldArgs + getAgentJarArgs());
|
||||
getLog().info(testArgsPropertyKey + " set to " + newArgs);
|
||||
if (!extraArgs.isEmpty()) {
|
||||
newArgs += ("=" + extraArgs);
|
||||
}
|
||||
projectProperties.setProperty(testArgsPropertyKey, newArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.4.2</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>testable-parent</name>
|
||||
<description>Unit test enhancement toolkit</description>
|
||||
@@ -42,7 +42,7 @@
|
||||
<plugin.gpg.version>1.6</plugin.gpg.version>
|
||||
<plugin.staging.version>1.6.8</plugin.staging.version>
|
||||
<plugin.maven.version>3.6.0</plugin.maven.version>
|
||||
<testable.version>0.4.0</testable.version>
|
||||
<testable.version>0.4.2</testable.version>
|
||||
</properties>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>com.alibaba.testable</groupId>
|
||||
<artifactId>testable-parent</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<version>0.4.2</version>
|
||||
<relativePath>../testable-parent</relativePath>
|
||||
</parent>
|
||||
<artifactId>testable-processor</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user