繁体   English   中英

如何让我的PHPUnit测试更简洁,更长久?

[英]How can I make my PHPUnit tests more succinct, less long?

我正在为我的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);
}

是否有任何语法可以使单元测试更短更容易阅读? 例如,而不是说“这个对象是一个模拟将调用此方法的方法,并且当调用此方法时,它将使用此参数调用一次,并将返回此值”如果我可以,那将是很好的只说once(object->method(argument)) => $returnvalue

你可以设计的类越多,你就可以在单元测试中使用它而不需要模拟,你需要编写的代码就越少。 但是对于上面的例子,我的第一反应是这个方法不需要单元测试,因为它不是真正执行任何逻辑,并且在写入后不会改变。

话虽这么说,假设您需要在此类的其他方法中使用工作流实例,请将创建它的代码提取到新方法中。 这允许您为每个测试模拟该方法,并且在一次测试中只有更长的模拟。

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

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"调用。 默认情况下, 所有方法都将被模拟,这就是您想要的。

还有其他一些Phake库可以PhakePhakeMockery这样的代码行,而有些人喜欢它,但我没有太多的个人经验。

令我觉得有点奇怪的是你使用公共属性设置模拟。 我原以为那些会进入Controllers构造函数。

鉴于那就是你可以做到的方法:

public function testAddCat()
{
    $cat = $this->getMockBuilder('Default_Model_Cat')
        ->disableOriginalConstructor()
        ->getMock();

    $workflow = $this->getMockBuilder('Default_Model_Workflow_Cat')
        ->disableOriginalConstructor()
        ->getMock();
    $workflow->expects($this->once())
        ->method('addCat')
        ->with($cat);

    $controller = new CatController(/*if you need params here pass them!*/);
    // You can use this to avoid mocking the object if you want
    // If your tests are more of a usage doc maybe don't
    $controller->serviceExecutor = "My fake Executor";

    $controller->catWorkflowFactory = $this->getMockBuilder('Factory')
        ->disableOriginalConstructor()
        ->getMock();
    $controller->catWorkflowFactory->expects($this->once())
        ->method('create')
        ->with(array("My fake Executor"))
        ->will($this->returnValue($workflow));

    $controller->addCat($cat);
}

让我们把一些常见的东西放到设置中

只是为了让每个函数都更好看一下,让我们把默认的模拟带入设置。

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);
}

它仍然不是漂亮,但我们将它分成更易于管理的块。

安装程序会创建所有虚假对象,但它们不会执行任何操作(因此它们不会因任何测试而失败,并且安装时间应该是疏忽

而测试则侧重于描述应该发生的事情。


一般来说,我会说“如果使用这个类很复杂,那么测试表明需要做很多事情是件好事”。 如果那是一个问题,可能会改变课程。 生产可以使用它也将很难设置一切正确。 但是很多框架/方法使控制器在这方面“特别”,所以你最了解:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM