Qafoo GmbH - passion for software quality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Author: Benjamin Eberlei :Date: Fri, 02 Sep 2016 07:59:09 +0200 :Revision: 8 :Copyright: All rights reserved Outside-In Testing and the Adapter and Facade Patterns ====================================================== :Keywords: php, testing, design pattern, adapter, facade :Description: Outside-In Testing benefits a lot from the facade and adapter patterns, which will separate your code from third-party libraries and simplify the APIs of your system. :Abstract: Outside-In Testing benefits a lot from the facade and adapter patterns, which will separate your code from third-party libraries and simplify the APIs of your system. We at Qafoo are big fans of outside-in testing as described in the book "`Growing Object-Oriented Software, Guided by Tests `_" (Steve Freeman and Nat Pryce). As part of our workshops on Test-Driven Development `we explain to our customers `_ how testing from the outside-in can help find the right test-mix. The technique puts a focus on test-driven-development, but instead of the traditional approach starts at the acceptance test level. The first test for a feature is an acceptance test and only then the feature is implemented from the outside classes first (UI and controllers), towards the inner classes (model, infrastructure). Mocks are used to describe roles of collaborators when starting to write tests for the outside classes. When a class is tested, the `roles described by interfaces `_ are implemented and the testing cycle starts again with mocks for the collaborators. Outside-In testing leads to interfaces that are written from what is useful for the client object using them, in contrast to objects that are composed of collaborators that already exist. Because at some point we have to interact with objects that exist already, we will need three techniques to link those newly created interfaces/roles to existing code in our project: 1. The adapter pattern, mostly for third-party code 2. The facade pattern, mostly to structure your own code into layers 3. Continuous refactoring of all the interfaces and implementations This blog post will focus on the facade adapter pattern as one of the most important ingredient to testable code. Even if very well tested, APIs of third party libraries or frameworks are usually not suited for projects using any form of automated testing, either because using them directly requires setting up external resources (I/O) or mocking them is complex, maybe even impossible. .. warning:: Side Fact: This even affects libraries that put a focus on testing, such as Doctrine2, Symfony2 or Zend Framework. The reason is that libraries often provide static APIs, fluent APIs, facades with too many public methods or complex object interactions with law-of-demeter violations. Take a common use-case, importing data from a remote source into your own database. In a Symfony2 project with Doctrine and Guzzle as supporting libraries, the feature could easily be implemented as a console command, using only the third-party code and some glue code of our own:: getContainer()->get('guzzle.http_client'); $entityManager = $this->getContainer()->get('doctrine.orm.default_entity_manager'); $request = $client->get('http://remote.source/products.xml'); $response = $request->send(); $products = $this->parseResponse($response); foreach ($products as $product) { $entityManager->persist($product); } $entityManager->flush(); } protected function parseResponse($response) { /** omitted */ } } Looks simple, but in the real world, you can safely assume there is quite some complexity in ``parseResponse`` and possibly even more logic inside the loop over all the ``$products``. In this scenario, the code is completely untestable, just by combining the APIs of Guzzle, Doctrine and Symfony. Lets take a different approach, starting with the highest level description of the feature as a test written in Gherkin for Behat:: Scenario: Import Products Given a remote service with products: | name | description | price | A | Nice and shiny | 100 | B | Rusty, but cheap | 10 When I import the products Then I should see product "A" in my product listing Then I should see product "B" in my product listing This will be our acceptance (or end-to-end) test that will use as many external systems and I/O as possible. We might need to mock data from the remote product service, but when possible we should use the real data. This test will require the UI to be done as well as the necessary database and remote system APIs and therefore will fail until we have implemented all the code necessary. So lets focus on the code directly with our first unit-test and lets imagine how we want the code to be used:: createSampleProduct(); $productB = $this->createSampleProduct(); \Phake::when($remoteCatalog)->fetch()->thenReturn(array($productA, $productB)); $importer = new ProductImporter($productGateway); $importer->import($remoteCatalog); \Phake::verify($productGateway)->store($productA); \Phake::verify($productGateway)->store($productB); } } When we fetch 2 products from the ``$remoteCatalog`` then those products should be passed to the ``$productGateway``, our storage system. To describe the use-case in this simple way, we have abstracted ``RemoteCatalog`` and ``ProductGateway``, which will act as facades used in our ``ProductImporter``. The implementation is very simple:: fetch(); foreach ($products as $product) { $this->productGateway->store($product); } } } We haven't seen the Guzzle and Doctrine code here again, instead we have written code that is entirely written in our business domain and uses concepts from this domain. Lets move to the implementation of the ``RemoteCatalog`` starting with a test:: createSampleProduct(); \Phake::when($client)->get($url)->thenReturn(''); \Phake::when($parser)->parse('')->thenReturn(array($productA)); $catalog = new HttpCatalog($url, $client, $parser); $data = $catalog->fetch(); $this->assertSame(array($productA), $data); } } This test again perfectly clear explains how fetching a catalog with HTTP should work on a technical level. Notice how we choose a very simple API for the HTTP client, one that is fetching and `$url` and retrieving the body as string. We don't need more. :: client->fetch($this->url); return $this->parser->parse($body); } } The `HttpClient` is a real adapter for us now, we want this very simple API and we are going to use Guzzle to implement it:: client = $client; } public function fetch($url) { $request = $this->client->get($url); $response = $request->send(); return $response->getBody(true); } } Implementing the Guzzle client we have reached a "leaf" of our object graph. It is important to see how only the Guzzle adapter actually uses Guzzle code. A complete solution would also require to handle the Guzzle Exceptions, but that is only a trivial task to introduce as well. You can continue with this example and implement the ``Parser`` and the ``ProductGateway`` objects. The technique stays the same: Think in terms of what you want the API to look from the outside and invent collaborators that help you think about the problem. Then implement them until you get to the "leafs" of the object graph. Only the leafs should actually contain code to third party software. Starting with new requirements of the system we will probably be able to reuse some of the code: The `HttpClient` interface is very useful in a good number of use-cases. The `HttpCatalog` can be used with specific `Parser` implementations to import many different product catalogs that have an HTTP interface. And if your customer uses some other protocols like FTP, BitTorrent or anything else then we are good as well. The ``ProductGateway`` will probably be used in various places to give us access to finding and storing products. .. note:: Qafoo can help you finding your right testing strategies – read about possible workshop here__. We can, of course, adapt those schedules to your needs. __ /services/workshops/testing.html Conclusion ---------- Specification and testing of the requirements from the outside requires you to think about what you need first and not on what you have and how you might combine this. This helps to design simple and reusable objects. But outside-in testing has additional benefits: When you don't test everything in your system with unit-tests (which is not uncommon) for any of the various reasons (time, prototyping, not core domain, ..) then you still have at least one acceptance test that verifies the complete feature and maybe some unit-tests for the tricky implementation details. Overall we think making yourself familiar with outside-in testing is beneficial to designing testable and maintainable applications. .. Local Variables: mode: rst fill-column: 79 End: vim: et syn=rst tw=79 Trackbacks ========== Comments ======== - Felix at Tue, 05 Jul 2016 13:45:29 +0200 Nice article, thanks! There is one Typo "RemoveCatalog" on the implementation part. - Toby at Tue, 05 Jul 2016 14:05:48 +0200 @Felix: Thanks. I fixed the typo. - Thomas at Wed, 06 Jul 2016 11:30:05 +0200 I'm missing one aspect: Will you (partly) replace the mock objects after implementing the real objects (if there are no reasons not to do that, e.g. execution time, external resources, ...). Alternatively: Do you write additional integration tests in the end? My main issue with mock objects is that the mocked methods can differ from the real implementation, e.g. return types. I know that there are issues with using the real implementation of the depended-on components (DOC) instead of mocks as well - e.g. finding out if some failure is in the system under test or in a DOC. But that hasn't been a real issue for me in practice. Regards Thomas - Nagarjun at Tue, 16 Aug 2016 20:09:30 +0200 Hi Our client is looking for PHP Developer(75% remote). If you are interested please send me your updated resume along with visa status and current/expecting salary. Role: PHP Developer (75% remote) Location: Hoover, AL Possibility of remote with 1-2 days onsite weekly Requirement: Candidate must have experience with web development (PHP, CSS, JQuery, and AJAX) and programming in codeignitor environment is a plus. Proven experience programming in PHP, CSS, JavaScript, JQuery and AJAX Proven knowledge of SQL environment and SQL statements PHP, CSS, MySQI, JQuery, and AJAX programming in codeignitor environment is a plus. At least 4 years experience is desirable. Thanks & Regards:- Name: Nagarjun Bandaru Email id: nagarjun@maania.com