在 PHPUnit 中测试具有依赖关系的对象

2022-01-25 00:00:00 unit-testing php phpunit qa

对于组成另一个对象作为其实现的一部分的对象,编写单元测试以便只测试主要对象的最佳方法是什么?简单的例子:

For objects which compose another object as part of their implementation, what's the best way to write the unit test so only the principle object gets tested? Trivial example:

class myObj { 
    public function doSomethingWhichIsLogged()
    {
        // ...
        $logger = new logger('/tmp/log.txt');
        $logger->info('some message');
        // ...
    }
}

我知道可以设计该对象,以便可以注入记录器对象依赖项并因此在单元测试中进行模拟,但情况并非总是如此 - 在更复杂的情况下,您确实需要组合其他对象或进行调用静态方法.

I know that the object could be designed so that the logger object dependency could be injected and hence mocked in a unit test, but that's not always the case - in more complicated scenarios, you do need to compose other objects or make calls to static methods.

由于我们不想测试记录器对象,只测试 myObj,我们该如何进行?我们是否使用测试脚本创建了一个存根的双重"?比如:

As we don't want to test the logger object, only the myObj, how do we proceed? Do we create a stubbed "double" with the test script? Something like:

class logger
{
    public function __construct($filepath) {}
    public function info($message) {}
}

class TestMyObj extends PHPUnit_Framework_TestCase 
{
    // ...
}

这对于小对象来说似乎是可行的,但对于 SUT 依赖于返回值的更复杂的 API 来说会很痛苦.另外,如果你想测试对依赖对象的调用,你可以使用模拟对象吗?有没有办法模拟由 SUT 实例化而不是传入的对象?

This seems feasible for small objects but would be a pain for more complicated APIs where the SUT depended on the return values. Also, what if you want to test the calls to the dependency object in the same was you can with mock objects? Is there a way of mocking objects which are instantiated by the SUT rather than being passed in?

我已经阅读了关于模拟的手册页,但它似乎没有涵盖这种依赖组合而不是聚合的情况.你是怎么做到的?

I've read the man page on mocks but it doesn't seem to cover this situation where the dependency is composed rather than aggregated. How do you do it?

推荐答案

您似乎已经意识到,具体的类依赖关系使测试变得困难(或完全不可能).您需要解耦这种依赖关系.一个不会破坏现有 API 的简单更改是默认为当前行为,但提供一个挂钩来覆盖它.有多种实现方式.

As you seem to be aware already, Concrete Class Dependencies makes testing hard (or outright impossible). You need to decouple that dependency. A simple change, that doesn't break the existing API, is to default to the current behaviour, but provide a hook to override it. There are a number of ways that this could be implemented.

有些语言有可以将模拟类注入代码的工具,但我不知道 PHP 有什么类似的东西.在大多数情况下,无论如何重构代码可能会更好.

Some languages have tools that can inject mock classes into code, but I don't know of anything like this for PHP. In most cases, you would probably be better off refactoring your code anyway.

相关文章