Lullabot: Write Unit Tests for Your Drupal 7 Code (part 2)

Planet Drupal - lun, 10/08/2015 - 19:12

This article is a continuation of Write Unit Tests for Your Drupal 7 Code (part 1), where I wrote about how important it is to have unit tests in your codebase and how you can start writing OOP code in Drupal 7 that can be tested with PHPUnit.

In this article I show you how this can be applied to a real life example.


I encourage you to start testing your code. Here are the most important points of the article:

Dependency Injection and Service Container

Jeremy Miller defines dependency injection as:

[...] In a nutshell, dependency injection just means that a given class or system is no longer responsible for instantiating their own dependencies.

In our MyClass we avoided instantiating CacheController by passing it through the constructor. This is a basic form of dependency injection. Acoording to Martin Fowler:

There are three main styles of dependency injection. The names I'm using for them are Constructor Injection, Setter Injection, and Interface Injection.

As long as you are injecting your dependencies, you will be able to swap those objects out with  their test doubles in your unit tests.

An effective way to pass objects into other objects is by using dependency injection via a service container. The service container will be in charge of giving the receiving class all the needed objects. Then, the receiving object will only need to get the service container. In our System Under Test (SUT), the service container will yield the actual objects, while in the unit test domain it will deliver mocked objects. Using a service container can be a little bit confusing at first, or even daunting, but it makes your API more stable and robust.

Using the service container, our example is changed to:

class MyClass implements MyClassInterface { // ... public function __construct(ContainerInterface $service_container) { $this->cacheController = $service_container->get('cache_controller'); $this->anotherService = $service_container->get('my_services.another_one'); } // ... public function myMethod() { $cache = $this->cacheController->cacheGet('cache_key'); // Here starts the logic we want to test. // ... } // ... }

Note that if you need to use a new service called 'my_services.another_one', the constructor signature remains unchanged. The services need to be declared separately in the service providers.

Dependency injection and service encapsulation is not only useful for mocking purposes, but also to help you to encapsulate your components –and services–. Borrowing, again, Jeremy Miller’s words:

Making sure that any new code that depends on undesirable legacy code uses Dependency Injection leaves an easier migration path to eliminate the legacy code later with all new code.

If you encapsulate your legacy dependencies you can ultimately write a new version and swap them out. Just like you do for your tests, but with the new implementation.

Just like with almost everything, there are several modules that will help you with these tasks:

  • Registry autoload will help you to structure your object oriented code by giving you autoloading if you follow the PSR-0 or PSR-4 standards.
  • Service container will provide you with a service container, with the added benefit that is very similar to the one that Drupal 8 will ship with.
  • XAutoload will give you both autoloading and a dependency injection container.

With these strategies, you will write code that can have it’s dependencies mocked. In the previous article I showed how to use fake classes or dummies for that. Now I want to show you how you can simplify that by using Mockery.

Mock your objects

Mockery is geared towards providing even more flexibility when creating mocks. Mockery is not tied to any test framework which makes it useful even if you decided to move away from PHPUnit.
In our previous example the test case would be:

// Called from the test case. $fake_cache_response = (object) array('data' => 1234); $cache_controller_fake = \Mockery::mock('CacheControllerInterface'); $cache_controller_fake->shouldReceive('cacheGet')->andReturn($fake_cache_response); $object = new MyClass($cache_controller_fake); $object->myMethod();

Here, I did not need to write a CacheControllerFake only for our test, I used Mockery instead.
PHPUnit comes with a great mock builder as well. Check its documentation to explore the possibilities. Sometimes you will want to use one or the other depending on how you want to mock your dependency, and the tools both frameworks offer. See the same example using PHPUnit instead of Mockery:

// Called from the test case. $fake_cache_response = (object) array('data' => 1234); $cache_controller_fake = $this ->getMockBuilder('CacheControllerInterface') ->getMock(); $cache_controller_fake->method('cacheGet')->willReturn($fake_cache_response); $object = new MyClass($cache_controller_fake); $object->myMethod();

Mocking your dependencies can be hard –but valuable– work. An alternative is to include the real dependency, if it does not break the test runner. The next section explains how to save some time using Drupal Unit Autoload.

Cutting corners

Sometimes writing tests for your code makes you realize that you need to use a class from another Drupal module. The first instinct would be, «no problem, let’s create a mock and inject it in place of the real object». That is a very good approach. However, it can be tedious to create and maintain all these mocks, especially for classes that don’t depend on a bootstrap. That code could just be required in your test case.

Since your unit test can be considered a standalone PHP script that executes some code –and makes some assertions– you could just use the require_once statement. This would include the code that contains the class definitions that your code needs. However, a better way of achieving this is by using Composer’s autoloader. An example composer.json in your tests directory would be:

{ "require-dev": { "phpunit/phpunit": "4.7.*", "mockery/mockery": "0.9.*" }, "autoload": { "psr-0": { "Drupal\\Component\\": "lib/" }, "psr-4": { "Drupal\\typed_entity\\": "../src/" } } }

With the previous example, your unit test script would know how to load any class in the Drupal\Component and Drupal\typed_entity namespaces. This will save you from writing test doubles for classes that you don’t have to mock.

At this point, you will be tempted to add classes from your module’s dependencies. The big problem is that every drupal module can be installed in a different location, so a simple ../../contrib/modulename will not do. That would only work for your installation, but not for others. This is one of the reasons why I wrote with Christian Lopez (penyaskito) the Drupal Unit Autoload. By adding Drupal Unit Autoload to your composer.json you can add references to Drupal core and other contributed modules. The following example speaks for itself:

{ "require-dev": { "phpunit/phpunit": "4.7.*", "mockery/mockery": "0.9.*", "mateu-aguilo-bosch/drupal-unit-autoload": "0.1.*" }, "autoload": { "psr-0": { "Drupal\\Component\\": "lib/", "Symfony\\": ["DRUPAL_CONTRIB<service_container>/lib"] }, "psr-4": { "Drupal\\typed_entity\\": "../src/", "Drupal\\service_container\\": ["DRUPAL_CONTRIB<service_container>/src"] }, "class-location": { "\\DrupalCacheInterface": "DRUPAL_ROOT/includes/cache.inc", "\\ServiceContainer": "DRUPAL_CONTRIB<service_container>/lib/ServiceContainer.php" } } }

We added mateu-aguilo-bosch/drupal-unit-autoload to the testing setup, so we can include Drupal aware autoloading options to our composer.json.

Striving for the green

One of the most interesting possibilities that PHPUnit offers is code coverage. Code coverage is a measure used to describe the degree to which the methods are tested. Having a high coverage reduces the number of bugs in your code greatly. Moreover, adding new code with test coverage will help you ensure that you are not introducing any bugs along with the new code.

PhpStorm coverage integration.

A test harness with coverage information is a valuable tool to include in your CI tool. One way to execute all your PHPUnit cases is by adding a phpunit.xml file describing where the tests are located and other integrations. Running the phpunit command in that folder will execute all the tests.

Another good 3rd party service is coveralls. It will tell your CI tool how the coverage of your code will change with the pull request –or patch– in question; since Coveralls knows about most of the major CI tools almost no configuration is needed. Coveralls also provides a web UI to see what parts of the code are covered and the ones that are not.

Coveralls.io dashboard.

Write tests until you get 100% test coverage, or a satisfactory number. The higher the number the higher the confidence that the code is bug free.

Read the next section to see all these tools in action in contributed Drupal 7 module.

A real life example

I applied the tips of this article to the TypedEntity module. TypedEntity is a nice little module that helps you get your code organized around your Drupal entities and bundles, as first class PHP objects. This module will help you to change your mindset.
Make sure to check the contents of the tests/ directory. In there you will see real life examples of a composer.json and test cases. To run the tests follow these steps:

  1. Download the latest version of the module in your Drupal site.
  2. Navigate to the typed_entity module directory.
  3. Execute tests/run-tests.sh in your terminal. You will need to have composer installed to run them.

This module executes both PHPUnit and Simpletest tests on every commit using Travis CI configured through .travis.yml and phpunit.xml.

Another great example, this one with a very high test coverage, is the Amazon S3 module. A new release of this useful module was created recently by fellow Lullabot Andrew Berry, and he added unit tests!

Do you do things differently in your Drupal 7 modules? Leave your thoughts in the comments!

Catégories: Elsewhere

SitePoint PHP Drupal: Drupal 8 Theming Revamped – Updates and New Features

Planet Drupal - lun, 10/08/2015 - 18:00

If you are a Drupal developer who has dabbled in theming older versions of Drupal (5, 6, 7) you understand why frustration is the trusty companion of any Drupal themer. Luckily, though, Drupal 8 promises so many improvements that even the Angry Themer is happy for a change. It is only natural we jump in […]

Continue reading %Drupal 8 Theming Revamped – Updates and New Features%

Catégories: Elsewhere

Daniel Pocock: Going 4K

Planet Debian - lun, 10/08/2015 - 17:59

I recently started using a 4K monitor. I've generally had a positive experience and thought I would share some comments about it.

What can you do with 4K resolution?
  • View the entire London Underground map or Swiss railway map without having to zoom or scroll
  • Run a mail client like Thunderbird/Icedove and an IRC client side-by-side
  • Run Eclipse and the Android emulator side-by-side
  • Run Eclipse and some other program you are debugging side-by-side
  • When reading a PDF, show a whole page at a time but only using half the screen
Are there hardware issues?

Originally the hardware was expensive and people would have to do things like using multiple cables to join their video card to their 4K screen. The latest generation of video cards and monitors don't have this problem.

I chose to use NVIDIA's entry-level Quadro K420 card, the smallest and least expensive Quadro supporting 4K. The PC is a HP Z800 workstation. HP suggests they only certify the K420 with their latest workstations (e.g. Z840) but I have observed no problem using it in a Z800.

The monitor is a 32" BenQ display. After reading comments from other 4K users, I felt that any monitor less than 30" would not help me get the most from the world of 4K.

The monitor connects to the video card using a DisplayPort 1.2 cable that was in the box with the monitor.

I've been able to get sound through the monitor but I found the volume is always a bit low and at one point sound stopped completely.

What about software issues?

Everything just appeared to work although the fonts are a bit smaller than what I'm used to. This appears to be related to DPI issues. DPI is not automatically detected and several applications have their own way of handling non-standard DPI. Discussion about this started on the debian-devel mailing list, given that non-standard DPI will be an issue with many newer monitors, handheld devices and more exotic displays such as televisions.

Some web sites just don't look so good when the browser window is maximized to 4K resolution while others use the extra space really well. For example, using the AirBNB search, the maps show more detail but using another hotel booking site I found that it was still trying to render a map within a constant size region using less than ten percent of the page.

Catégories: Elsewhere

Appnovation Technologies: Overriding queue classes used in the Batch API in Drupal 7

Planet Drupal - lun, 10/08/2015 - 17:04
There are lots of benefits in working with a platform such as Drupal, that come packed full of goodies for developers like myself and my colleagues, that enable us to build intelligent and innovative solutions.
Catégories: Elsewhere

Nuvole: Drupal 8 Configuration Management: beware of pitfalls

Planet Drupal - lun, 10/08/2015 - 16:35
Nuvole's Configuration Management workshop at Drupalaton 2015 in Hungary

Drupalaton in Hungary last weekend gave the opportunity for a 90 minutes workshop about Configuration Management (CMI) and how it changes a Drupal developers work flow with the coming Drupal 8.

CMI allows you to have the whole site configuration under control and that is a great improvement. But it also comes with its pitfalls. Developers have to be more vigilant when sharing configuration with each other: mindless exporting configuration may override the configuration shared by others, mindless importing may override one's own work and forgetting to import merged configuration leads to ignoring the work of your co-developers at a later stage.

CMI, while it is much more awesome than anything we have in Drupal 7, is also much more unforgiving. So it would be all too easy to not use it properly and what it was designed for and then wonder why you can't have nice things.

Fortunately, a methodical git workflow can help recover from mistakes (except importing configuration before exporting it first). So don't forget the order of the steps:

  1. config export
  2. git commit
  3. git merge
  4. config import

And do it often to keep the merges and failures simple and easy to resolve.

Please find the full Drupalaton 2015 workshop slides attached below.

Tags: Drupal PlanetDrupal 8Attachments:  CMI presentation Drupalaton 2015
Catégories: Elsewhere


Subscribe to jfhovinne agrégateur - Elsewhere