假设我想测试一个简单的助手,它将类名称作为参数并进行重定向。使用PHPUnit测试帮助函数
我该如何测试这个功能是否在很多控制器内被调用?我应该测试在整个代码中作为参数传递的每个类名(将它们自己写在提供者函数中)?还是有一个神奇的功能,这对我来说呢?
假设我想测试一个简单的助手,它将类名称作为参数并进行重定向。使用PHPUnit测试帮助函数
我该如何测试这个功能是否在很多控制器内被调用?我应该测试在整个代码中作为参数传递的每个类名(将它们自己写在提供者函数中)?还是有一个神奇的功能,这对我来说呢?
你的问题是为什么依赖注入 - 当完成正确(不是大多数流行的框架如何实现它) - 被吹捧为代码可测试性的终极目的。
要理解为什么,让我们看看“助手函数”和面向类的编程如何让您的控制器难以测试。
class Helpers {
public static function myHelper() {
return 42;
}
}
class MyController {
public function doSomething() {
return Helpers::myHelper() + 100;
}
}
单元测试的整个要点是验证代码的“单元”是否独立工作。如果你不能分离功能,你的测试是没有意义的,因为它的结果可能会受到其他代码行为的影响。这可能会导致统计学家称为类型I和类型II错误:基本上,这意味着您可以获得可能对您说谎的测试结果。
在上面的代码中,助手不能被轻易地模拟,以确定MyController::doSomething
工作在与外界影响完全隔离。为什么不?因为我们不能“嘲笑”帮助器方法的行为,以保证我们的doSomething
方法实际上将100添加到帮助器结果中。我们坚持帮助者的确切行为(返回42)。这是一个正确的面向对象和控制反转完全消除的问题。让我们考虑如何一个例子:
如果MyController
要求对于它的依赖,而不是使用静态辅助功能,它变得微不足道嘲弄外界的影响。试想一下:
interface AnswerMachine {
public function getAnswer();
}
class UltimateAnswerer implements AnswerMachine {
public function getAnswer() {
return 42;
}
}
class MyController {
private $answerer;
public function __construct(AnswerMachine $answerer) {
$this->answerer = $answerer;
}
public function doSomething() {
return $this->answerer->getAnswer() + 100;
}
}
现在,它是平凡简单的测试MyController::doSomething
事实上确实增加100无论它从应答机回来:
// test file
class StubAnswerer implements AnswerMachine {
public function getAnswer() {
return 50;
}
}
$stubAnswer = new StubAnswerer();
$testController = new MyController($stubAnswerer);
assert($testController->doSomething() === 150);
这个例子还演示了如何正确使用接口在你的代码中可以大大简化测试过程。像PHPUnit这样的测试框架可以非常容易地模拟接口定义,以便准确执行您希望它们以测试代码单元的独立功能。
所以我希望这些非常简单的例子说明在测试代码时依赖注入的强大程度。但更重要的是,我希望他们能证明为什么如果你的选择框架使用静态(只是另一个全局名称),单例和辅助函数,你应该保持警惕。
你不能测试每个可能的参数组合到你需要测试的所有功能;它会比你的宇宙生命更长久。所以你使用人类智能(有些人可能称之为作弊;-)。测试一次,在这种情况下,以模拟控制器作为参数。
然后看看你的代码,并问问自己是否有其他传入的对象真的会有不同的行为。对于你描述为“简单帮手”的东西,答案可能不是。但是,如果是的话,怎么样?创建另一个模拟控制器类来模拟不同的行为。例如。这第二个控制器可能没有助手类期望调用的函数。您期望抛出异常。为此创建单元测试。
重复,直到满意。