update doc about generics type method and add kotlin demo

This commit is contained in:
金戟 2020-12-09 23:37:26 +08:00
parent 857cbac81c
commit 757becc08b
10 changed files with 170 additions and 11 deletions

View File

@ -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() {

View File

@ -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() {

View File

@ -16,6 +16,9 @@ class DemoTemplateTest {
private DemoTemplate demoTemplate = new DemoTemplate();
/* 第一种写法:使用泛型定义 */
/* First solution: use generics type */
@TestableMock
private <T> List<T> getList(DemoTemplate self, T value) {
return new ArrayList<T>() {{ add((T)(value.toString() + "_mock_list")); }};
@ -27,9 +30,9 @@ class DemoTemplateTest {
}
@TestableMock(targetMethod = TestableTool.CONSTRUCTOR)
public HashSet newHashSet() {
HashSet<Object> set = new HashSet<>();
set.add("insert_mock");
private <T> HashSet<T> newHashSet() {
HashSet<T> 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<Object> getList(DemoTemplate self, Object value) {
// return new ArrayList<Object>() {{ add(value.toString() + "_mock_list"); }};
//}
//
//@TestableMock
//private Map<Object, Object> getMap(DemoTemplate self, Object key, Object value) {
// return new HashMap<Object, Object>() {{ put(key, value.toString() + "_mock_map"); }};
//}
//
//@TestableMock(targetMethod = TestableTool.CONSTRUCTOR)
//private HashSet newHashSet() {
// HashSet<Object> 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();

View File

@ -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 <T> getList(value: T): List<T> {
val l: MutableList<T> = ArrayList()
l.add(value)
return l
}
private fun <K, V> getMap(key: K, value: V): Map<K, V> {
val m: MutableMap<K, V> = 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<String> = HashSet()
set.add("world")
return set
}
}

View File

@ -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")

View File

@ -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")

View File

@ -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)

View File

@ -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()

View File

@ -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 <T> getList(self: DemoTemplate, value: T): List<T> {
return mutableListOf((value.toString() + "_mock_list") as T)
}
@TestableMock
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 = TestableTool.CONSTRUCTOR)
private fun newHashSet(): HashSet<*> {
val set = HashSet<Any>()
set.add("insert_mock")
return set
}
@TestableMock
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()
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())
}
}

View File

@ -5,23 +5,37 @@
直接创建被测类对象,然后利用`TestableMock`访问私有成员的能力直接给这些字段赋值即可。
#### 2. 父类变量指向子类对象时如何实现Mock方法
#### 2. `TestableMock`是否能够与其他Mock工具一起使用
`TestableMock`可与其他基于动态代理机制的Mock工具安全的共同使用譬如`Mockito`、`EasyMock`、`MockRunner`等皆属此范畴。
对于其他会修改类加载器或被测类字节码的Mock工具譬如`PowerMock`和`JMockit`,尚无案例证明会与`TestableMock`发生冲突,但从原理来说二者可能存在不兼容风险,请谨慎使用。
#### 3. 父类变量指向子类对象时如何实现Mock方法
在代码中,经常会有使用<u>接口变量或父类变量</u>指向子类实例,调用父类或子类方法的情况。
这时候遵循一个原则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)测试框架可使用。