单元测试 是测试一小段代码的测试(主要是单个方法)。
集成测试 是测试多个代码区域之间交互的测试(希望它们已经有了自己的单元测试)。有时,被测代码的某些部分需要其他代码以特定的方式进行操作。这就是模拟与存根的用武之地。因此,我们模拟/存根掉一部分代码,以便非常具体地执行。这使得我们的集成测试可以在没有副作用的情况下可预测地运行。
所有测试都应该能够独立运行,而不需要共享数据。如果数据共享是必要的,这表明系统解耦程度不够。
当与外部 API 交互时(特别是将使用 POST 请求修改活动数据的 RESTful API) ,我理解我们可以(应该?)为集成测试模拟与该 API 的交互(在 这个答案中更有说服力地说明)。我还知道我们可以单元测试与 API 交互的各个组件(构造请求、解析结果、抛出错误等)。我不明白的是该怎么做。
如何测试与具有副作用的外部 API 的交互?
用于购物的 Google 内容 API就是一个很好的例子。为了能够执行手头的任务,它需要大量的准备工作,然后执行实际的请求,然后分析返回值。有些是 没有任何“沙盒”环境。
这样做的代码通常有相当多的抽象层,比如:
<?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 接受的每个可能的代码路径设置结果/入口点(在这种情况下,必须通过试验和错误才能找到) ,但是允许我使用 fixture。
进一步的扩展是当某些请求依赖于某些已经在 Live 中的数据时。再次使用 GoogleContentAPI 作为示例,要向子帐户添加数据源,子帐户必须已经存在。
我能想到的一种方法是下面的步骤;
testCreateAccount
testCreateDataFeed
依赖于 testCreateAccount
没有任何错误
testCreateDataFeed
中,创建一个新帐户这就提出了进一步的问题: 如何测试删除帐户/数据源?testCreateDataFeed
让我感觉很脏——如果创建数据提要失败了怎么办?测试失败,因此子帐户永远不会被删除... ... 我不能在没有创建的情况下测试删除,所以我要编写另一个依赖于 testCreateAccount
的测试(testDeleteAccount
) ,然后再创建然后删除自己的帐户(因为数据不应该在测试之间共享)。
相关阅读: