From b8b4844c3891e7f110bf5a710b8a6bfd00d7ed10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E6=88=9F?= Date: Sat, 21 Nov 2020 22:53:32 +0800 Subject: [PATCH] add testcase for inherition --- .../alibaba/testable/demo/model/BlackBox.java | 23 +++-- .../com/alibaba/testable/demo/model/Box.java | 10 ++- .../alibaba/testable/demo/model/Color.java | 7 ++ .../demo/service/DemoInheritService.java | 60 +++++++++++++ .../demo/service/DemoMockService.java | 9 -- .../demo/service/DemoInheritServiceTest.java | 88 +++++++++++++++++++ .../demo/service/DemoMockServiceTest.java | 13 --- .../alibaba/testable/demo/model/BlackBox.kt | 25 +++--- .../com/alibaba/testable/demo/model/Box.kt | 10 ++- .../com/alibaba/testable/demo/model/Color.kt | 7 ++ .../demo/service/DemoInheritService.kt | 64 ++++++++++++++ .../testable/demo/service/DemoMockService.kt | 11 +-- .../demo/service/DemoInheritServiceTest.kt | 86 ++++++++++++++++++ .../demo/service/DemoMockServiceTest.kt | 13 --- docs/frequency-asked-questions.md | 16 +++- 15 files changed, 368 insertions(+), 74 deletions(-) create mode 100644 demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Color.java create mode 100644 demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoInheritService.java create mode 100644 demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoInheritServiceTest.java create mode 100644 demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Color.kt create mode 100644 demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoInheritService.kt create mode 100644 demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoInheritServiceTest.kt diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/BlackBox.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/BlackBox.java index 699a09c..2071107 100644 --- a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/BlackBox.java +++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/BlackBox.java @@ -1,24 +1,23 @@ package com.alibaba.testable.demo.model; -public class BlackBox implements Box { - - private String data; - - @Override - public void put(String something) { - data = something; - } +public class BlackBox extends Box implements Color { public BlackBox(String data) { this.data = data; } - public String get() { - return data; - } - public static BlackBox secretBox() { return new BlackBox("secret"); } + @Override + public void put(String something) { + data = something; + } + + @Override + public String getColor() { + return "black"; + } + } diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Box.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Box.java index 2778e72..a280f9d 100644 --- a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Box.java +++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Box.java @@ -1,7 +1,13 @@ package com.alibaba.testable.demo.model; -public interface Box { +abstract public class Box { - void put(String something); + protected String data; + + abstract public void put(String something); + + public String get() { + return data; + } } diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Color.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Color.java new file mode 100644 index 0000000..daa5ba2 --- /dev/null +++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/model/Color.java @@ -0,0 +1,7 @@ +package com.alibaba.testable.demo.model; + +public interface Color { + + String getColor(); + +} diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoInheritService.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoInheritService.java new file mode 100644 index 0000000..fdcb246 --- /dev/null +++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoInheritService.java @@ -0,0 +1,60 @@ +package com.alibaba.testable.demo.service; + +import com.alibaba.testable.demo.model.BlackBox; +import com.alibaba.testable.demo.model.Box; +import com.alibaba.testable.demo.model.Color; +import org.springframework.stereotype.Service; + +@Service +public class DemoInheritService { + + /** + * call method overridden by sub class via parent class variable + */ + public Box putIntoBox() { + Box box = new BlackBox(""); + box.put("data"); + return box; + } + + /** + * call method overridden by sub class via sub class variable + */ + public BlackBox putIntoBlackBox() { + BlackBox box = new BlackBox(""); + box.put("data"); + return box; + } + + /** + * call method defined in parent class via parent class variable + */ + public String getFromBox() { + Box box = new BlackBox("data"); + return box.get(); + } + + /** + * call method defined in parent class via sub class variable + */ + public String getFromBlackBox() { + BlackBox box = new BlackBox("data"); + return box.get(); + } + + /** + * call method defined in interface via interface variable + */ + public String getColorViaColor() { + Color color = new BlackBox(""); + return color.getColor(); + } + + /** + * call method defined in interface via sub class variable + */ + public String getColorViaBox() { + BlackBox box = new BlackBox(""); + return box.getColor(); + } +} diff --git a/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoMockService.java b/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoMockService.java index f7a619f..d7468ee 100644 --- a/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoMockService.java +++ b/demo/java-demo/src/main/java/com/alibaba/testable/demo/service/DemoMockService.java @@ -39,15 +39,6 @@ public class DemoMockService { return BlackBox.secretBox(); } - /** - * method with override method invoke - */ - public Box putBox() { - Box box = new BlackBox(""); - box.put("data"); - return box; - } - /** * two methods invoke same private method */ diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoInheritServiceTest.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoInheritServiceTest.java new file mode 100644 index 0000000..6f56b56 --- /dev/null +++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoInheritServiceTest.java @@ -0,0 +1,88 @@ +package com.alibaba.testable.demo.service; + +import com.alibaba.testable.core.annotation.TestableMock; +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.*; + +class DemoInheritServiceTest { + + @TestableMock(targetMethod = "put") + private void put_into_box(Box self, String something) { + self.put("put_" + something + "_into_box"); + } + + @TestableMock(targetMethod = "put") + private void put_into_blackbox(BlackBox self, String something) { + self.put("put_" + something + "_into_blackbox"); + } + + @TestableMock(targetMethod = "get") + private String get_from_box(Box self) { + return "get_from_box"; + } + + @TestableMock(targetMethod = "get") + private String get_from_blackbox(BlackBox self) { + return "get_from_blackbox"; + } + + @TestableMock(targetMethod = "getColor") + private String get_color_from_color(Color self) { + return "color_from_color"; + } + + @TestableMock(targetMethod = "getColor") + private String get_color_from_blackbox(BlackBox self) { + return "color_from_blackbox"; + } + + private DemoInheritService inheritService = new DemoInheritService(); + + @Test + void should_able_to_mock_call_sub_object_method_by_parent_object() throws Exception { + BlackBox box = (BlackBox)inheritService.putIntoBox(); + verify("put_into_box").withTimes(1); + assertEquals("put_data_into_box", box.get()); + } + + @Test + void should_able_to_mock_call_sub_object_method_by_sub_object() throws Exception { + BlackBox box = inheritService.putIntoBlackBox(); + verify("put_into_blackbox").withTimes(1); + assertEquals("put_data_into_blackbox", box.get()); + } + + @Test + void should_able_to_mock_call_parent_object_method_by_parent_object() throws Exception { + String content = inheritService.getFromBox(); + verify("get_from_box").withTimes(1); + assertEquals("get_from_box", content); + } + + @Test + void should_able_to_mock_call_parent_object_method_by_sub_object() throws Exception { + String content = inheritService.getFromBlackBox(); + verify("get_from_blackbox").withTimes(1); + assertEquals("get_from_blackbox", content); + } + + @Test + void should_able_to_mock_call_interface_method_by_interface_object() throws Exception { + String color = inheritService.getColorViaColor(); + verify("get_color_from_color").withTimes(1); + assertEquals("color_from_color", color); + } + + @Test + void should_able_to_mock_call_interface_method_by_sub_class_object() throws Exception { + String color = inheritService.getColorViaBox(); + verify("get_color_from_blackbox").withTimes(1); + assertEquals("color_from_blackbox", color); + } + +} diff --git a/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoMockServiceTest.java b/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoMockServiceTest.java index 431338b..7590ebd 100644 --- a/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoMockServiceTest.java +++ b/demo/java-demo/src/test/java/com/alibaba/testable/demo/service/DemoMockServiceTest.java @@ -2,7 +2,6 @@ package com.alibaba.testable.demo.service; import com.alibaba.testable.core.annotation.TestableMock; import com.alibaba.testable.demo.model.BlackBox; -import com.alibaba.testable.demo.model.Box; import org.junit.jupiter.api.Test; import java.util.concurrent.Executors; @@ -43,11 +42,6 @@ class DemoMockServiceTest { return new BlackBox("not_secret_box"); } - @TestableMock - private void put(Box self, String something) { - self.put("put_" + something + "_mocked"); - } - @TestableMock private String callFromDifferentMethod(DemoMockService self) { if (TEST_CASE.equals("should_able_to_get_test_case_name")) { @@ -87,13 +81,6 @@ class DemoMockServiceTest { verify("secretBox").withTimes(1); } - @Test - void should_able_to_mock_override_method() throws Exception { - BlackBox box = (BlackBox)demoService.putBox(); - verify("put").withTimes(1); - assertEquals("put_data_mocked", box.get()); - } - @Test void should_able_to_get_source_method_name() throws Exception { // synchronous diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/BlackBox.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/BlackBox.kt index e6be1be..c8dec9b 100644 --- a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/BlackBox.kt +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/BlackBox.kt @@ -1,26 +1,29 @@ package com.alibaba.testable.demo.model -class BlackBox(private var data: String) : Box { +class BlackBox(var input: String) : Box(), Color { + + init { + this.content = input + } override fun put(something: String) { - data = something + content = something } - fun get(): String { - return data + override val color: String + get() = "black" + + fun trim(): String? { + return content?.trim() } - fun trim(): String { - return data.trim() - } - - fun substring(from: Int, to: Int): String { - return data.substring(from, to) + fun substring(from: Int, to: Int): String? { + return content?.substring(from, to) } fun startsWith(prefix: String): Boolean { - return data.startsWith(prefix) + return content?.startsWith(prefix) == true } companion object { diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Box.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Box.kt index d88da88..e7ccd2b 100644 --- a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Box.kt +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Box.kt @@ -1,7 +1,13 @@ package com.alibaba.testable.demo.model -interface Box { +abstract class Box { - fun put(something: String) + var content: String? = null + + abstract fun put(something: String) + + open fun get(): String? { + return content + } } diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Color.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Color.kt new file mode 100644 index 0000000..ab52230 --- /dev/null +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/model/Color.kt @@ -0,0 +1,7 @@ +package com.alibaba.testable.demo.model + +interface Color { + + val color: String + +} diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoInheritService.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoInheritService.kt new file mode 100644 index 0000000..64e6ae4 --- /dev/null +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoInheritService.kt @@ -0,0 +1,64 @@ +package com.alibaba.testable.demo.service + +import com.alibaba.testable.demo.model.BlackBox +import com.alibaba.testable.demo.model.Box +import com.alibaba.testable.demo.model.Color +import org.springframework.stereotype.Service + +@Service +class DemoInheritService { + + /** + * call method overridden by sub class via parent class variable + */ + fun putIntoBox(): Box { + val box: Box = BlackBox("") + box.put("data") + return box + } + + /** + * call method overridden by sub class via sub class variable + */ + fun putIntoBlackBox(): BlackBox { + val box = BlackBox("") + box.put("data") + return box + } + + /** + * call method defined in parent class via parent class variable + */ + val fromBox: String? + get() { + val box: Box = BlackBox("data") + return box.get() + } + + /** + * call method defined in parent class via sub class variable + */ + val fromBlackBox: String? + get() { + val box = BlackBox("data") + return box.get() + } + + /** + * call method defined in interface via interface variable + */ + val colorViaColor: String + get() { + val color: Color = BlackBox("") + return color.color + } + + /** + * call method defined in interface via sub class variable + */ + val colorViaBox: String + get() { + val box = BlackBox("") + return box.color + } +} diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoMockService.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoMockService.kt index 845d568..a04ade1 100644 --- a/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoMockService.kt +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/testable/demo/service/DemoMockService.kt @@ -14,7 +14,7 @@ class DemoMockService { /** * method with new operation */ - fun newFunc(): String { + fun newFunc(): String? { return BlackBox("something").get() } @@ -40,15 +40,6 @@ class DemoMockService { return ColorBox.createBox("Red", BlackBox.secretBox()) } - /** - * method with override method invoke - */ - fun putBox(): Box { - val box: Box = BlackBox("") - box.put("data") - return box - } - /** * two methods invoke same private method */ diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoInheritServiceTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoInheritServiceTest.kt new file mode 100644 index 0000000..67e1d24 --- /dev/null +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoInheritServiceTest.kt @@ -0,0 +1,86 @@ +package com.alibaba.testable.demo.service + +import com.alibaba.testable.core.annotation.TestableMock +import com.alibaba.testable.core.matcher.InvokeVerifier +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.Assertions +import org.junit.jupiter.api.Test + +internal class DemoInheritServiceTest { + + @TestableMock(targetMethod = "put") + private fun put_into_box(self: Box, something: String) { + self.put("put_" + something + "_into_box") + } + + @TestableMock(targetMethod = "put") + private fun put_into_blackbox(self: BlackBox, something: String) { + self.put("put_" + something + "_into_blackbox") + } + + @TestableMock(targetMethod = "get") + private fun get_from_box(self: Box): String { + return "get_from_box" + } + + @TestableMock(targetMethod = "get") + private fun get_from_blackbox(self: BlackBox): String { + return "get_from_blackbox" + } + + @TestableMock(targetMethod = "getColor") + private fun get_color_from_color(self: Color): String { + return "color_from_color" + } + + @TestableMock(targetMethod = "getColor") + private fun get_color_from_blackbox(self: BlackBox): String { + return "color_from_blackbox" + } + + private val inheritService = DemoInheritService() + + @Test + fun should_able_to_mock_call_sub_object_method_by_parent_object() { + val box = inheritService.putIntoBox() as BlackBox + InvokeVerifier.verify("put_into_box").withTimes(1) + Assertions.assertEquals("put_data_into_box", box.get()) + } + + @Test + fun should_able_to_mock_call_sub_object_method_by_sub_object() { + val box = inheritService.putIntoBlackBox() + InvokeVerifier.verify("put_into_blackbox").withTimes(1) + Assertions.assertEquals("put_data_into_blackbox", box.get()) + } + + @Test + fun should_able_to_mock_call_parent_object_method_by_parent_object() { + val content = inheritService.fromBox + InvokeVerifier.verify("get_from_box").withTimes(1) + Assertions.assertEquals("get_from_box", content) + } + + @Test + fun should_able_to_mock_call_parent_object_method_by_sub_object() { + val content = inheritService.fromBlackBox + InvokeVerifier.verify("get_from_blackbox").withTimes(1) + Assertions.assertEquals("get_from_blackbox", content) + } + + @Test + fun should_able_to_mock_call_interface_method_by_interface_object() { + val color = inheritService.colorViaColor + InvokeVerifier.verify("get_color_from_color").withTimes(1) + Assertions.assertEquals("color_from_color", color) + } + + @Test + fun should_able_to_mock_call_interface_method_by_sub_class_object() { + val color = inheritService.colorViaBox + InvokeVerifier.verify("get_color_from_blackbox").withTimes(1) + Assertions.assertEquals("color_from_blackbox", color) + } +} diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoMockServiceTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoMockServiceTest.kt index 936684c..898f4a5 100644 --- a/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoMockServiceTest.kt +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/testable/demo/service/DemoMockServiceTest.kt @@ -4,7 +4,6 @@ import com.alibaba.testable.core.annotation.TestableMock import com.alibaba.testable.core.matcher.InvokeVerifier.verify import com.alibaba.testable.core.tool.TestableTool.* import com.alibaba.testable.demo.model.BlackBox -import com.alibaba.testable.demo.model.Box import com.alibaba.testable.demo.model.ColorBox import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -37,11 +36,6 @@ internal class DemoMockServiceTest { return BlackBox("White_${box.get()}") } - @TestableMock - private fun put(self: Box, something: String) { - self.put("put_" + something + "_mocked") - } - @TestableMock private fun callFromDifferentMethod(self: DemoMockService): String { return if (TEST_CASE == "should_able_to_get_test_case_name") { @@ -83,13 +77,6 @@ internal class DemoMockServiceTest { verify("createBox").withTimes(1) } - @Test - fun should_able_to_mock_override_method() { - val box = demoService.putBox() as BlackBox - verify("put").withTimes(1) - assertEquals("put_data_mocked", box.get()) - } - @Test fun should_able_to_get_source_method_name() { // synchronous diff --git a/docs/frequency-asked-questions.md b/docs/frequency-asked-questions.md index adcec88..be7e6c8 100644 --- a/docs/frequency-asked-questions.md +++ b/docs/frequency-asked-questions.md @@ -5,6 +5,18 @@ 直接创建被测类对象,然后利用`TestableMock`访问私有成员的能力直接给这些字段赋值即可。 -#### 2. 通过接口对象或基类对象指向派生类的实例,调用执行了派生类实现的方法。使用`@TestableMock`定义Mock方法时,首个参数类型应该用 接口/基类 还是 派生类? +#### 2. 父类变量指向子类对象时,如何实现Mock方法? -应该使用 接口/基类 类型,参见`should_able_to_mock_override_method`测试用例。 +在代码中,经常会有使用接口变量或父类变量指向子类实例,调用父类或子类方法的情况。 + +这时候遵循一个原则,Mock方法的首个参数类型**始终与发起调用的变量类型一致**。 + +因此,不论被调用方法来自父类还是子类,也不论子类是否覆写该方法,Mock方法的首个参数类型都应该使用变量自身的接口或父类类型。 + +参见Java和Kotlin示例中`DemoInheritServiceTest`测试类的用例。 + +#### 3. `TestableMock`能否用于Android项目的测试? + +结合[Roboelectric](https://github.com/robolectric/robolectric)测试框架可使用。 + +Android系统的`Dalvik`和`ART`虚拟机采用了与标准JVM不同的字节码体系,会影响`TestableMock`的正常工作。`Roboelectric`框架能在普通JVM虚拟机上运行Android单元测试,其速度比通过Android虚拟机运行单元测试快非常多,当下绝大多数Android App的单元测试都使用了`Roboelectric`框架。