【问题标题】:PHPUnit - Mark test as failed when two related functions failPHPUnit - 当两个相关函数失败时将测试标记为失败
【发布时间】:2017-12-02 22:29:42
【问题描述】:

我最近开始使用 PHPUnit 进行单元测试,并且我构建了测试以检查函数成功和失败时的正确行为。

例如,我可以测试函数connectToDevice(),期望连接成功时返回true,否则返回false。然后我会:

public function testConnectToDeviceSuccess()
{
    $this->assertTrue($this->myObject->connectToDevice());
}

public function testConnectToDeviceFailure()
{
    $this->assertFalse($this->myObject->connectToDevice());
}

目前其中一个功能必然会失败,那么 PHPUnit 会报告我 1 失败。相反,我会将其设置为仅在两个功能都失败时标记测试失败。因此,当 1 成功时,将跳过另一个。

这在 PHPUnit 中是否可以在两个不同的函数中维护这些测试?

【问题讨论】:

  • 两者都失败的唯一时间是你有一个错误,否则其中一个总是对或错。如果您在测试中有错误,则测试不会完成(仅当您没有发现错误时)。
  • @Edwin 问题是我不想有几十个本应失败的失败测试。此外,一个函数可能会抛出一个意外的异常,这将导致两个函数都失败,从而导致测试失败。
  • 那么你做错了什么,无论是在测试中还是在开发中。测试应该始终有效,如果您想测试失败,只需将您的环境设置为您确定不存在连接 => connectToDevice 将是错误的。
  • @Edwin 我的观点是,当一个函数失败时(例如connectToDevice),它应该返回/执行我定义的操作,然后它的测试就会成功。因此,测试与函数成功并没有严格相关,而是与调用函数时发生的返回或动作相关。正如我所说,我是从单元测试开始的,所以我不知道我的论点是否正确。
  • 问题是你在两个测试函数中测试相同的函数connectToDevice而不改变环境。这两个功能是相反的 => 你总是会有一个失败的测试。要更改您在其中一项功能中操作环境的功能,然后您将始终使测试成功。

标签: php unit-testing phpunit


【解决方案1】:

我认为您遇到的问题是您缺少确保正确测试结果的设置。这听起来可能有点奇怪,但考虑一下这种情况

class ConnectedResource
{
    private $connector;

    public function __construct($connector)
    {
        $this->connector = $connector;
    }

    public function connectToDevice()
    {
        try {
            $this->connector->connect();
        } catch (\Exception $e) {
            return false;
        }

        return true;
    }
}

这确实很简单,但最重要的是您要测试两条路径:connect() 成功和异常。

你现在在测试中会做的是

public function testSuccessfulConnectReturnsTrue()
{
    $connector = new Connector(
        // config to make a successful connection
    );
    $myObject = new ConnectedResource($connector);

    $this->assertTrue($myObject->connectToDevice());
}

public function testFailedConnectReturnsFalse()
{
    $connector = new Connector(
        // invalid config that will raise an exception
    );
    $myObject = new ConnectedResource($connector);

    $this->assertFalse($myObject->connectToDevice());
}

现在两个测试都可以工作,因为它们使用不同的连接(一个有效,一个无效)。

另一种可能性是传入一个模拟连接器。换句话说,您可以创建一个虚拟对象,而不是真正的连接器,您可以在其中决定$this->connector->connect() 将返回什么。将其视为类似于“假设我有一个返回 true 的连接器,那么 ConnectedResource 的行为应该像这样”。代码可能如下所示:

public function testSuccessfulConnectReturnsTrue()
{
    $connector = $this->createMock(Connector::class);
    $connector->expect($this->any())
        ->method('connect')
        ->willReturn(true)
    ;
    $myObject = new ConnectedResource($connector);

    $this->assertTrue($myObject->connectToDevice());
}

对于失败场景,它看起来像这样:

public function testFailedConnectReturnsFalse()
{
    $connector = $this->createMock(Connector::class);
    $connector->expect($this->any())
        ->method('connect')
        ->willReturn($this->throwException(new Exception))
    ;
    $myObject = new ConnectedResource($connector);

    $this->assertTrue($myObject->connectToDevice());
}

现在您可以控制测试范围之外的所有内容(连接),并且只测试在connectToDevice() 中定义的行为。这将使您的测试对其他类中的更改安全,但可能会导致问题,例如在将来更新 connect() 函数更改时,例如添加或更改参数。

重要的是,您必须确保满足运行测试的要求。

【讨论】:

    【解决方案2】:

    这是可能的(您可以根据另一项测试的结果进行一项测试,请参阅Test Dependencies),但是请考虑您将在此处的问题中有效测试的内容,并将其包含在一项测试中:

    public function testConnectToDeviceBehavior()
    {
        $actual = $this->myObject->connectToDevice() === $this->myObject->connectToDevice();
        $this->assertTrue($actual);
    }
    

    当且仅当连接到设备没有两次给出相同的结果时,这将导致测试失败。

    您可以使用多个测试的测试依赖项来编写它,但这很可能不是您最终想要测试的内容。

    但是,如果是,请检查 Phpunit 文档的链接部分。

    否则请考虑the other answer 中概述的内容,使用依赖注入并通过与设备的正常连接和失败连接来测试您的对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-07-28
      • 1970-01-01
      • 1970-01-01
      • 2020-03-26
      • 2021-01-30
      • 2013-08-26
      • 2022-07-12
      相关资源
      最近更新 更多