首先,我的知识是:
单元测试是那些测试一小段代码(大多数是单一方法)的测试。
集成测试是测试多个代码区域之间的交互的测试(希望它们已经具有自己的单元测试)。有时,被测代码的某些部分需要其他代码以特定方式起作用。这是Mocks&Stubs出现的地方。因此,我们模拟/存根代码的一部分以非常具体地执行。这使我们的集成测试可以预期地运行而没有副作用。
所有测试都应能够独立运行而无需数据共享。如果必须进行数据共享,则表明系统耦合不够充分。
接下来,我面临的情况是:
与外部API(特别是RESTful API,将通过POST请求修改实时数据)进行交互时,我了解我们可以(应该吗?)模拟出与该API的交互(在此答案中更雄辩地说),以进行集成测试。我也了解我们可以对与该API交互的各个组件进行单元测试(构造请求,解析结果,抛出错误等)。我不明白的是如何真正做到这一点。
所以,最后:我的问题。
如何测试与具有副作用的外部API的交互?
谷歌的Content API for shopping是一个很好的例子。为了能够执行手头的任务,需要大量的准备工作,然后执行实际的请求,然后分析返回值。其中一些没有任何“沙盒”环境。
执行此操作的代码通常具有相当多的抽象层,例如:
<?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请求,而该请求具有更改实时数据的副作用。
这可以接受吗?我有什么选择?我看不到一种模拟测试的Request对象的方法。即使我这样做,也将意味着为Google API接受的每个可能的代码路径设置结果/入口点(在这种情况下,必须通过反复试验才能找到),但允许我使用固定装置。
进一步的扩展是某些请求依赖于某些已经实时存在的数据时。再次以Google Content API为例,要将数据Feed添加到子帐户,该子帐户必须已经存在。
我可以想到的一种方法是以下步骤;
- 在
testCreateAccount
- 创建一个子账户
- 断言子帐户已创建
- 删除子账户
- 有无
testCreateDataFeed
是否取决于testCreateAccount
没有任何错误- 在中
testCreateDataFeed
,创建一个新帐户 - 创建数据Feed
- 断言数据供稿已创建
- 删除数据文件
- 删除子账户
- 在中
这就提出了进一步的问题。如何测试帐户/数据Feed的删除?testCreateDataFeed
对我来说很脏-如果创建数据提要失败怎么办?测试失败,因此子帐户永远不会被删除...我无法在没有创建的情况下测试删除,因此我在创建然后删除自己的帐户之前是否编写了另一个测试(testDeleteAccount
)testCreateAccount
(因为数据不应在测试之间共享)。
综上所述
- 如何测试与影响实时数据的外部API的交互?
- 当对象隐藏在抽象层后面时,如何在集成测试中模拟/存根对象?
- 如果测试失败并且实时数据处于不一致状态,该怎么办?
- 我实际上如何在代码中进行所有这些操作?
有关: