将同一对象中具有依赖关系的方法重构为更可测试的方法(PHP)
我目前在我的类中有一个方法,它必须调用其他方法,一些方法在同一个对象中,另一些方法来自其他对象将同一对象中具有依赖关系的方法重构为更可测试的方法(PHP),php,unit-testing,tdd,dependency-injection,inversion-of-control,Php,Unit Testing,Tdd,Dependency Injection,Inversion Of Control,我目前在我的类中有一个方法,它必须调用其他方法,一些方法在同一个对象中,另一些方法来自其他对象 class MyClass { public function myMethod() { $var1 = $this->otherMethod1(); $var2 = $this->otherMethod2(); $var3 = $this->otherMethod3(); $otherObject = new Othe
class MyClass
{
public function myMethod()
{
$var1 = $this->otherMethod1();
$var2 = $this->otherMethod2();
$var3 = $this->otherMethod3();
$otherObject = new OtherClass();
$var4 = $otherObject->someMethod();
# some processing goes on with these 4 variables
# then the method returns something else
return $var5;
}
}
我对整个TDD游戏还不熟悉,但我认为我所理解的更多可测试代码的关键前提是组合、松散耦合和一些依赖注入/控制反转策略
在这种特殊情况下,我如何将一个方法重构成更可测试的东西
我是否将$this
对象引用作为参数传递给该方法,以便轻松模拟/存根协作方法?这是推荐的还是过于夸张了?
class MyClass
{
public function myMethod($self, $other)
{
# $self == $this
$var1 = $self->otherMethod1();
$var2 = $self->otherMethod2();
$var3 = $self->otherMethod3();
$var4 = $other->someMethod();
# ...
return $var5;
}
}
另外,对我来说,依赖关系对于TDD来说是一个非常重要的问题,因为人们必须考虑如何将存根/模拟注入到所述方法中进行测试。大多数TDER是否将DI/IoC作为公共依赖的主要策略?在哪一点上会被夸大?你能给出一些有效的建议吗?我可能错了,但我的印象是,对象/类对于它们的客户机来说应该是黑盒子,对于它们的测试客户机来说也是如此(我认为封装是我要寻找的术语)。这些问题很好。。。首先,我要说的是,我根本不了解JS,但我是一名单元测试人员,并处理过这些问题。我首先想指出,如果您不使用JsUnit,它是存在的 我不会太担心您的方法调用同一类中的其他方法。。。这是必然的。我更担心的是另一个对象的创建,这取决于它有多复杂 例如,如果您正在实例化一个通过网络执行各种操作的类,那么对于一个简单的单元测试来说,这个类太重了。您更愿意做的是模拟对该类的依赖,这样您就可以让对象生成您希望从其在网络上的操作中收到的结果,而不会产生在网络上的开销:网络故障、时间等等 传入另一个对象有点混乱。人们通常使用工厂方法来实例化另一个对象。工厂方法可以根据您是否正在测试(通常通过标志)来决定是否实例化真实对象或模拟对象。事实上,您可能希望使另一个对象成为类的成员,并在构造函数中调用工厂,或者在那里决定是否实例化模拟对象或真实对象。在setup函数或测试用例中,您可以对模拟对象设置特殊条件,以便它返回正确的值
另外,只需确保在同一类中对其他函数进行了测试。。。我希望这有帮助 您可以做几件事: 最好的方法是模拟,这里有一个这样的库: 它应该让你只模拟出你想要的特定功能 如果这不起作用,那么下一个最好的方法是提供
method2
的实现,作为MyClass
类中对象的方法。如果不能直接模拟方法,我发现这是一种更简单的方法:
class MyClass {
function __construct($method2Impl) {
$this->method2Impl = $method2Impl;
}
function method2() {
return $this->method2Imple->call();
}
}
另一个选项是添加一个“正在测试”标志,以便该方法的行为不同。我也不推荐这样做——最终你会有不同的代码路径和它们自己的bug
另一个选择是子类化并覆盖所需的行为。我-真的-不建议这样做,因为您最终会自定义覆盖的mock,使其本身有bug:)
最后,如果您需要模拟一个方法,因为它太复杂了,那么将它移动到自己的对象中并使用组合(基本上使用我上面提到的
method2Impl
技术)可能是一个很好的迹象。看起来这个类的整个想法并不完全正确。在TDD中,您测试的是类,而不是方法。如果一个方法有它自己的责任并提供它自己的(单独的可测试的)功能,那么它应该被移动到一个单独的类中。否则它只会破坏整个OOP封装。特别是它打破了单一责任原则
在您的情况下,我会将测试的方法提取到另一个类中,并将$var1
、$var2
、$var3
和$other
作为依赖项注入。$other
以及测试类所依赖的任何对象都应该被模拟
class TestMyClass extends MyTestFrameworkUnitTestBase{
function testMyClass()
{
$myClass = new MyClass();
$myClass->setVar1('asdf');
$myClass->setVar2(23);
$myClass->setVar3(78);
$otherMock = getMockForClassOther();
$myClass->setOther($otherMock);
$this->assertEquals('result', $myClass->myMethod());
}
}
我使用的基本规则是:如果我想测试某个东西,我应该让它成为一个类。然而,在PHP中并非总是如此。但它在90%的情况下都能在PHP中工作。(根据我的经验)可能,这更多的是违反单一责任原则的问题,而这正导致TDD问题 这是一件好事,这意味着TDD暴露了设计缺陷。故事大概是这样的 如果这些方法不是公开的,只是将代码分解成更易于消化的块,老实说,我不会在意
如果这些方法是公共的,那么你就有问题了。遵循规则,“类实例的任何公共方法必须在任何点都可以调用”。也就是说,如果您需要对方法调用进行某种排序,那么是时候将该类拆分了。方法的效果并不总是直接(或立即)对客户端可见。而且,仅仅因为它给出了正确的结果并不意味着它用了正确的方法。这就是使用模拟的原因——它们可以告诉你是否正确调用了函数或方法。嗯……在我看来,玩一个对象的内部来测试声音是向系统引入更多错误的好方法(Eizenberg眼睛规则)。将一个方法作为黑盒来测试似乎不符合TDD。测试驱动了该方法的设计。mock不仅为您提供了一种适当的方法来隔离测试purpo的方法