Unit testing 依赖项反转、猴子补丁,两者都适用于单元测试吗?
这些都是人为设计的示例,大部分都是JavaScript,但问题是语言不可知的,通常集中在单元测试上 代码基 单元测试(猴子补丁样式) 问题Unit testing 依赖项反转、猴子补丁,两者都适用于单元测试吗?,unit-testing,language-agnostic,stub,dependency-inversion,Unit Testing,Language Agnostic,Stub,Dependency Inversion,这些都是人为设计的示例,大部分都是JavaScript,但问题是语言不可知的,通常集中在单元测试上 代码基 单元测试(猴子补丁样式) 问题 测试与实现紧密耦合 猴子补丁在某些语言中可能不可能实现 未测试的副作用依赖项更改不会破坏现有测试(即静默和未修补的依赖项) 单元测试(依赖项反转/注入样式) 我理解依赖项反转/注入、存根、伪造、模拟等概念,但在现实世界中的多级函数调用中还没有遇到它。也就是说,到目前为止,我看到的示例只显示了一个呼叫者和一个被呼叫者 这就是我推断的两个以上的层次: //
- 测试与实现紧密耦合
- 猴子补丁在某些语言中可能不可能实现
- 未测试的副作用依赖项更改不会破坏现有测试(即静默和未修补的依赖项)
// Refactored code
function func1() {
return func2(func3, func4, func5, 7, 4);
}
function func2(dependent1, dependent2, dependent3, param1, param2) {
return param1 + param2 + dependent1(11) + dependent2(dependent3, 14, 2, 8);
}
function func3(param1) {
return param1 + 5;
}
function func4(dependent1, param1, param2, param3) {
return dependent1(6, 1) + param1 + param2 + param3;
}
function func5(param1, param2) {
return param1 + param2;
}
// Tests
function func5_stub(param1, param2) {
return 5;
}
assert(func4(func5_stub, 1, 2, 3) == 11);
问题
- 测试与实现紧密耦合
- 顶级函数中充斥着未使用的参数(这些参数只是向下传递)
- 如何测试最高级别的函数(本例中为func1)?每次反转依赖项时,都会无意中创建另一个级别
/*
*这个函数现在对于单元测试来说是微不足道的。
*/
函数depInvFunc(param1,param2,depFunc1,depFunc2){
//做点什么
var result1=depFunc1(param1);
var result2=depFunc2(param2);
如果(结果1%15==0){
结果1*=4;
}
返回result1+result2;
}
/*
*此函数可以在任何地方使用,而不是使用上述函数
*并且必须始终指定相关参数函数。
*
*此函数不需要测试(也不应该测试),因为它具有
*没有逻辑,它只是一个简单的函数调用。
*
*将这些依赖于包装器的函数定义为配置
*函数(如配置文件)。您的配置没有单元测试,
*您只需自己手动检查它们。
*/
函数wrappedePinvFunc(参数1,参数2){
返回depinfunc(param1,param2,importedFunc1,importedFunc2);
}
静态类型化示例(Java)
DepInvFunc.java:
public class DepInvFunc {
public int doDepInvStuff(String param1, String param2, Dep1 dep1,
Dep2 dep2) {
// do some stuff
int result1 = dep1.doDepStuff(param1);
int result2 = dep2.doDepStuff(param2);
if (result % 15 == 0) {
result1 *= 4;
}
return result1 + result2;
}
}
public class WrappedDepInvFunc {
public int wrappedDoDepInvStuff(String param1, String param2) {
Dep1 dep1 = new Dep1();
Dep2 dep2 = new Dep2();
return DepInvFunc().doDepInvStuff(param1, param2, dep1, dep2);
}
}
WrappeddedPinvFunc.java:
public class DepInvFunc {
public int doDepInvStuff(String param1, String param2, Dep1 dep1,
Dep2 dep2) {
// do some stuff
int result1 = dep1.doDepStuff(param1);
int result2 = dep2.doDepStuff(param2);
if (result % 15 == 0) {
result1 *= 4;
}
return result1 + result2;
}
}
public class WrappedDepInvFunc {
public int wrappedDoDepInvStuff(String param1, String param2) {
Dep1 dep1 = new Dep1();
Dep2 dep2 = new Dep2();
return DepInvFunc().doDepInvStuff(param1, param2, dep1, dep2);
}
}
Dep1.java:
public class Dep1 {
public int doDepStuff(String param1) {
// do stuff
return 5;
}
}
Dep2.java:
public class Dep2 {
public int doDepStuff(String param1) {
// do stuff
return 7;
}
}
因此,这种方法(在使用动态类型语言时)的唯一缺点是,由于您可能间接调用函数,您(和/或您的IDE)可能无法检测到提供给这些间接函数调用的无效参数 当使用静态类型语言的编译时类型检查时,这个问题在很大程度上被克服了 这种方法避免了脆弱且可能不可用的monkey修补的需要,并且不会出现必须将依赖函数的参数从高级函数向下传递到低级函数的问题
Tldr:将所有(或尽可能多的)逻辑放入依赖反转函数(通过依赖注入很容易测试)中,并将它们封装在无逻辑/最小函数(不需要测试)中
我刚刚从以下两个来源获得灵感后想到了这一策略:
- 的容器组件设计模式
/*
*这个函数现在对于单元测试来说是微不足道的。
*/
函数depInvFunc(param1,param2,depFunc1,depFunc2){
//做点什么
var result1=depFunc1(param1);
var result2=depFunc2(param2);
如果(结果1%15==0){
结果1*=4;
}
返回result1+result2;
}
/*
*此函数可以在任何地方使用,而不是使用上述函数
*并且必须始终指定相关参数函数。
*
*此函数不需要测试(也不应该测试),因为它具有
*没有逻辑,它只是一个简单的函数调用。
*
*将这些依赖于包装器的函数定义为配置
*函数(如配置文件)。您的配置没有单元测试,
*您只需自己手动检查它们。
*/
函数wrappedePinvFunc(参数1,参数2){
返回depinfunc(param1,param2,importedFunc1,importedFunc2);
}
静态类型化示例(Java)
DepInvFunc.java:
public class DepInvFunc {
public int doDepInvStuff(String param1, String param2, Dep1 dep1,
Dep2 dep2) {
// do some stuff
int result1 = dep1.doDepStuff(param1);
int result2 = dep2.doDepStuff(param2);
if (result % 15 == 0) {
result1 *= 4;
}
return result1 + result2;
}
}
public class WrappedDepInvFunc {
public int wrappedDoDepInvStuff(String param1, String param2) {
Dep1 dep1 = new Dep1();
Dep2 dep2 = new Dep2();
return DepInvFunc().doDepInvStuff(param1, param2, dep1, dep2);
}
}
WrappeddedPinvFunc.java:
public class DepInvFunc {
public int doDepInvStuff(String param1, String param2, Dep1 dep1,
Dep2 dep2) {
// do some stuff
int result1 = dep1.doDepStuff(param1);
int result2 = dep2.doDepStuff(param2);
if (result % 15 == 0) {
result1 *= 4;
}
return result1 + result2;
}
}
public class WrappedDepInvFunc {
public int wrappedDoDepInvStuff(String param1, String param2) {
Dep1 dep1 = new Dep1();
Dep2 dep2 = new Dep2();
return DepInvFunc().doDepInvStuff(param1, param2, dep1, dep2);
}
}
Dep1.java:
public class Dep1 {
public int doDepStuff(String param1) {
// do stuff
return 5;
}
}
Dep2.java:
public class Dep2 {
public int doDepStuff(String param1) {
// do stuff
return 7;
}
}
因此,这种方法(在使用动态类型语言时)的唯一缺点是,由于您可能间接调用函数,您(和/或您的IDE)可能无法检测到提供给这些间接函数调用的无效参数 当使用静态类型语言的编译时类型检查时,这个问题在很大程度上被克服了 这种方法避免了脆性和潜在的una