Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/280.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何使PHPUnit测试更简洁、更短?_Php_Unit Testing_Mocking_Phpunit - Fatal编程技术网

如何使PHPUnit测试更简洁、更短?

如何使PHPUnit测试更简洁、更短?,php,unit-testing,mocking,phpunit,Php,Unit Testing,Mocking,Phpunit,我正在为我的web应用程序编写的PHPUnit测试,其长度和不透明性让我非常头疼。测试中的代码似乎比他们正在测试的代码多出一个数量级 例如,假设我的网站有一个CatController对象,该对象上有以下方法: public function addCat(Default_Model_Cat $cat) { $workflow = $this->catWorkflowFactory->create(array($this->serviceExecutor));

我正在为我的web应用程序编写的PHPUnit测试,其长度和不透明性让我非常头疼。测试中的代码似乎比他们正在测试的代码多出一个数量级

例如,假设我的网站有一个
CatController
对象,该对象上有以下方法:

public function addCat(Default_Model_Cat $cat)
{
    $workflow = $this->catWorkflowFactory->create(array($this->serviceExecutor));
    $workflow->addCat($cat);
}
我必须创建一个单元测试来彻底测试它,它是这样的:

public function testAddCat()
{
    $cat = $this->getMockBuilder('Default_Model_Cat')
        ->disableOriginalConstructor()
        ->getMock();
    $controller = $this->getMockBuilder('CatController')
        ->disableOriginalConstructor()
        ->setMethods(array('none'))
        ->getMock();
    $workflow = $this->getMockBuilder('Default_Model_Workflow_Cat')
        ->setMethods(array('addCat'))
        ->disableOriginalConstructor()
        ->getMock();
    $workflow->expects($this->once())
        ->method('addCat')
        ->with($cat);
    $controller->serviceExecutor = $this->getMockBuilder('ServiceExecutor')
        ->disableOriginalConstructor()
        ->getMock();
    $controller->catWorkflowFactory = $this->getMockBuilder('Factory')
        ->disableOriginalConstructor()
        ->setMethods(array('create'))
        ->getMock();
    $controller->catWorkflowFactory->expects($this->once())
        ->method('create')
        ->with($controller->serviceExecutor)
        ->will($this->returnValue($workflow));
    $controller->addCat($cat);
}
public function addCat(Default_Model_Cat $cat) {
    $this->createWorkflow()->addCat($cat);
}

public function removeCat(Default_Model_Cat $cat) {
    $this->createWorkflow()->removeCat($cat);
}

public function createWorkflow() {
    return $this->catWorkflowFactory->create(array($this->serviceExecutor));
}

有没有什么语法可以让单元测试更短、更容易阅读?例如,与其说“此对象是一个将调用此方法的模拟对象,当对其调用此方法时,它将使用此参数调用一次并返回此值”,不如说“如果我可以说类似于
一次(对象->方法(参数))的话就好了”=>$returnvalue

您越能在单元测试中设计出可用的类,而无需模拟,所需编写的模拟代码就越少。但是对于上面的例子,我的第一反应是,这个方法不需要单元测试,因为它实际上不执行任何逻辑,并且在编写之后不会改变

也就是说,假设您需要此类的其他方法中的工作流实例,请将创建该实例的代码提取到新方法中。这允许您为每个测试模拟该方法,并且在一个测试中只有较长的模拟时间

例如,如果您也有一个
removeCat()
方法,它将如下所示:

public function testAddCat()
{
    $cat = $this->getMockBuilder('Default_Model_Cat')
        ->disableOriginalConstructor()
        ->getMock();
    $controller = $this->getMockBuilder('CatController')
        ->disableOriginalConstructor()
        ->setMethods(array('none'))
        ->getMock();
    $workflow = $this->getMockBuilder('Default_Model_Workflow_Cat')
        ->setMethods(array('addCat'))
        ->disableOriginalConstructor()
        ->getMock();
    $workflow->expects($this->once())
        ->method('addCat')
        ->with($cat);
    $controller->serviceExecutor = $this->getMockBuilder('ServiceExecutor')
        ->disableOriginalConstructor()
        ->getMock();
    $controller->catWorkflowFactory = $this->getMockBuilder('Factory')
        ->disableOriginalConstructor()
        ->setMethods(array('create'))
        ->getMock();
    $controller->catWorkflowFactory->expects($this->once())
        ->method('create')
        ->with($controller->serviceExecutor)
        ->will($this->returnValue($workflow));
    $controller->addCat($cat);
}
public function addCat(Default_Model_Cat $cat) {
    $this->createWorkflow()->addCat($cat);
}

public function removeCat(Default_Model_Cat $cat) {
    $this->createWorkflow()->removeCat($cat);
}

public function createWorkflow() {
    return $this->catWorkflowFactory->create(array($this->serviceExecutor));
}
上述方法非常简短,实际上不需要单元测试,但现在它们会稍微简短一些:

public function testAddCat() {
    $cat = $this->getMockBuilder('Default_Model_Cat')
        ->disableOriginalConstructor()
        ->getMock();
    $controller = $this->getMockBuilder('CatController')
        ->disableOriginalConstructor()
        ->setMethods(array('createWorkflow'))
        ->getMock();
    $workflow = $this->getMockBuilder('Default_Model_Workflow_Cat')
        ->setMethods(array('addCat'))
        ->disableOriginalConstructor()
        ->getMock();
    $controller->expects($this->once())
        ->method('createWorkflow')
        ->will($this->returnValue($workflow));
    $workflow->expects($this->once())
        ->method('addCat')
        ->with($cat);
    $controller->addCat($cat);
}

如果控制器中有许多这样的方法,那么可以在测试用例中创建助手方法来创建模拟。最后,您真的需要禁用模拟上的原始构造函数吗?我自己很少需要这样做。

如果你有一个
CatController
对象,你不应该在测试中模仿它,如果可能的话。你想测试这个类,所以使用真实的类

您可以摆脱所有的
“setMethod”
调用。默认情况下,所有方法都将被模拟,这就是您想要的

还有其他一些mocking库可以减少代码行的mocking,比如一些人喜欢的
Phake
mockry
,但我个人对此没有太多经验

让我感到有点奇怪的是,您使用公共属性设置模拟。我本以为这些会进入控制器构造函数

考虑到这是你可以做到的方法:
让我们把一些常见的东西放到设置中 为了使每个函数更易于阅读,让我们将默认的mock放入设置中

public function setUp() {

    $this->controller = new CatController(/*if you need params, pass them!*/);
    $this->serviceExecutor = $this->getMockBuilder('ServiceExecutor')
        ->disableOriginalConstructor()
        ->getMock();
    $this->controller->serviceExecutor = $this->serviceExecutor;
    $this->cat = $this->getMockBuilder('Default_Model_Cat')
        ->disableOriginalConstructor()
        ->getMock();
    $this->workflow = $this->getMockBuilder('Default_Model_Workflow_Cat')
        ->disableOriginalConstructor()
        ->getMock();
    $this->controller->catWorkflowFactory = $this->getMockBuilder('Factory')
        ->disableOriginalConstructor()
        ->getMock();
}
方法是:

public function testAddCat()
{
    $this->workflow->expects($this->once())
        ->method('addCat')
        ->with($this->cat);

    $this->controller->catWorkflowFactory->expects($this->once())
        ->method('create')
        ->with(array($this->serviceExecutor))
        ->will($this->returnValue($this->workflow));

    $this->controller->addCat($cat);
}
它仍然不是很漂亮,但是我们把它分成了更容易管理的部分

安装程序创建了所有的假对象,但它们什么都不做(因此它们不会通过任何测试,并且安装时间应该是很长的)

而测试的重点是描述应该发生什么



总的来说,我会说“如果使用这个类有那么复杂,也许这是一件好事,测试表明需要做很多事情”。如果这是一个问题,可能会改变类别。使用它的产品可能也很难将所有事情都设置正确。但许多框架/方法使控制器在这方面“特别”,因此您最清楚:)

只是一个想法:我肯定这不适用于其他人,但当我的单元测试看起来越来越荒谬时,我经常发现问题在于我的代码设计/分离不够理想:)我非常不同意“但是对于上面的例子,我的第一反应是,这个方法不需要单元测试,因为它实际上不执行任何逻辑,并且在编写之后不会改变。“它正在向其他方法发送调用。这几乎是一个方法应该做的所有事情,并且应该有一个测试来确保使用正确的参数将正确的方法分派到。除此之外,我也不希望在没有任何特定需求的情况下嘲笑SUT,只是为了简化测试。我认为这是遗留问题的解决办法,而不是新代码的良好实践。我只想谈几点。不过答案不错:)理想情况下,测试将使用真实对象而不是模拟对象,并简单地验证是否添加了类别。但是,这种方法可能出现错误的几件事在前面是非常明显的。任何重构都需要进行大量的测试重构,并获得边际收益。争取100%的代码覆盖率可能会成为一种负担,而不是一种帮助,而简短的方法会减少这种需要。这是我从中得到的一个重要教训。对我来说,“干净代码”这本书是关于拥有小的描述性命名方法,并努力用可读性好的代码实现100%的代码覆盖率。对我来说,模拟SUT总是一个WTF因素,只有很少的用例(例如,在不引入日历对象的情况下解决“当前时间”问题)。在手边的原始示例中,我认为没有必要嘲笑SUT,因此我不会介绍一个。我也没有看到您的方法在重构时是如何减少测试采用时间的。3-4依赖模拟总是需要时间来适应,特别是在难以阅读的情况下。书中的一个租户认为,比他们测试的代码更复杂的测试是一种代码味道,应该避免——要么重构,这样你就不需要测试,要么重新思考你的设计。例如,我不为getter和setter编写测试。正如我所说,我不会为上述方法编写测试,但OP需要一个测试,并在示例中使用了mock。我假设他们需要使用mock,因为真正的类不能在单元测试中构造。David,你最初的回答违背了我学到的关于单元测试的两条基本原则:(1)为所有做任何事情的代码编写单元测试(如果代码什么都做不了,那么就删除它)。(2) 单元测试应该只测试单元,最小的部分