且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何集成测试与外部API进行交互写的?

更新时间:2023-02-07 20:07:15


  

如何测试与效果的实时数据的外部API交互?


块引用>

您没有。你必须真正相信实际的API实际工作。

您可以 - 也应该 - 运动与实时数据的API,以确保你了解它。

但你并不需要对其进行测试。如果API不起作用,干脆停止使用。不要测试每个边角情况。


  

我如何可以模拟在集成测试/存根对象时,他们隐藏的抽象层,后面?


块引用>

这是问题的关键。测试抽象。你要相信,实施工作。您正在测试的 code。不是他们的code。


  

我该怎么办时,测试失败,并且实时数据被留在不一致的状态?


块引用>

什么?你为什么要测试现场的API,以确保他们的工作?你不信任他们吗?如果你不信任他们,不测试。找到可以信任的供应商。

您只测试 code。你信任的 code。你假装平凡的不足code,以确保您的code ++工程。


你怎么做到这一点。


  1. 玩的API。发送请求。获得响应。


  2. 玩弄你的应用程序。搞清楚什么样的,你要发送的请求。


  3. 返回到API。发送已知良好的请求。获得响应。 保存这个响应。这是你的金标准响应良好的请求。推崇到一个测试用例这一点。


  4. 现在你可以在你的应用程序工作,知道你有一个黄金标准响应thatreally离真正的API来了。这应该是足够的上手操作的响应。


  5. 在您通过一些用例的工作(好的要求,错误的请求),你应该能够获得良好的反应,从API一些典型的错误响应。保存好和错误消息。这些都是有用的单元测试,以确保您正在处理的一些种恰当反应的。


First up, where my knowledge is at:

Unit Tests are those which test a small piece of code (single methods, mostly).

Integration Tests are those which test the interaction between multiple areas of code (which hopefully already have their own Unit Tests). Sometimes, parts of the code under test requires other code to act in a particular way. This is where Mocks & Stubs come in. So, we mock/stub out a part of the code to perform very specifically. This allows our Integration Test to run predictably without side effects.

All tests should be able to be run stand-alone without data sharing. If data sharing is necessary, this is a sign the system isn't decoupled enough.

Next up, the situation I am facing:

When interacting with an external API (specifically, a RESTful API that will modify live data with a POST request), I understand we can (should?) mock out the interaction with that API (more eloquently stated in this answer) for an Integration Test. I also understand we can Unit Test the individual components of interacting with that API (constructing the request, parsing the result, throwing errors, etc). What I don't get is how to actually go about this.

So, finally: My question(s).

How do I test my interaction with an external API that has side effects?

A perfect example is Google's Content API for shopping. To be able to perform the task at hand, it requires a decent amount of prep work, then performing the actual request, then analysing the return value. Some of this is without any 'sandbox' environment.

The code to do this generally has quite a few layers of abstraction, something like:

<?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());
    }
}
?>

Note: This is a PHP5 / PHPUnit example

Given that testTheRequest is the method called by the test suite, the example will execute a live request.

Now, this live request will (hopefully, provided everything went well) do a POST request that has the side effect of altering live data.

Is this acceptable? What alternatives do I have? I can't see a way to mock out the Request object for the test. And even if I did, it would mean setting up results / entry points for every possible code path that Google's API accepts (which in this case would have to be found by trial and error), but would allow me the use of fixtures.

A further extension is when certain requests rely on certain data being Live already. Using the Google Content API as an example again, to add a Data Feed to a Sub Account, the Sub Account must already exist.

One approach I can think of is the following steps;

  1. In testCreateAccount
    1. Create a sub-account
    2. Assert the sub-account was created
    3. Delete the sub-account

  2. Have testCreateDataFeed depend on testCreateAccount not having any errors
    1. In testCreateDataFeed, create a new account
    2. Create the data feed
    3. Assert the data feed was created
    4. Delete the data feed
    5. Delete the sub-account

This then raises the further question; how do I test the deletion of accounts / data feeds? testCreateDataFeed feels dirty to me - What if creating the data feed fails? The test fails, therefore the sub-account is never deleted... I can't test deletion without creation, so do I write another test (testDeleteAccount) that relies on testCreateAccount before creating then deleting an account of its own (since data shouldn't be shared between tests).

In Summary

  • How do I test interacting with an external API that effects live data?
  • How can I mock / stub objects in an Integration test when they're hidden behind layers of abstraction?
  • What do I do when a test fails and the live data is left in an inconsistent state?
  • How in code do I actually go about doing all this?

Related:

How do I test interacting with an external API that effects live data?

You don't. You have to actually trust that the actual API actually works.

You can -- and should -- exercise the API with live data to be sure you understand it.

But you don't need to test it. If the API doesn't work, simply stop using it. Don't test every edge and corner case.

How can I mock / stub objects in an Integration test when they're hidden behind layers of abstraction?

That's the point. Test the abstraction. You have to trust that the implementation works. You're testing your code. Not their code.

What do I do when a test fails and the live data is left in an inconsistent state?

What? Why are you testing live API's to be sure they work? You don't trust them? If you don't trust them, don't test. Find a vendor you can trust.

You only test your code. You trust their code. You trivially mock enough of their code to be sure your code works.


How you do this.

  1. Play around with the API. Send requests. Get responses.

  2. Play around with your app. Figure out what kinds of requests you're going to send.

  3. Go back to the API. Send a known good request. Get the response. Save this response. This is your gold-standard response to a good request. Canonize this into a test case.

  4. Now you can work on your app knowing that you have a gold-standard response thatreally came from the real API. That should be sufficient to get started handling the responses.

  5. After you have worked through a few use cases (good request, bad request) you should be able to get a good response and some typical error responses from the API. Save the good and the error messages. These are useful for unit testing to be sure you're handling some of the kinds of responses properly.