From 757becc08bd06c12aed2fca4ebb065e5a2280c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Wed, 9 Dec 2020 23:37:26 +0800 Subject: [PATCH] update doc about generics type method and add kotlin demo --- .../testable/demo/DemoInheritTest.java | 3 +- .../alibaba/testable/demo/DemoMockTest.java | 3 +- .../testable/demo/DemoTemplateTest.java | 36 ++++++++++- .../com/alibaba/testable/demo/DemoTemplate.kt | 35 ++++++++++ .../alibaba/testable/demo/DemoInheritTest.kt | 4 ++ .../alibaba/testable/demo/DemoMatcherTest.kt | 5 +- .../com/alibaba/testable/demo/DemoMockTest.kt | 4 ++ .../testable/demo/DemoPrivateAccessTest.kt | 5 +- .../alibaba/testable/demo/DemoTemplateTest.kt | 64 +++++++++++++++++++ docs/zh-cn/doc/frequency-asked-questions.md | 22 +++++-- 10 files changed, 170 insertions(+), 11 deletions(-) create mode 100644 demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoTemplate.kt create mode 100644 demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoTemplateTest.kt diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoInheritTest.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoInheritTest.java index baf5a3e..4c50126 100644 --- a/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoInheritTest.java +++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoInheritTest.java @@ -15,6 +15,8 @@ import static org.junit.jupiter.api.Assertions.*; */ class DemoInheritTest { + private DemoInherit demoInherit = new DemoInherit(); + @TestableMock(targetMethod = "put") private void put_into_box(Box self, String something) { self.put("put_" + something + "_into_box"); @@ -45,7 +47,6 @@ class DemoInheritTest { return "color_from_blackbox"; } - private DemoInherit demoInherit = new DemoInherit(); @Test void should_able_to_mock_call_sub_object_method_by_parent_object() { diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoMockTest.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoMockTest.java index 9126ac1..7a1667b 100644 --- a/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoMockTest.java +++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoMockTest.java @@ -17,6 +17,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; */ class DemoMockTest { + private DemoMock demoMock = new DemoMock(); + @TestableMock(targetMethod = CONSTRUCTOR) private BlackBox createBlackBox(String text) { return new BlackBox("mock_" + text); @@ -58,7 +60,6 @@ class DemoMockTest { } } - private DemoMock demoMock = new DemoMock(); @Test void should_able_to_mock_new_object() { diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoTemplateTest.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoTemplateTest.java index 9443f48..27bb2fd 100644 --- a/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoTemplateTest.java +++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/DemoTemplateTest.java @@ -16,6 +16,9 @@ class DemoTemplateTest { private DemoTemplate demoTemplate = new DemoTemplate(); + /* 第一种写法:使用泛型定义 */ + /* First solution: use generics type */ + @TestableMock private List getList(DemoTemplate self, T value) { return new ArrayList() {{ add((T)(value.toString() + "_mock_list")); }}; @@ -27,9 +30,9 @@ class DemoTemplateTest { } @TestableMock(targetMethod = TestableTool.CONSTRUCTOR) - public HashSet newHashSet() { - HashSet set = new HashSet<>(); - set.add("insert_mock"); + private HashSet newHashSet() { + HashSet set = new HashSet<>(); + set.add((T)"insert_mock"); return set; } @@ -39,6 +42,33 @@ class DemoTemplateTest { return true; } + /* 第二种写法:使用Object类型 */ + /* Second solution: use object type */ + + //@TestableMock + //private List getList(DemoTemplate self, Object value) { + // return new ArrayList() {{ add(value.toString() + "_mock_list"); }}; + //} + // + //@TestableMock + //private Map getMap(DemoTemplate self, Object key, Object value) { + // return new HashMap() {{ put(key, value.toString() + "_mock_map"); }}; + //} + // + //@TestableMock(targetMethod = TestableTool.CONSTRUCTOR) + //private HashSet newHashSet() { + // HashSet set = new HashSet<>(); + // set.add("insert_mock"); + // return set; + //} + // + //@TestableMock + //private boolean add(Set s, Object e) { + // s.add(e.toString() + "_mocked"); + // return true; + //} + + @Test void should_able_to_mock_single_template_method() { String res = demoTemplate.singleTemplateMethod(); diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoTemplate.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoTemplate.kt new file mode 100644 index 0000000..74458ba --- /dev/null +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/DemoTemplate.kt @@ -0,0 +1,35 @@ +package com.alibaba.testable.demo + +import java.util.ArrayList +import java.util.HashMap +import java.util.HashSet + +class DemoTemplate { + private fun getList(value: T): List { + val l: MutableList = ArrayList() + l.add(value) + return l + } + + private fun getMap(key: K, value: V): Map { + val m: MutableMap = HashMap() + m[key] = value + return m + } + + fun singleTemplateMethod(): String { + val list = getList("demo") + return list[0] + } + + fun doubleTemplateMethod(): String? { + val map = getMap("hello", "testable") + return map["hello"] + } + + fun newTemplateMethod(): Set<*> { + val set: MutableSet = HashSet() + set.add("world") + return set + } +} diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoInheritTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoInheritTest.kt index 54bc7d2..77501d7 100644 --- a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoInheritTest.kt +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoInheritTest.kt @@ -8,6 +8,10 @@ import com.alibaba.testable.demo.model.Color import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +/** + * 演示父类变量引用子类对象时的Mock场景 + * Demonstrate scenario of mocking method from sub-type object referred by parent-type variable + */ internal class DemoInheritTest { @TestableMock(targetMethod = "put") diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoMatcherTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoMatcherTest.kt index 91b21af..39a6094 100644 --- a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoMatcherTest.kt +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoMatcherTest.kt @@ -8,7 +8,10 @@ import com.alibaba.testable.demo.model.BlackBox import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test - +/** + * 演示Mock方法调用校验器 + * Demonstrate mock method invocation verifier + */ internal class DemoMatcherTest { @TestableMock(targetMethod = "methodToBeMocked") diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoMockTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoMockTest.kt index d798cff..8b92f04 100644 --- a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoMockTest.kt +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoMockTest.kt @@ -9,6 +9,10 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import java.util.concurrent.Executors +/** + * 演示基本的Mock功能 + * Demonstrate basic mock functionality + */ internal class DemoMockTest { @TestableMock(targetMethod = CONSTRUCTOR) diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoPrivateAccessTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoPrivateAccessTest.kt index 177afcc..438c687 100644 --- a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoPrivateAccessTest.kt +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoPrivateAccessTest.kt @@ -4,7 +4,10 @@ import com.alibaba.testable.core.accessor.PrivateAccessor import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test - +/** + * 演示私有成员访问功能 + * Demonstrate private member access functionality + */ internal class DemoPrivateAccessTest { private val demoPrivateAccess = DemoPrivateAccess() diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoTemplateTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoTemplateTest.kt new file mode 100644 index 0000000..0f37974 --- /dev/null +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/DemoTemplateTest.kt @@ -0,0 +1,64 @@ +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.TestableTool +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.* + +/** + * 演示模板方法可以被Mock + * Demonstrate template method can be mocked + */ +@MockWith(diagnose = MockDiagnose.ENABLE) +internal class DemoTemplateTest { + + private val demoTemplate = DemoTemplate() + + @TestableMock + private fun getList(self: DemoTemplate, value: T): List { + return mutableListOf((value.toString() + "_mock_list") as T) + } + + @TestableMock + private fun getMap(self: DemoTemplate, key: K, value: V): Map { + return mutableMapOf(key to (value.toString() + "_mock_map") as V) + } + + @TestableMock(targetMethod = TestableTool.CONSTRUCTOR) + private fun newHashSet(): HashSet<*> { + val set = HashSet() + set.add("insert_mock") + return set + } + + @TestableMock + private fun add(s: MutableSet, 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() + Assertions.assertEquals("demo_mock_list", res) + } + + @Test + fun should_able_to_mock_double_template_method() { + val res = demoTemplate.doubleTemplateMethod() + Assertions.assertEquals("testable_mock_map", res) + } + + @Test + fun should_able_to_mock_new_template_method() { + val res = demoTemplate.newTemplateMethod() + Assertions.assertEquals(2, res.size) + val iterator = res.stream().iterator() + Assertions.assertEquals("insert_mock", iterator.next()) + Assertions.assertEquals("world_mocked", iterator.next()) + } + +} diff --git a/docs/zh-cn/doc/frequency-asked-questions.md b/docs/zh-cn/doc/frequency-asked-questions.md index de1c33f..78e74b7 100644 --- a/docs/zh-cn/doc/frequency-asked-questions.md +++ b/docs/zh-cn/doc/frequency-asked-questions.md @@ -5,23 +5,37 @@ 直接创建被测类对象,然后利用`TestableMock`访问私有成员的能力直接给这些字段赋值即可。 -#### 2. 父类变量指向子类对象时,如何实现Mock方法? +#### 2. `TestableMock`是否能够与其他Mock工具一起使用? + +`TestableMock`可与其他基于动态代理机制的Mock工具安全的共同使用,譬如`Mockito`、`EasyMock`、`MockRunner`等皆属此范畴。 + +对于其他会修改类加载器或被测类字节码的Mock工具,譬如`PowerMock`和`JMockit`,尚无案例证明会与`TestableMock`发生冲突,但从原理来说二者可能存在不兼容风险,请谨慎使用。 + +#### 3. 父类变量指向子类对象时,如何实现Mock方法? 在代码中,经常会有使用接口变量或父类变量指向子类实例,调用父类或子类方法的情况。 这时候遵循一个原则,Mock方法的首个参数类型**始终与发起调用的变量类型一致**。 -因此,不论被调用方法来自父类还是子类,也不论子类是否覆写该方法,Mock方法的首个参数类型都应该使用变量自身的接口或父类类型。 +因此,不论实际被调用方法来自父类还是子类,也不论子类是否覆写该方法。若变量为父类型(或接口类型),则Mock方法的首个参数类型都应该使用相同的父类(或接口)类型。 参见Java和Kotlin示例中`DemoInheritTest`测试类的用例。 -#### 3. 在Kotlin项目对`String`类中的方法进行Mock不生效? +#### 4. 如何Mock对于泛型方法(模板方法)? + +与普通方法的Mock方法相同,直接在Mock方法上使用相同的泛型参数即可。 + +参见Java和Kotlin示例中`DemoTemplateTest`测试类的用例。 + +不过,由于JVM存在泛型擦除机制,对于Java项目也可以直接使用`Object`类型替代泛型参数,见Java版`DemoTemplateTest`测试类中被注释掉的"第二种写法"示例。 + +#### 5. 在Kotlin项目对`String`类中的方法进行Mock不生效? Kotlin语言中的`String`类型实际上是`kotlin.String`,而非`java.lang.String`。但在构建生成自字节码的时候又会被替换为Java的`java.lang.String`类,因此无论将Mock目标写为`kotlin.String`或`java.lang.String`均无法正常匹配到原始的被调用方法。 实际场景中需要对`String`类中的方法进行Mock的场景很少,`TestableMock`暂未对这种情况做特别处理。 -#### 4. `TestableMock`能否用于Android项目的测试? +#### 6. `TestableMock`能否用于Android项目的测试? 结合[Roboelectric](https://github.com/robolectric/robolectric)测试框架可使用。