fromAugust 2015

Testing, Testing... One... Two...Three

Moving Drupal 8 Forward

PhotoWriting tests for a project is a terrific way to learn about its underlying code. In the case of Drupal 8, nearly all the code is a complete overhaul from Drupal 7. For folks familiar with the inner workings of Drupal 7, writing PHPUnit tests for a given part of the codebase is a perfect way to understand what has changed.

As of June 2015, there are 566 active issues in the Drupal 8.0.x queue tagged with Needs tests. There’s lots of room for contribution for folks who can write a test. Since this column is dedicated to testing, let's look at the benefits of pushing Drupal 8 forward through test writing.

There are three different types of tests in Drupal 8:

  • Unit tests (PHPUnit) are used to test that the individual classes function as expected in relative isolation.
  • Integration tests (KernelTestBase) expand from this concept, but still only pull in code needed to test.
  • Functional tests (WebTestBase and BrowserTestBase) bring everything together, perform a full site install, and then assert the expected behavior.

Note that since the functional tests perform a full site install, they are quite slow, and should only be used when integration or unit tests are insufficient.)

To get started, find an issue in the “Needs work” or “Needs review” state that already has a patch and is tagged “Needs tests.” If the issue already has tests, update the issue to remove that tag and move along, or review the issue, give feedback, and then move along.

When a patch that only contains the fix is found, the next step is to backward-engineer the bug. The test must fail without the patch – meaning it shows that this is a bug – and then, with the patch, the test must pass. This pattern makes it easy for overworked core committers to see that the problem has been demonstrated and fixed.

For folks writing their first test, the ideal issue will have clearly defined “steps to reproduce” in the issue summary. The test then is essentially an automated way of reproducing those.

Let's use Issue #2482857: Cannot delete a book parent as an example.
To reproduce:

  1. Enable the Book module.
  2. Add a top-level book node.
  3. Add one or more child book nodes.
  4. Attempt to delete the parent node, note the exception.
  5. Try again and it succeeds.
  6. Visit one of the child nodes and note the fatal error.

The bug can be verified by manually performing those steps, but our test will ensure the fix doesn't regress later. Since this is testing the book module, tests will be found in core/modules/book/src/Tests or in core/modules/book/tests for unit tests. This test will be functional since it cannot be easily reproduced by a unit or integration test.

BookTest looks like a good starting point; it already enables the book module.

   * Modules to install.
   * @var array
  public static $modules = array('book', 'block', 'node_access_test');

Even better, it already has a helper function to build out a book with child nodes, BookTest::createBook(). The test also already includes one to test book deletion: BookTest::testBookDelete(). This means steps 1-3 above are essentially completed, so we just need to do 4-6:

     // Tests directly deleting a book parent.
     $nodes = $this->createBook();
     $this->drupalGet('node/' . $this->book->id() . '/delete');
     $this->assertRaw(t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $this->book->label()]));
     // Delete parent, and visit a child page.
     $this->drupalPostForm('node/' . $this->book->id() . '/delete', [], t('Delete'));
     $this->drupalGet('node/' . $nodes[0]->id());

The drupalGet and drupalPostForm methods are responsible for actually sending GET and POST requests to the test installation of Drupal, so we're really just clicking around the site here to achieve the steps to reproduce from above.

Once you have a failing test that illustrates the issue, this is posted as a test-only patch, meaning it is expected to fail. It is then combined with the fix for a patch that will pass. If you file an issue – or come across one without a patch – making the test patch to show the failure will greatly help moving the issue along since the person that eventually fixes it won't have to write that test, and it eliminates the need for manual testing as the patch is being written.

Whether you're just looking to learn more about the underlying code of Drupal 8, or want to really help push it out the door, writing tests is a great way to do both!

In future columns, I plan to dive deeper into the three test types mentioned above, as there is much to discover within.

Image: "Equilibrium 8_8_8" by Felipe Gabaldón is licensed under CC BY 2.0