diff --git a/demo/java-demo/src/main/java/com/alibaba/demo/association/CookerService.java b/demo/java-demo/src/main/java/com/alibaba/demo/association/CookerService.java index 6829824..bf26026 100644 --- a/demo/java-demo/src/main/java/com/alibaba/demo/association/CookerService.java +++ b/demo/java-demo/src/main/java/com/alibaba/demo/association/CookerService.java @@ -7,19 +7,19 @@ package com.alibaba.demo.association; public class CookerService { private static String hireSandwichCooker() { - return "Sandwich-Cooker"; + return "Real-Sandwich-Cooker"; } private static String hireHamburgerCooker() { - return "Hamburger-Cooker"; + return "Real-Hamburger-Cooker"; } private String cookSandwich() { - return "Cooked-Sandwich"; + return "Real-Sandwich"; } private String cookHamburger() { - return "Cooked-Hamburger"; + return "Real-Hamburger"; } public String prepareSandwich() { diff --git a/demo/java-demo/src/test/java/com/alibaba/demo/association/SellerServiceTest.java b/demo/java-demo/src/test/java/com/alibaba/demo/association/SellerServiceTest.java index ae3c481..5156e86 100644 --- a/demo/java-demo/src/test/java/com/alibaba/demo/association/SellerServiceTest.java +++ b/demo/java-demo/src/test/java/com/alibaba/demo/association/SellerServiceTest.java @@ -15,7 +15,7 @@ class SellerServiceTest { @Test void should_sell_hamburger() { - assertEquals("Hamburger-Cooker & Cooked-Hamburger", sellerService.sellHamburger()); + assertEquals("Real-Hamburger-Cooker & Real-Hamburger", sellerService.sellHamburger()); } } diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/demo/association/CookerService.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/demo/association/CookerService.kt new file mode 100644 index 0000000..ac7c608 --- /dev/null +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/demo/association/CookerService.kt @@ -0,0 +1,34 @@ +package com.alibaba.demo.association + +/** + * 目标类,此类中的一些调用将会被Mock掉 + * Target class, some invocations inside this class will be mocked + */ +class CookerService { + + private fun cookSandwich(): String { + return "Real-Sandwich" + } + + private fun cookHamburger(): String { + return "Real-Hamburger" + } + + fun prepareSandwich(): String { + return hireSandwichCooker() + " & " + cookSandwich() + } + + fun prepareHamburger(): String { + return hireHamburgerCooker() + " & " + cookHamburger() + } + + companion object { + private fun hireSandwichCooker(): String { + return "Real-Sandwich-Cooker" + } + + private fun hireHamburgerCooker(): String { + return "Real-Hamburger-Cooker" + } + } +} diff --git a/demo/kotlin-demo/src/main/kotlin/com/alibaba/demo/association/SellerService.kt b/demo/kotlin-demo/src/main/kotlin/com/alibaba/demo/association/SellerService.kt new file mode 100644 index 0000000..c0d2a9e --- /dev/null +++ b/demo/kotlin-demo/src/main/kotlin/com/alibaba/demo/association/SellerService.kt @@ -0,0 +1,18 @@ +package com.alibaba.demo.association + +/** + * 被测类,会访问`CookerService`里的方法 + * Class to be tested, which will access methods in TargetService class + */ +class SellerService { + + private val cookerService = CookerService() + + fun sellSandwich(): String { + return cookerService.prepareSandwich() + } + + fun sellHamburger(): String { + return cookerService.prepareHamburger() + } +} diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/demo/association/CookerServiceMock.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/demo/association/CookerServiceMock.kt new file mode 100644 index 0000000..084a3f6 --- /dev/null +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/demo/association/CookerServiceMock.kt @@ -0,0 +1,27 @@ +package com.alibaba.demo.association + +import com.alibaba.testable.core.annotation.MockMethod +import com.alibaba.testable.core.model.MockScope + +internal class CookerServiceMock { + + @MockMethod(targetClass = CookerService::class) + private fun cookSandwich(): String { + return "Faked-Sandwich" + } + + @MockMethod(targetClass = CookerService::class, scope = MockScope.ASSOCIATED) + private fun cookHamburger(): String { + return "Faked-Hamburger" + } + + @MockMethod(targetClass = CookerService::class) + fun hireSandwichCooker(): String { + return "Fake-Sandwich-Cooker" + } + + @MockMethod(targetClass = CookerService::class, scope = MockScope.ASSOCIATED) + fun hireHamburgerCooker(): String { + return "Fake-Hamburger-Cooker" + } +} diff --git a/demo/kotlin-demo/src/test/kotlin/com/alibaba/demo/association/SellerServiceTest.kt b/demo/kotlin-demo/src/test/kotlin/com/alibaba/demo/association/SellerServiceTest.kt new file mode 100644 index 0000000..ef7b24f --- /dev/null +++ b/demo/kotlin-demo/src/test/kotlin/com/alibaba/demo/association/SellerServiceTest.kt @@ -0,0 +1,19 @@ +package com.alibaba.demo.association + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +internal class SellerServiceTest { + + private val sellerService = SellerService() + + @Test + fun should_sell_sandwich() { + Assertions.assertEquals("Fake-Sandwich-Cooker & Faked-Sandwich", sellerService.sellSandwich()) + } + + @Test + fun should_sell_hamburger() { + Assertions.assertEquals("Real-Hamburger-Cooker & Real-Hamburger", sellerService.sellHamburger()) + } +} diff --git a/docs/zh-cn/doc/scope-of-mock.md b/docs/zh-cn/doc/scope-of-mock.md index b365a45..d3ca5e2 100644 --- a/docs/zh-cn/doc/scope-of-mock.md +++ b/docs/zh-cn/doc/scope-of-mock.md @@ -6,11 +6,15 @@ Mock的生效范围 - `MockScope.GLOBAL`:该Mock方法将全局生效 - `MockScope.ASSOCIATED`:该Mock方法仅对Mock容器关联测试类中的测试用例生效 -举例来说,`AaaService`和`BbbService`是两个需要被测试的类,在`BbbService`的代码里有个`recordTicket()`调用依赖外部系统。因此在进行单元测试时,开发者在`BbbService`关联的Mock容器里使用`@MockMethod`注解定义了这个调用的替代方法。此时,若该Mock方法的`scope`值为`MockScope.GLOBAL`,则不论是在`AaaServiceTest`测试类还是在`BbbServiceTest`测试类的测试用例,只要直接或间接的执行到这行调用,都会被置换为调用Mock方法。若该Mock方法的`scope`值为`MockScope.ASSOCIATED`,则Mock只对`BbbServiceTest`类中的测试用例生效,而`AaaServiceTest`类中的测试用例在运行过程中执行到了`BbbService`类的相关代码,将会执行`recordTicket()`的原本调用。 - 对于常规项目而言,单元测试里需要被Mock的调用都是由于其中包含了不需要或不便于测试的逻辑,譬如“依赖外部系统”、“包含随机结果”、“执行非常耗时”等等,这类调用在整个单元测试的生命周期里都应该被Mock方法置换,不论调用的发起者是谁。因此`TestableMock`默认所有Mock方法都是全局生效的,即`scope`默认值为`MockScope.GLOBAL`。 -在一些大型项目中,会有“下层模块编写单元测试,上层模块编写端到端集成测试,两者混合在一起运行”的情况,这时候大部分Mock方法都应该使用`MockScope.ASSOCIATED`作为生效范围。针对这种情况,`TestableMock`支持通过`mockScope`运行参数来修改默认的Mock方法生效范围,详见[全局运行参数](zh-cn/doc/javaagent-args.md)文档。 +> 举例来说,`CookerService`和`SellerService`是两个需要被测试的类,假设`CookerService`的代码里的`hireXxx()`和`cookXxx()`方法都需要依赖外部系统。因此在进行单元测试时,开发者在`CookerService`关联的Mock容器里使用`@MockMethod`注解定义了这些调用的替代方法。 +> +> 此时,若该Mock方法的`scope`值为`MockScope.GLOBAL`,则不论是在`SellerServiceTest`测试类还是在`CookerServiceTest`测试类的测试用例,只要直接或间接的执行到这行调用,都会被置换为调用Mock方法。若该Mock方法的`scope`值为`MockScope.ASSOCIATED`,则Mock只对`CookerServiceTest`类中的测试用例生效,而`SellerServiceTest`类中的测试用例在运行过程中执行到了`CookerService`类的相关代码,将会执行原本的调用。 +> +> 参见Java和Kotlin示例中`SellerServiceTest`测试类的用例。 + +在一些大型项目中,会有“下层模块编写单元测试,上层模块编写端到端集成测试,两者混合在一起运行”的情况,这时候大部分Mock方法都应该使用`MockScope.ASSOCIATED`作为生效范围。针对这种情况,`TestableMock`支持通过`mock.scope.default`运行参数来修改默认的Mock方法生效范围,详见[全局运行参数](zh-cn/doc/javaagent-args.md)文档。 > 特别说明。若要Mock静态块里的调用,Mock方法的`scope`必须为`MockScope.GLOBAL`,因为静态块中的代码在程序初始化时就会执行,不属于任何测试用例。典型场景是在使用JNI开发的项目中Mock系统库的加载方法。 > ```java @@ -24,4 +28,4 @@ Mock的生效范围 > private void loadLibrary(String libname) { > System.err.println("loadLibrary " + libname); > } -> ``` \ No newline at end of file +> ```