split private access and mock demo class

This commit is contained in:
金戟 2020-10-28 09:12:25 +08:00
parent afecf4ddf5
commit e33c208b8d
15 changed files with 202 additions and 107 deletions

View File

@ -1,4 +1,4 @@
package com.alibaba.testable.demo;
package com.alibaba.testable.demo.model;
public class BlackBox implements Box {

View File

@ -1,4 +1,4 @@
package com.alibaba.testable.demo;
package com.alibaba.testable.demo.model;
public interface Box {

View File

@ -1,32 +1,17 @@
package com.alibaba.testable.demo;
package com.alibaba.testable.demo.service;
import com.alibaba.testable.demo.model.BlackBox;
import com.alibaba.testable.demo.model.Box;
import org.springframework.stereotype.Service;
import sun.net.www.http.HttpClient;
import java.net.URL;
@Service
public class DemoService {
private int count;
public class DemoMockService {
/**
* Target 1 - private method
*/
private String privateFunc(String s, int i) {
return s + " - " + i;
}
/**
* Target 2 - method with private field access
*/
public String privateFieldAccessFunc() {
count += 2;
return String.valueOf(count);
}
/**
* Target 3 - method with new operation
* method with new operation
*/
public String newFunc() {
BlackBox component = new BlackBox("something");
@ -34,28 +19,28 @@ public class DemoService {
}
/**
* Target 4 - method with member method invoke
* method with member method invoke
*/
public String outerFunc(String s) throws Exception {
return "{ \"res\": \"" + innerFunc(s) + "\"}";
}
/**
* Target 5 - method with common method invoke
* method with common method invoke
*/
public String commonFunc() {
return "anything".trim() + "__" + "anything".substring(1, 2) + "__" + "abc".startsWith("ab");
}
/**
* Target 6 - method with static method invoke
* method with static method invoke
*/
public BlackBox getBox() {
return BlackBox.secretBox();
}
/**
* Target 7 - method with override method invoke
* method with override method invoke
*/
public Box putBox() {
Box box = new BlackBox("");
@ -64,7 +49,7 @@ public class DemoService {
}
/**
* Target 8 - two methods invoke same private method
* two methods invoke same private method
*/
public String callerOne() {
return callFromDifferentMethod();

View File

@ -0,0 +1,25 @@
package com.alibaba.testable.demo.service;
import org.springframework.stereotype.Service;
@Service
public class DemoPrivateAccessService {
private int count;
/**
* private method
*/
private String privateFunc(String s, int i) {
return s + " - " + i;
}
/**
* method with private field access
*/
public String privateFieldAccessFunc() {
count += 2;
return String.valueOf(count);
}
}

View File

@ -1,8 +1,8 @@
package com.alibaba.testable.demo;
package com.alibaba.testable.demo.service;
import com.alibaba.testable.core.accessor.PrivateAccessor;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
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;
@ -10,8 +10,7 @@ import java.util.concurrent.Executors;
import static com.alibaba.testable.core.tool.TestableTool.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnablePrivateAccess
class DemoServiceTest {
class DemoMockServiceTest {
@TestableMock(targetMethod = CONSTRUCTOR)
private BlackBox createBlackBox(String text) {
@ -19,7 +18,7 @@ class DemoServiceTest {
}
@TestableMock
private String innerFunc(DemoService self, String text) {
private String innerFunc(DemoMockService self, String text) {
return "mock_" + text;
}
@ -39,7 +38,7 @@ class DemoServiceTest {
}
@TestableMock
private BlackBox secretBox(BlackBox _) {
private BlackBox secretBox(BlackBox ignore) {
return new BlackBox("not_secret_box");
}
@ -49,7 +48,7 @@ class DemoServiceTest {
}
@TestableMock
private String callFromDifferentMethod(DemoService self) {
private String callFromDifferentMethod(DemoMockService self) {
if (TEST_CASE.equals("should_able_to_get_test_case_name")) {
return "mock_special";
}
@ -59,22 +58,7 @@ class DemoServiceTest {
}
}
private DemoService demoService = new DemoService();
@Test
void should_able_to_mock_private_method() throws Exception {
assertEquals("hello - 1", demoService.privateFunc("hello", 1));
assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1));
}
@Test
void should_able_to_mock_private_field() throws Exception {
demoService.count = 2;
assertEquals("4", demoService.privateFieldAccessFunc());
PrivateAccessor.set(demoService, "count", 3);
assertEquals("5", demoService.privateFieldAccessFunc());
assertEquals(new Integer(5), PrivateAccessor.get(demoService, "count"));
}
private DemoMockService demoService = new DemoMockService();
@Test
void should_able_to_mock_new_object() throws Exception {

View File

@ -0,0 +1,29 @@
package com.alibaba.testable.demo.service;
import com.alibaba.testable.core.accessor.PrivateAccessor;
import com.alibaba.testable.processor.annotation.EnablePrivateAccess;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnablePrivateAccess
class DemoPrivateAccessServiceTest {
private DemoPrivateAccessService demoService = new DemoPrivateAccessService();
@Test
void should_able_to_mock_private_method() throws Exception {
assertEquals("hello - 1", demoService.privateFunc("hello", 1));
assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1));
}
@Test
void should_able_to_mock_private_field() throws Exception {
demoService.count = 2;
assertEquals("4", demoService.privateFieldAccessFunc());
PrivateAccessor.set(demoService, "count", 3);
assertEquals("5", demoService.privateFieldAccessFunc());
assertEquals(new Integer(5), PrivateAccessor.get(demoService, "count"));
}
}

View File

@ -1,4 +1,4 @@
package com.alibaba.testable.demo
package com.alibaba.testable.demo.model
class BlackBox(private var data: String) : Box {

View File

@ -1,4 +1,4 @@
package com.alibaba.testable.demo
package com.alibaba.testable.demo.model
interface Box {

View File

@ -1,46 +1,32 @@
package com.alibaba.testable.demo
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.ColorBox
import org.springframework.stereotype.Service
import sun.net.www.http.HttpClient
import java.net.URL
@Service
class DemoService {
private var count = 0
class DemoMockService {
/**
* Target 1 - private method
*/
private fun privateFunc(s: String, i: Int): String {
return "$s - $i"
}
/**
* Target 2 - method with private field access
*/
fun privateFieldAccessFunc(): String {
count += 2
return count.toString()
}
/**
* Target 3 - method with new operation
* method with new operation
*/
fun newFunc(): String {
return BlackBox("something").get()
}
/**
* Target 4 - method with member method invoke
* method with member method invoke
*/
fun outerFunc(s: String): String {
return "{ \"res\": \"" + innerFunc(s) + "\"}"
}
/**
* Target 5 - method with common method invoke
* method with common method invoke
*/
fun commonFunc(): String {
val box = BlackBox("anything")
@ -48,14 +34,14 @@ class DemoService {
}
/**
* Target 6 - method with static method invoke
* method with static method invoke
*/
fun getBox(): BlackBox {
return ColorBox.createBox("Red", BlackBox.secretBox())
}
/**
* Target 7 - method with override method invoke
* method with override method invoke
*/
fun putBox(): Box {
val box: Box = BlackBox("")
@ -64,7 +50,7 @@ class DemoService {
}
/**
* Target 8 - two methods invoke same private method
* two methods invoke same private method
*/
fun callerOne(): String {
return callFromDifferentMethod()

View File

@ -0,0 +1,26 @@
package com.alibaba.testable.demo.service
import org.springframework.stereotype.Service
@Service
class DemoPrivateAccessService {
private var count = 0
/**
* private method
*/
private fun privateFunc(s: String, i: Int): String {
return "$s - $i"
}
/**
* method with private field access
*/
fun privateFieldAccessFunc(): String {
count += 2
return count.toString()
}
}

View File

@ -1,4 +1,4 @@
package com.alibaba.testable.demo
package com.alibaba.testable.demo.util
import java.io.File
import java.io.IOException

View File

@ -1,22 +1,22 @@
package com.alibaba.testable.demo
package com.alibaba.testable.demo.service
import com.alibaba.testable.core.accessor.PrivateAccessor
import com.alibaba.testable.core.annotation.TestableMock
import com.alibaba.testable.core.tool.TestableTool.*
import com.alibaba.testable.processor.annotation.EnablePrivateAccess
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
import java.util.concurrent.Executors
@EnablePrivateAccess
internal class DemoServiceTest {
internal class DemoMockServiceTest {
@TestableMock(targetMethod = CONSTRUCTOR)
private fun createBlackBox(text: String) = BlackBox("mock_$text")
@TestableMock
private fun innerFunc(self: DemoService, text: String) = "mock_$text"
private fun innerFunc(self: DemoMockService, text: String) = "mock_$text"
@TestableMock
private fun trim(self: BlackBox) = "trim_string"
@ -43,7 +43,7 @@ internal class DemoServiceTest {
}
@TestableMock
private fun callFromDifferentMethod(self: DemoService): String {
private fun callFromDifferentMethod(self: DemoMockService): String {
return if (TEST_CASE == "should_able_to_get_test_case_name") {
"mock_special"
} else {
@ -54,19 +54,7 @@ internal class DemoServiceTest {
}
}
private val demoService = DemoService()
@Test
fun should_able_to_mock_private_method() {
assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1))
}
@Test
fun should_able_to_mock_private_field() {
PrivateAccessor.set(demoService, "count", 3)
assertEquals("5", demoService.privateFieldAccessFunc())
assertEquals(5, PrivateAccessor.get(demoService, "count"))
}
private val demoService = DemoMockService()
@Test
fun should_able_to_mock_new_object() {

View File

@ -0,0 +1,23 @@
package com.alibaba.testable.demo.service
import com.alibaba.testable.core.accessor.PrivateAccessor
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
internal class DemoPrivateAccessServiceTest {
private val demoService = DemoPrivateAccessService()
@Test
fun should_able_to_mock_private_method() {
assertEquals("hello - 1", PrivateAccessor.invoke(demoService, "privateFunc", "hello", 1))
}
@Test
fun should_able_to_mock_private_field() {
PrivateAccessor.set(demoService, "count", 3)
assertEquals("5", demoService.privateFieldAccessFunc())
assertEquals(5, PrivateAccessor.get(demoService, "count"))
}
}

View File

@ -1,8 +1,9 @@
package com.alibaba.testable.demo
package com.alibaba.testable.demo.util
import org.junit.jupiter.api.Test
import com.alibaba.testable.core.annotation.TestableMock
import com.alibaba.testable.core.tool.TestableTool.verify
import com.alibaba.testable.demo.util.PathUtil
import java.io.File
class PathUtilTest {

View File

@ -51,7 +51,7 @@
若不希望看到IDE的语法错误提醒或是在基于JVM的非Java语言项目里譬如Kotlin语言也可以借助`PrivateAccessor`工具类来实现私有成员的访问。
效果见示例项目文件`DemoServiceTest.java`中的`should_able_to_mock_private_method()`和`should_able_to_mock_private_field()`测试用例。
效果见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_private_method()`和`should_able_to_mock_private_field()`测试用例。
### Mock被测类的任意方法调用
@ -61,9 +61,22 @@
此时被测类中所有对该需覆写方法的调用将在单元测试运行时将自动被替换为对上述自定义Mock方法的调用。
**注意**当遇到有两个需覆写的方法重名时,可将需覆写的方法名写到`@TestableMock`注解的`targetMethod`参数里此时Mock方法自身就可以随意命名了
**注意**也可以将需覆写的方法名写到`@TestableMock`注解的`targetMethod`参数里这样Mock方法自身就可以随意命名了当遇到重名的待覆写方法时特别有用
示例项目文件`DemoServiceTest.java`中的`should_able_to_mock_common_method()`用例详细展示了这种用法。
例如,被测类中有一处`"anything".substring(1, 2)`调用,我们希望在运行测试的时候将它换成一个固定字符串,则只需在测试类定义如下方法:
```java
// 原方法签名为`String substring(int, int)`
// 调用此方法的对象`"anything"`类型为`String`
// 则Mock方法签名在其参数列表首位增加一个类型为`String`的参数(名字随意)
// 此参数可用于获得当时的实际调用者的值和上下文
@TestableMock
private String substring(String self, int i, int j) {
return "sub_string";
}
```
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_common_method()`测试用例。(由于Kotlin对String类型进行了魔改故Kotlin示例中将被测方法在`BlackBox`类里加了一层封装)
**2. <u>覆写被测类自身的成员方法</u>**
@ -71,13 +84,36 @@
操作方法与前一种情况相同Mock方法的第一个参数类型需与被测类相同即可实现对被测类自身不论是公有或私有成员方法的覆写。
详见示例项目文件`DemoServiceTest.java`中的`should_able_to_mock_member_method()`用例。
例如,被测类中有一个签名为`String innerFunc(String)`的私有方法,我们希望在测试的时候将它替换掉,则只需在测试类定义如下方法:
```java
// 被测类型是`DemoMockService`
// 因此在定义Mock方法时在目标方法参数首位加一个类型为`DemoMockService`的参数(名字随意)
@TestableMock
private String innerFunc(DemoMockService self, String text) {
return "mock_" + text;
}
```
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_member_method()`测试用例。
**3. <u>覆写任意类的静态方法</u>**
对于静态方法的Mock与普通方法相同。但需要注意的是对于静态方法传入Mock方法的第一个参数实际值始终是`null`。
详见示例项目文件`DemoServiceTest.java`中的`should_able_to_mock_static_method()`用例。
例如,在被测类中调用了`BlackBox`类型中的静态方法`secretBox()`,改方法签名为`BlackBox secretBox()`则Mock方法如下
```java
// 目标静态方法定义在`BlackBox`类型中
// 在定义Mock方法时在目标方法参数首位加一个类型为`BlackBox`的参数(名字随意)
// 此参数仅用于标识目标类型,实际传入值将始终为`null`
@TestableMock
private BlackBox secretBox(BlackBox ignore) {
return new BlackBox("not_secret_box");
}
```
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_static_method()`测试用例。
**4. <u>覆写任意类的new操作</u>**
@ -85,10 +121,22 @@
此时被测类中所有用`new`创建指定类的操作并使用了与Mock方法参数一致的构造函数将被替换为对该自定义方法的调用。
详见示例项目文件`DemoServiceTest.java`中的`should_able_to_mock_new_object()`用例。
例如,在被测类中有一处`new BlackBox("something")`调用希望在测试时将它换掉通常是换成Mock对象或换成使用测试参数创建的临时对象则只需定义如下Mock方法
```java
// 要覆写的构造函数签名为`BlackBox(String)`
// 无需在Mock方法参数列表增加额外参数由于使用了`targetMethod`参数Mock方法的名称随意起
// 此处的`CONSTRUCTOR`为`TestableTool`辅助类提供的常量,值为"<init>"
@TestableMock(targetMethod = CONSTRUCTOR)
private BlackBox createBlackBox(String text) {
return new BlackBox("mock_" + text);
}
```
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_mock_new_object()`测试用例。
**5. <u>识别当前测试用例和调用来源</u>**
在Mock方法中可以通过`TestableTool.TEST_CASE`和`TestableTool.SOURCE_METHOD`来识别**当前运行的测试用例名称**和**进入该Mock方法前的被测类方法名称**,从而区分处理不同的调用场景。
详见示例项目文件`DemoServiceTest.java`中的`should_able_to_get_source_method_name()`和`should_able_to_get_test_case_name()`用例。
完整代码示例见`java-demo`和`kotlin-demo`示例项目中的`should_able_to_get_source_method_name()`和`should_able_to_get_test_case_name()`测试用例。