正确测试调用 API 的 SDK
Properly testing an SDK that calls an API
我已经编写了一个 API,现在我正在为第 3 方编写一个 SDK,以便更轻松地与我的 API 进行交互。
在为我的 SDK 编写测试时,我的理解是最好不要简单地调用所有 API 端点,因为:
- API 中的测试将负责确保 API 正常工作。
- 如果 SDK 测试直接调用 API,我的测试会很慢。
举个例子,假设我的 API 有这个端点:
/account
在我的 API 测试套件中,我实际上调用了这个端点来验证它 returns 正确的数据。
我在我的 SDK 中采用什么方法对此进行测试?我应该嘲笑 /account
的请求吗?我还需要做什么才能使我的 SDK 得到良好的覆盖?
我查看了其他 SDK(Stripe、Algolia、AWS),看看它们是如何处理这个问题的,但在某些情况下,它们看起来确实在调用实际 API.
(我目前正在使用 PHPUnit,但我也会用其他语言编写 SDK。)
在为 SDK 编写测试时,您假设您的 api 确实按预期工作(并且您为 api 编写测试以确保这一点)。
因此,使用某种沙箱甚至是 api 的完整模拟就足够了。
我建议使用 wiremock 之类的东西来模拟您的 API,然后围绕该模拟 API 编写您的单元测试,以确保一切正常。
这样,当您的生产应用程序出现故障时,您至少可以确保(通过 运行 单元测试)您的应用程序方面没有任何故障,但实际 API 可能存在问题(即响应格式正在更改)。
我最终采用了这种方法:
我有两个单元测试和集成测试。
我的集成测试调用实际 API。我通常 运行 这种频率要低得多——就像在我将代码推送到远程之前一样。 (任何使用我的代码的人都必须提供他们自己的 API 凭据)
我的单元测试 - 我 运行 非常频繁 - 只是确保我的代码的响应是我期望的样子。我相信第 3 方 API 会给我很好的数据(而且我仍然有集成测试来支持它)。
我已通过 mocking Guzzle, using Reflection to replace the client instance in my SDK code, and then using Mock Handlers 完成此操作以模拟我期望的实际响应。
这是一个例子:
/** @test */
public function it_retrieves_an_account()
{
$account = $this->mockClient()->retrieve();
$this->assertEquals(json_decode('{"id": "9876543210"}'), $account);
}
protected function mockClient()
{
$stream = Psr7\stream_for('{"id": "9876543210"}');
$mock = new MockHandler([new Response(
200,
['Content-Type' => 'application/json'],
Psr7\stream_for($stream)
)]);
$handler = HandlerStack::create($mock);
$mockClient = new Client(['handler' => $handler]);
$account = new SparklyAppsAccount(new SparklyApps('0123456789'));
$reflection = new \ReflectionClass($account);
$reflection_property = $reflection->getProperty('client');
$reflection_property->setAccessible(true);
$reflection_property->setValue($account, $mockClient);
return $account;
}
我已经编写了一个 API,现在我正在为第 3 方编写一个 SDK,以便更轻松地与我的 API 进行交互。
在为我的 SDK 编写测试时,我的理解是最好不要简单地调用所有 API 端点,因为:
- API 中的测试将负责确保 API 正常工作。
- 如果 SDK 测试直接调用 API,我的测试会很慢。
举个例子,假设我的 API 有这个端点:
/account
在我的 API 测试套件中,我实际上调用了这个端点来验证它 returns 正确的数据。
我在我的 SDK 中采用什么方法对此进行测试?我应该嘲笑 /account
的请求吗?我还需要做什么才能使我的 SDK 得到良好的覆盖?
我查看了其他 SDK(Stripe、Algolia、AWS),看看它们是如何处理这个问题的,但在某些情况下,它们看起来确实在调用实际 API.
(我目前正在使用 PHPUnit,但我也会用其他语言编写 SDK。)
在为 SDK 编写测试时,您假设您的 api 确实按预期工作(并且您为 api 编写测试以确保这一点)。
因此,使用某种沙箱甚至是 api 的完整模拟就足够了。
我建议使用 wiremock 之类的东西来模拟您的 API,然后围绕该模拟 API 编写您的单元测试,以确保一切正常。
这样,当您的生产应用程序出现故障时,您至少可以确保(通过 运行 单元测试)您的应用程序方面没有任何故障,但实际 API 可能存在问题(即响应格式正在更改)。
我最终采用了这种方法:
我有两个单元测试和集成测试。
我的集成测试调用实际 API。我通常 运行 这种频率要低得多——就像在我将代码推送到远程之前一样。 (任何使用我的代码的人都必须提供他们自己的 API 凭据)
我的单元测试 - 我 运行 非常频繁 - 只是确保我的代码的响应是我期望的样子。我相信第 3 方 API 会给我很好的数据(而且我仍然有集成测试来支持它)。
我已通过 mocking Guzzle, using Reflection to replace the client instance in my SDK code, and then using Mock Handlers 完成此操作以模拟我期望的实际响应。
这是一个例子:
/** @test */
public function it_retrieves_an_account()
{
$account = $this->mockClient()->retrieve();
$this->assertEquals(json_decode('{"id": "9876543210"}'), $account);
}
protected function mockClient()
{
$stream = Psr7\stream_for('{"id": "9876543210"}');
$mock = new MockHandler([new Response(
200,
['Content-Type' => 'application/json'],
Psr7\stream_for($stream)
)]);
$handler = HandlerStack::create($mock);
$mockClient = new Client(['handler' => $handler]);
$account = new SparklyAppsAccount(new SparklyApps('0123456789'));
$reflection = new \ReflectionClass($account);
$reflection_property = $reflection->getProperty('client');
$reflection_property->setAccessible(true);
$reflection_property->setValue($account, $mockClient);
return $account;
}