Qafoo GmbH - passion for software quality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Author: Kore Nordmann :Date: Tue, 15 Nov 2016 09:43:56 +0100 :Revision: 5 :Copyright: All rights reserved ========================= Using Traits With PHPUnit ========================= :Abstract: As we already wrote that `"Code Reuse By Inheritance"`__ has lots of problems and we consider it a code smell. You should always aim to use Dependency Injection, most likely Constructor Injection. But with test cases in PHPUnit we cannot do this because we have no control about how and when our test cases are created. There are a similar problem in other frameworks, like we discussed in `"Object Lifecycle Control"`__. We also blogged about `traits as a Code Smell`__, but let me show and explain why they might be fine to use in your test cases. :Description: As we already wrote that "Code Reuse By Inheritance" has lots of problems and we consider it a code smell. You should always aim to use Dependency Injection, most likely Constructor Injection. But with test cases in PHPUnit we cannot do this because we have no control about how and when our test cases are created. There are a similar problem in other frameworks, like we discussed in "Object Lifecycle Control". We also blogged about traits as a Code Smell, but let me show and explain why they might be fine to use in your test cases. :Keywords: phpunit, testing, traits, design __ /blog/063_code_reuse_by_inheritance.html __ /blog/020_object_lifecycle_control.html __ /blog/072_utilize_dynamic_dispatch.html As we already wrote that `"Code Reuse By Inheritance"`__ has lots of problems and we consider it a code smell. You should always aim to use Dependency Injection, most likely Constructor Injection. But with test cases in PHPUnit we cannot do this because we have no control about how and when our test cases are created. There are a similar problem in other frameworks, like we discussed in `"Object Lifecycle Control"`__. We also blogged about `traits as a Code Smell`__, but let me show and explain why they might be fine to use in your test cases. So in PHPUnit it is common to reuse code by inheritance. Even by now we often create some base class providing common functionality. This works fine and is OK if this really defines an "is a"-relationship. Let's continue with an example from our `Page Object test repository`__. The patterns described here are usually not required for Unit Tests (which you should always also write) but mostly for integration or functional tests. __ /blog/063_code_reuse_by_inheritance.html __ /blog/020_object_lifecycle_control.html __ /blog/072_utilize_dynamic_dispatch.html __ https://github.com/QafooLabs/PageObjects An Example ========== The `example repository`__ implements functional tests for a website using the `Page Object pattern`__. This means that the tests access a website and assert on its contents, try to fill forms, submit them and click links. Such tests are a useful part of `your test mix`__, but should never be your only tests. Let's see the options for code reuse we employ in this little test project – and the reasoning behind it. We start with a simple test case:: class LoginTest extends FeatureTest { public function testLogInWithWrongPassword() { $page = (new Page\Login($this->session))->visit(Page\Login::PATH); $page->setUser(getenv('USER')); $page->setPassword('wrongPassword'); $newPage = $page->login(); $this->assertInstanceOf(Page\Login::class, $newPage); } // … } This test case extends from a base class ``FeatureTest`` to re-use its functionality. The base class uses PHPUnits ``setUp()`` method to setup and start the mink test driver which will act as a browser to access the website. And it provides a default ``tearDown()`` method to reset the browser state again. In this case the inheritance defines a clear "is a" relationship with the ``FeatureTest`` and it overwrites default methods in the PHPUnit stack which should be overwritten in any feature test. The ``FeatureTest`` again extends an ``IntegrationTest`` which is empty in this example but normally would provide access to the application stack to reset a database, access random services or something similar. We just do not need anything like this in this little test project. Since functional tests can be considered a superset of integration tests this is fine again and provides common functionality which belongs to all tests of this type. __ https://github.com/QafooLabs/PageObjects __ /blog/089_introduction_to_page_objects.html __ /blog/055_finding_the_right_test_mix.html Traits ====== Let's take a look at a slightly more complex test case now:: class DashboardTest extends FeatureTest { use Helper\User; // … public function testHasDemoOrganization() { $this->logIn(); $page = (new Page\Dashboard($this->session))->visit(Page\Dashboard::PATH); $organizations = $page->getOrganizations(); $this->assertArrayHasKey('demo', $organizations); return $organizations['demo']; } // … } In this case we using the ``Helper\User`` trait to include some functionality – it provides the ``logIn()`` method which is used in the test ``testHasDemoOrganization()``. Not every feature test might need this aspect and in "normal" software you would provide such helpers through constructor injection. But since we do not have any control on the test case creation we include the code using a trait. The trait enables code reuse – we can use it any test case which requires login. The trait extracts this concern and we do not clutter every test case requiring login with this kind of code. Whats The Difference? ===================== The trait helps us in this example, the code looks clean, so you might want to ask: **Why would traits ever be considered a code smell?** One of the most important reasons is that in a test case there probably won't be a reason to change a dependency without adapting the code (Open Closed Principle). In other words: There is no reason for dynamic dispatch. A trait establishes a dependency to another class which is defined by the name of the trait (instead of an instance of some class which could be a subtype). There is no easy way to change the actually used implementation from the outside. If you include a ``LoggerTrait`` there is no way to change the used ``LoggerTrait``, during tests or when the requirements change, without changing code. Traits establish a ``static`` dependency which is hard to mock and hard to replace during runtime or configuration. But we will never mock our test cases, right? And if the use cases change we will change the test cases. This can happen a lot as compared to unit tests. Especially (Open Source) libraries and extensible software commonly has the requirement that people should be able to change the behaviour without changing the code. Most likely because they do not have direct access to the code or it would have side effects to other usages of the code. But nobody uses your test cases in such a way, thus you are "allowed" to sin in here – at least a little bit. And there are no other options. This, generally, can be another reason to use traits. Traits are often an option when refactoring legacy software to temporarily use common code before we can migrate to sensible dependency injection. Being a code smell they even help knowing about places which are still not done. .. note:: If you want help finding the correct testing strategy for you and get a kickstart in testing – `book us for an on-site workshop`__. __ /services/workshops/testing.html Summary ======= In tests traits can be a great tool to reuse common code, while we still consider traits a code smell in almost every other case. .. Local Variables: mode: rst fill-column: 79 End: vim: et syn=rst tw=79 Trackbacks ========== Comments ========