首页 > 后端开发 > php教程 > 用嘲笑嘲笑您的测试依赖性

用嘲笑嘲笑您的测试依赖性

William Shakespeare
发布: 2025-02-20 09:07:09
原创
153 人浏览过

Mock your Test Dependencies with Mockery

虽然并非每个人都在这样做,但测试你的应用程序是作为开发人员最基本的部分之一。单元测试是最常见的测试。通过单元测试,你可以检查一个类是否完全按照你的预期那样运行。有时,你在你的应用程序中使用的是第三方服务,很难设置好所有内容来进行单元测试。这正是模拟发挥作用的时候。

关键要点

  • 模拟是创建一个替代单元测试中真实对象的替代对象的过程,这在测试严重依赖依赖注入的应用程序时特别有用。
  • Mockery 是 Pádraic Brady 创建的一个库,可用于模拟单元测试中的对象,为 PHPUnit 的默认模拟功能提供了一种替代方案。
  • Mockery 允许开发人员定义对方法调用次数、将接收的参数以及将返回的值的期望,使其成为隔离单元测试中依赖项的强大工具。
  • 虽然 PHPUnit 已经可以模拟对象,但 Mockery 为希望确保其单元测试不受其他类影响的开发人员提供了更大的灵活性和便利性。

什么是模拟?

模拟对象只不过是创建一个替代对象,它在单元测试中替换真实对象。如果你的应用程序严重依赖依赖注入,模拟是可行的方法。

模拟对象可能有几个原因:

  1. 执行单元测试时,最好隔离类。你不想让另一个类或服务干扰你的单元测试。
  2. 对象尚不存在。你可以先创建测试,然后构建最终对象。
  3. 模拟对象通常比为测试准备整个数据库更快。

运行单元测试时,你可能正在使用 PHPUnit。PHPUnit 带有一些默认的模拟功能,如文档中所示。你可以在 Jeune Asuncion 撰写的这篇文章中阅读更多关于模拟的常规信息以及 PHPUnit 的模拟功能。

在本文中,我们将深入探讨由 Pádraic Brady 创建的库 Mockery。我们将创建一个温度类,该类将注入当前不存在的天气服务。

设置

让我们从设置项目开始。我们从包含以下内容的 composer.json 文件开始。这将确保我们拥有 mockery 和 PHPUnit。

<code>{
    "name": "sitepoint/weather",
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "psr-0": { "": "src/" }
    },
    "require-dev": {
        "phpunit/phpunit": "4.1.*",
        "mockery/mockery": "0.9.*"
    }
}</code>
登录后复制
登录后复制
登录后复制

我们还创建一个名为 phpunit.xml 的 PHPUnit 配置文件

<phpunit>
    <testsuite name="SitePoint Weather">
        <directory>./tests</directory>
    </testsuite>
    <listeners>
        <listener class="\Mockery\Adapter\Phpunit\TestListener"
                  file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"/>
    </listeners>
</phpunit>
登录后复制
登录后复制
登录后复制

定义这个监听器很重要。如果没有监听器,如果方法 once()twice()times() 使用不正确,则不会引发错误。稍后将详细介绍。

我还创建了 2 个目录。src 目录用于保存我的类,tests 目录用于存储我们的测试。在 src 目录中,我创建了路径 SitePointWeather。

我们首先创建 WeatherServiceInterface。我们不存在的天气服务将实现此接口。在这种情况下,我们只提供一个方法,该方法将为我们提供摄氏温度。

<code>{
    "name": "sitepoint/weather",
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "psr-0": { "": "src/" }
    },
    "require-dev": {
        "phpunit/phpunit": "4.1.*",
        "mockery/mockery": "0.9.*"
    }
}</code>
登录后复制
登录后复制
登录后复制

因此,我们有一个服务可以为我们提供摄氏温度。我想获得华氏温度。为此,我创建了一个名为 TemperatureService 的新类。此服务将注入天气服务。除此之外,我们还定义了一个方法,该方法将摄氏温度转换为华氏温度。

<phpunit>
    <testsuite name="SitePoint Weather">
        <directory>./tests</directory>
    </testsuite>
    <listeners>
        <listener class="\Mockery\Adapter\Phpunit\TestListener"
                  file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"/>
    </listeners>
</phpunit>
登录后复制
登录后复制
登录后复制

创建单元测试

我们已经准备好设置单元测试了。我们在 tests 目录中创建一个 TemperatureServiceTest 类。在这个类中,我们创建方法 testGetTempFahrenheit(),它将测试我们的华氏方法。

此方法中要做的第一步是创建一个新的 TemperatureService 对象。就在我们这样做的时候,我们的构造函数将请求一个实现了 WeatherServiceInterface 的对象。由于我们还没有这样的对象(我们也不想要),我们将使用 Mockery 为我们创建一个模拟对象。让我们看看完成后的方法是什么样的。

namespace SitePoint\Weather;

interface WeatherServiceInterface
{
    /**
     * 返回摄氏温度
     *
     * @return float
     */
    public function getTempCelsius();
}
登录后复制

我们首先创建模拟对象。我们告诉 Mockery 我们想要模拟哪个对象(或接口)。第二步是描述将在此模拟对象上调用的方法。在 shouldReceive() 方法中,我们定义将调用的方法的名称。

我们定义此方法将调用的次数。我们可以使用 once()twice()times(X)。在这种情况下,我们预计它只会调用一次。如果未调用或调用次数过多,单元测试将失败。

最后,我们在 andReturn() 方法中定义将返回的值。在这种情况下,我们返回 25。Mockery 还具有 andReturnNull()andReturnSelf()andReturnUndefined() 等返回方法。如果这是你的预期,Mockery 也能够抛出异常。

我们现在有了模拟对象,可以创建我们的 TemperatureService 对象并像往常一样进行测试。25 摄氏度是 77 华氏度,因此我们检查是否从我们的 getTempFahrenheit() 方法中收到 77。

如果你在你的根目录中运行 vendor/bin/phpunit tests/,你将从 PHPUnit 获得绿灯,表明一切都很完美。

高级用法

上面的例子相当简单。没有参数,只是一个简单的调用。让我们让事情变得复杂一些。

假设我们的天气服务还有一个方法可以在确切的小时获取温度。我们将以下方法添加到我们当前的 WeatherServiceInterface。

namespace SitePoint\Weather;

class TemperatureService
{
    /**
     * @var WeatherServiceInterace $weatherService 保存天气服务
     */
    private $weatherService;

    /**
     * 构造函数。
     *
     * @param WeatherServiceInterface $weatherService
     */
    public function __construct(WeatherServiceInterface $weatherService) {
        $this->weatherService = $weatherService;
    }

    /**
     * 获取当前华氏温度
     *
     * @return float
     */
    public function getTempFahrenheit() {
        return ($this->weatherService->getTempCelsius() * 1.8000) + 32;
    }
}
登录后复制

我们想知道,晚上 0:00 到 6:00 之间的平均温度是多少。为此,我们在 TemperatureService 中创建一个新方法来计算平均温度。为此,我们从 WeatherService 中检索 7 个温度并计算平均值。

<code>{
    "name": "sitepoint/weather",
    "license": "MIT",
    "type": "project",
    "require": {
        "php": ">=5.3.3"
    },
    "autoload": {
        "psr-0": { "": "src/" }
    },
    "require-dev": {
        "phpunit/phpunit": "4.1.*",
        "mockery/mockery": "0.9.*"
    }
}</code>
登录后复制
登录后复制
登录后复制

让我们看看我们的测试方法。

<phpunit>
    <testsuite name="SitePoint Weather">
        <directory>./tests</directory>
    </testsuite>
    <listeners>
        <listener class="\Mockery\Adapter\Phpunit\TestListener"
                  file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"/>
    </listeners>
</phpunit>
登录后复制
登录后复制
登录后复制

我们再次模拟接口,并定义将调用的方法。接下来,我们定义此方法将调用的次数。我们在前面的示例中使用了 once(),现在我们使用 times(7) 来指示我们期望此方法被调用 7 次。如果该方法没有被精确调用 7 次,则测试将失败。如果你没有在 phpunit.xml 配置文件中定义监听器,你将不会收到关于此的通知。

接下来,我们定义 with() 方法。在 with 方法中,你可以定义你期望的参数。在这种情况下,我们期望 7 个不同的小时。

最后,我们有 andReturn() 方法。在这种情况下,我们指示了 7 个返回值。如果你定义的返回值较少,则每次都会重复最后一个可用的返回值。

当然,Mockery 可以做更多的事情。有关完整的指南和文档,我建议你查看 Github 页面。

如果你对上面项目的代码感兴趣,你可以查看这个 Github 页面。

结论

使用 PHPUnit,你已经可以模拟对象了。但是,你也可以像上面示例中解释的那样使用 Mockery。如果你正在对你的类进行单元测试,并且你不想让任何其他类影响你的测试,mockery 可以轻松地帮助你。如果你真的想进行功能测试,最好看看你是否可以集成真正的测试。你目前是否正在使用 PHPUnit 模拟并考虑切换到 Mockery?你想在后续文章中看到更多更大的 Mockery 示例吗?请在下面的评论中告诉我。

关于 Mockery 和测试依赖项的常见问题解答 (FAQ)

什么是 Mockery,为什么它在 PHP 测试中很重要?

Mockery 是一个强大而灵活的 PHP 模拟对象框架,用于单元测试。它被设计为 PHPUnit 模拟对象功能的直接替代品。Mockery 允许开发人员隔离被测代码并创建测试替身,这些测试替身模拟复杂对象的行为。这在单元测试中至关重要,因为它确保被测代码不依赖于任何外部因素或状态。

如何在我的 PHP 项目中安装和设置 Mockery?

要安装 Mockery,你需要拥有 Composer,这是一个 PHP 的依赖项管理器。你可以通过运行命令 composer require --dev mockery/mockery 来安装 Mockery。安装后,你可以在测试文件中通过在测试拆卸方法中调用 Mockery::close() 来设置 Mockery,以清理模拟对象。

如何使用 Mockery 创建模拟对象?

在 Mockery 中创建模拟对象很简单。你可以使用 mock() 方法来创建一个模拟对象。例如,$mock = Mockery::mock('MyClass'); 将创建一个 MyClass 的模拟对象。

如何在 Mockery 中定义期望?

在 Mockery 中,你通过将方法链接到模拟对象来定义期望。例如,$mock->shouldReceive('myMethod')->once()->andReturn('mocked value'); 此代码告诉 Mockery 预期“myMethod”将被调用一次,并且应该返回“mocked value”。

Mockery 中模拟和存根的区别是什么?

在 Mockery 中,模拟是我们可以在其上设置期望的对象,而存根是预先编程了响应的模拟对象。当响应是唯一重要的事情时,通常使用存根,而当测试交互本身时,则使用模拟。

如何使用 Mockery 测试私有方法?

不建议直接测试私有方法,因为它违反了封装原则。但是,如果需要,你可以使用 Mockery 中的 shouldAllowMockingProtectedMethods() 方法来允许模拟受保护和私有方法。

如何在 Mockery 中处理构造函数参数?

如果要模拟的类具有带参数的构造函数,可以将它们作为数组传递给 mock() 方法。例如,$mock = Mockery::mock('MyClass', [$arg1, $arg2]); 将将 $arg1 和 $arg2 传递给 MyClass 的构造函数。

如何使用 Mockery 模拟静态方法?

Mockery 提供了一种使用 alias: 前缀模拟静态方法的方法。例如,$mock = Mockery::mock('alias:MyClass'); 将创建一个可以用来对 MyClass 的静态方法设置期望的模拟对象。

如何验证 Mockery 中是否已满足所有期望?

你可以在测试拆卸方法中使用 Mockery::close() 方法来验证是否已满足所有期望。如果任何期望未满足,Mockery 将引发异常。

如何在 Mockery 中处理异常?

你可以使用 andThrow() 方法设置模拟对象以抛出异常。例如,$mock->shouldReceive('myMethod')->andThrow(new Exception); 将在调用“myMethod”时抛出异常。

以上是用嘲笑嘲笑您的测试依赖性的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板