unit-testing - 如何编写集成测试以与外部 API 交互?

标签 unit-testing phpunit integration-testing restapi

首先,我的知识在哪里:

单元测试是测试一小段代码(主要是单一方法)的测试。

集成测试是那些测试多个代码区域之间的交互的测试(希望它们已经有了自己的单元测试)。有时,被测代码的某些部分需要其他代码以特定方式运行。这就是 Mocks & Stubs 的用武之地。因此,我们模拟/ stub 部分代码以非常具体地执行。这使我们的集成测试能够以可预测的方式运行而不会产生副作用。

所有测试都应该能够在没有数据共享的情况下独立运行。如果数据共享是必要的,这是系统解耦不够的标志。

接下来,我面临的情况:

当与外部 API(特别是将通过 POST 请求修改实时数据的 RESTful API)交互时,我知道我们可以(应该?)模拟与该 API 的交互(更 Eloquent 地在 this answer 中说明)以进行集成测试。我也明白我们可以对与该 API 交互的各个组件进行单元测试(构建请求、解析结果、抛出错误等)。我不明白的是如何实际解决这个问题。

所以,最后:我的问题。

如何测试我与具有副作用的外部 API 的交互?

一个完美的例子是 Google's Content API for shopping .为了能够执行手头的任务,它需要大量的准备工作,然后执行实际请求,然后分析返回值。其中一些是 without any 'sandbox' environment .

执行此操作的代码通常具有相当多的抽象层,例如:

<?php
class Request
{
    public function setUrl(..){ /* ... */ }
    public function setData(..){ /* ... */ }
    public function setHeaders(..){ /* ... */ }
    public function execute(..){
        // Do some CURL request or some-such
    }   
    public function wasSuccessful(){
        // some test to see if the CURL request was successful
    }   
}

class GoogleAPIRequest
{
    private $request;
    abstract protected function getUrl();
    abstract protected function getData();

    public function __construct() {
        $this->request = new Request();
        $this->request->setUrl($this->getUrl());
        $this->request->setData($this->getData());
        $this->request->setHeaders($this->getHeaders());
    }   

    public function doRequest() {
        $this->request->execute();
    }   
    public function wasSuccessful() {
        return ($this->request->wasSuccessful() && $this->parseResult());
    }   
    private function parseResult() {
        // return false when result can't be parsed
    }   

    protected function getHeaders() {
        // return some GoogleAPI specific headers
    }   
}

class CreateSubAccountRequest extends GoogleAPIRequest
{
    private $dataObject;

    public function __construct($dataObject) {
        parent::__construct();
        $this->dataObject = $dataObject;
    }   
    protected function getUrl() {
        return "http://...";
    }
    protected function getData() {
        return $this->dataObject->getSomeValue();
    }
}

class aTest
{
    public function testTheRequest() {
        $dataObject = getSomeDataObject(..);
        $request = new CreateSubAccountRequest($dataObject);
        $request->doRequest();
        $this->assertTrue($request->wasSuccessful());
    }
}
?>

注意:这是一个 PHP5/PHPUnit 示例

鉴于 testTheRequest是测试套件调用的方法,该示例将执行实时请求。

现在,此实时请求将(希望一切顺利)执行具有更改实时数据的副作用的 POST 请求。

这是可以接受的吗?我有哪些选择?我看不到模拟请求对象以进行测试的方法。即使我这样做了,也意味着为 Google 的 API 接受的每个可能的代码路径设置结果/入口点(在这种情况下必须通过反复试验找到),但允许我使用固定装置。

进一步的扩展是当某些请求依赖于某些已经处于 Live 状态的数据时。再次以 Google Content API 为例,要向子帐户添加数据 Feed,子帐户必须已经存在。

我能想到的一种方法是以下步骤;
  • testCreateAccount
  • 创建子账号
  • 断言子账户已创建
  • 删除子账号
  • testCreateDataFeed依赖 testCreateAccount没有任何错误
  • testCreateDataFeed , 创建一个新帐户
  • 创建数据馈送
  • 断言数据馈送已创建
  • 删除数据提要
  • 删除子账号

  • 这就提出了进一步的问题;如何测试帐户/数据馈送的删除? testCreateDataFeed我觉得很脏 - 如果创建数据提要失败怎么办?测试失败,因此子账户永远不会被删除......我无法在没有创建的情况下测试删除,所以我编写另一个依赖于 testDeleteAccount 的测试( testCreateAccount )在创建然后删除自己的帐户之前(因为不应在测试之间共享数据)。

    总结
  • 如何测试与影响实时数据的外部 API 的交互?
  • 当它们隐藏在抽象层后面时,如何在集成测试中模拟/ stub 对象?
  • 当测试失败并且实时数据处于不一致状态时,我该怎么办?
  • 我实际上如何在代码中完成所有这些?


  • 相关:
  • How can mocking external services improve unit tests?
  • Writing unit tests for a REST-ful API
  • 最佳答案

    这更多是对 one already given 的补充回答。 :

    查看您的代码,class GoogleAPIRequest具有 class Request 的硬编码依赖项.这会阻止您独立于请求类对其进行测试,因此您无法模拟请求。

    您需要使请求可注入(inject),以便在测试时将其更改为模拟。完成后,不会发送真正的 API HTTP 请求,不会更改实时数据,您可以更快地进行测试。

    关于unit-testing - 如何编写集成测试以与外部 API 交互?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7564038/

    相关文章:

    java - 如何使用 Spring dbunit 模拟数据库 View ?

    ios - 无法模拟 NSUserDefaults

    phpunit memory_limit 参数不适用

    symfony - 如何在 Symfony 4 中为测试环境设置数据库

    Java 集成测试可重用基础设置

    c# - 当有多个连接可用时,无法处理连接问题(数据库或外部系统)

    unit-testing - 使用 TestServer 测试 ASP.NET Core 完整的 .NET Framework 给出了在 vs 2017 中找不到的方法

    unit-testing - 黑盒单元测试

    c# - 在 VS 开发人员命令提示符之外运行单元测试?

    php - 在 PHPUnit 中断言数组 equalTo 且某些值为 null