Unit testing PHP-PHPUnit-Handle在模拟对象的try/catch中失败

Unit testing PHP-PHPUnit-Handle在模拟对象的try/catch中失败,unit-testing,phpunit,try-catch,assertion,Unit Testing,Phpunit,Try Catch,Assertion,当断言失败被测试代码“try-catch”块捕获时,我遇到了一些问题,导致结果为假测试结果 更准确地说,我创建了一段代码来重现问题: TestUnit.php namespace MyNamespace; class TestUnit { private $service = null; public function setDependency($service) { $this->service = $service; }

当断言失败被测试代码“try-catch”块捕获时,我遇到了一些问题,导致结果为假测试结果

更准确地说,我创建了一段代码来重现问题:

TestUnit.php

namespace MyNamespace;

class TestUnit
{
    private $service = null;

    public function setDependency($service)
    {
        $this->service = $service;
    }

    public function test($data)
    {
        $data++;
        try {
            $this->service->save($data);
            return 1;
        } catch (\Exception $e) {
            // Here we catch that we cannot save
        }
    }
}
namespace MyNamespace;

class ServiceDependency
{
    public function save($item)
    {
        echo $item;
    }
}
namespace MyNamespace;

class TestUnitTest extends \PHPUnit_Framework_TestCase
{
    public function testMethod()
    {
        $mydata = 5;
        $expectedData = 7;

        $testUnit = new TestUnit();
        $dependentService = $this->createMock(ServiceDependency::class);
        $dependentService->expects($this->any())
            ->method('save')
            ->will($this->returnCallback(function($data) use($expectedData) {
                var_dump($data == $expectedData);
                $this->assertEquals($data, $expectedData, "This should fail!");
                $this->assertTrue(false, "This should fail also!");
            }));
        $testUnit->setDependency($dependentService);
        $testUnit->test($mydata);
    }
}
ServiceDependency.php

namespace MyNamespace;

class TestUnit
{
    private $service = null;

    public function setDependency($service)
    {
        $this->service = $service;
    }

    public function test($data)
    {
        $data++;
        try {
            $this->service->save($data);
            return 1;
        } catch (\Exception $e) {
            // Here we catch that we cannot save
        }
    }
}
namespace MyNamespace;

class ServiceDependency
{
    public function save($item)
    {
        echo $item;
    }
}
namespace MyNamespace;

class TestUnitTest extends \PHPUnit_Framework_TestCase
{
    public function testMethod()
    {
        $mydata = 5;
        $expectedData = 7;

        $testUnit = new TestUnit();
        $dependentService = $this->createMock(ServiceDependency::class);
        $dependentService->expects($this->any())
            ->method('save')
            ->will($this->returnCallback(function($data) use($expectedData) {
                var_dump($data == $expectedData);
                $this->assertEquals($data, $expectedData, "This should fail!");
                $this->assertTrue(false, "This should fail also!");
            }));
        $testUnit->setDependency($dependentService);
        $testUnit->test($mydata);
    }
}
TestUnitTest.php

namespace MyNamespace;

class TestUnit
{
    private $service = null;

    public function setDependency($service)
    {
        $this->service = $service;
    }

    public function test($data)
    {
        $data++;
        try {
            $this->service->save($data);
            return 1;
        } catch (\Exception $e) {
            // Here we catch that we cannot save
        }
    }
}
namespace MyNamespace;

class ServiceDependency
{
    public function save($item)
    {
        echo $item;
    }
}
namespace MyNamespace;

class TestUnitTest extends \PHPUnit_Framework_TestCase
{
    public function testMethod()
    {
        $mydata = 5;
        $expectedData = 7;

        $testUnit = new TestUnit();
        $dependentService = $this->createMock(ServiceDependency::class);
        $dependentService->expects($this->any())
            ->method('save')
            ->will($this->returnCallback(function($data) use($expectedData) {
                var_dump($data == $expectedData);
                $this->assertEquals($data, $expectedData, "This should fail!");
                $this->assertTrue(false, "This should fail also!");
            }));
        $testUnit->setDependency($dependentService);
        $testUnit->test($mydata);
    }
}
测试结果如下:

PHPUnit 5.6.3由塞巴斯蒂安·伯格曼和贡献者撰写

1/1(100%)/--path--/TestUnitTest.php:16:

布尔(假)

时间:211毫秒,内存:10.00MB

OK(1个测试,1个断言)

这里真正发生的是断言抛出一个被TestUnit::test()try/catch块捕获的PHPUnit\u Framework\u ExpectationFailedException


如何绕过这类问题?

我只是在尝试测试一些应该触发事件的方法时遇到了同样的问题。 我正在模拟一个事件调度器/handler/obsever/[insert name here]类,该类包装在调用方的try&catch中

为了解决这个问题,我创建了一个trait(我在多个测试类中使用它),它的工作是存储结果,并在try-and-catch之外断言它们

在我的示例中,我检查事件是否按正确的顺序调用。(为清晰起见,代码已简化)

trait事件stacktrait
{
受保护的$expectedEventStack=[];
受保护的$actualEventStack=[];
/**
*检查是否调用了正确的事件
*@param array$eventStack预期事件的顺序
*@返回无效
*/
受保护的函数createMockEventHandlerWithStack(数组$eventStack)
{
$this->expectedEventStack=$eventStack;
$this->actualEventStack=[];
$event=$this->createMock(\event::class);
$event->expected($this->any())
->方法('dispatch')
->与(
$this->callback(函数($subject)
{
$this->actualEventStack[]=$subject;
返回true;
}
)
)
->威尔($this->returnValue(true));
}
受保护的函数assertAllEventsWhereCalled(?string$message=null)
{
$this->assertEquals($this->expectedEventStack,$this->actualCallerStack,‘事件不符合预期’);
}
}
然后在测试课上

class foobertest扩展了PHPUnit\Framework\TestCase
{
使用事件特征;
从ArrayFireSevents()测试的公共函数
{
$this->createMockEventHandlerWithStack(
[
在创建::事件之前,
创建::事件,
]
);
$foobar=new\foobar();
$foobar->addFromArray([…]);
$this->AssertAllEventsWhere调用();
}
}

(注意,我对单元测试相当陌生,所以可能有更好的方法)

似乎在
returnCallback
语句中忽略了断言。尝试在第一个应该失败的断言之后移动var_转储,不会打印任何内容,但不会计算失败的断言。我认为您应该更改测试策略……即使我强制它执行asertion(通过尝试捕获asertion块,它也不会失败)。失败的断言结果是导致难以测试代码块的异常。任何包含try/catch错误处理的代码都会导致捕获失败的断言。。。我将尝试进一步挖掘这种情况,但很明显,对于这种代码,测试设计更为复杂……从技术上讲,有一种方法可以绕过您提到的问题——在
TestUnit
test
方法的catch块中过滤
$e
,并在需要时重新调用异常(这是因为您强制从产品代码中抛出PHPUnit异常——您在回调中这样做).但你肯定不会喜欢这样的决定,因为它会使你的生产代码依赖于测试代码。因此,正如@Matteo所说,改变你的测试策略是一个更好的方法。你想测试什么行为?目前它并没有真正测试任何东西。这只是一个虚拟示例。“大师”代码要复杂得多,并且与前面介绍的代码不同,但是对于像这样的测试场景,如果依赖对象被try-catch包围。。。