在 PHP 中在实例化自己的对象的函数中使用模拟对象

2022-01-25 00:00:00 mocking unit-testing database php phpunit

我一直在研究如何将单元测试覆盖率添加到用 PHP 编写的大型现有代码库中.静态类和可实例化类中的许多函数都会调用库或实例化对象,以获得与内存缓存和数据库的连接.它们通常看起来像这样:

I have been looking into how to add unit testing coverage to a large, existing codebase written in PHP. Many functions in both static and instantiable classes make a call to a library or instantiate an object in order to obtain connections to memcache and the database. They typically look something like this:

public function getSomeData() {
    $key = "SomeMemcacheKey";
    $cache = get_memcache();

    $results = $cache->get($key);
    if (!$results) {
        $database = new DatabaseObject();
        $sql = "SELECT * from someDatabase.someTable";
        $results = $database->query($sql);

        $cache->set($key, $results);
    }

    return $results;
}

我和我的同事目前正在尝试通过 PHPUnit 为我们正在编写的一些新类实现覆盖.我试图找到一种方法,以隔离方式为我们现有代码库中的函数创建单元测试,这些函数类似于上面的伪代码,但没有成功.

My colleagues and I are currently trying to implement coverage via PHPUnit for a few of the new classes we are writing. I have attempted to find a way to create unit tests in an isolated manner for functions in our existing codebase that resemble the pseudo-code above, but have been unsuccessful.

我在 PHPUnit 文档中看到的示例都依赖于类中的某些方法,通过该方法可以将模拟对象附加到它,例如:$objectBeingTested->attach($mockObject); 我查看了 SimpleUnit,在那里看到了同样的东西,模拟对象通过其构造函数传递到类中.这不会为实例化自己的数据库对象的函数留下太多空间.

The examples I've seen in the PHPUnit documentation all rely on having some method in the class by which a mock object can be attached to it, such as: $objectBeingTested->attach($mockObject); I looked at SimpleUnit, and saw the same thing there, the mock objects were being passed into the class via its constructor. This doesn't leave much room for functions which instantiate their own database objects.

有什么方法可以模拟出这类调用吗?我们可以使用另一个单元测试框架吗?还是我们将来必须更改我们使用的模式以促进单元测试?

Is there any way to mock out these sorts of calls? Is there another unit testing framework we can use? Or are we going to have to change the patterns we are using in the future in order to facilitate unit testing?

我想做的是能够在运行测试时用模拟类替换整个类.例如,可以将 DatabaseObject 类替换为模拟类,并且在测试期间任何时候实例化它时,它实际上都是模拟版本的实例.

What I'd like to do is be able to swap out an entire class with a mock class when running tests. For instance, the DatabaseObject class could be replaced with a mock class, and any time it's instantiated during a test, it would actually be an instance of the mock version.

我的团队一直在谈论重构我们在新代码中访问数据库和 memcache 的方法,可能使用单例.我想如果我们以这样一种方式编写单例,它自己的实例可以被一个模拟对象替换,那可能会有所帮助......

There has been talk in my team of refactoring our methods of accessing the database and memcache in new code, perhaps using singletons. I suppose that could help if we were to write the singleton in such a way that its own instance of itself could be replaced with a mock object...

这是我第一次涉足单元测试.如果我做错了,请说出来.:)

This is my first foray into unit testing. If I'm doing it wrong, please say so. :)

谢谢.

推荐答案

在一个完美的世界中,您将有时间重构所有遗留代码以使用依赖注入或类似的东西.但在现实世界中,你经常不得不处理你已经被处理过的手.

In a perfect world, you'd have the time to refactor all your legacy code to use dependency injection or something similar. But in the real world, you often have to deal the hand you've been dealt.

PHPUnit 的作者 Sebastian Bergmann 编写了一个测试助手扩展,它允许您用回调和重命名函数覆盖 new 运算符.这些将允许您在测试期间对代码进行修补,直到您可以将其重构为更可测试.当然,使用它编写的测试越多,撤消它的工作就越多.

Sebastian Bergmann, the author of PHPUnit, wrote a test helpers extension that allows you to override the new operator with a callback and rename functions. These will allow you to monkey patch your code during testing until you can refactor it to be more testable. Granted, the more tests you write using this, the more work you'll have undoing it.

注意: Test-Helper 扩展被取代 https://github.com/krakjoe/uopz

相关文章