fromJune 2014
Column:

Testing 1 2 3

Exceptional Exceptions: Success and Failure in Behat
0

In Behat, anything that doesn’t throw an exception is treated as a success. Every custom step definition presents the developer with the responsibility to check for exceptions and the opportunity to increase the value of scenario automation by providing meaningful feedback about failure.

We’ll explore this opportunity in the custom step definitions in the following scenario for a site whose main source of income results from presenting a discounted sale product on the front page:

	Scenario: Daily Deal discount 
     	  Given I am on the homepage 
   	  When I click the Daily Deal "Buy now!" link 
   	  Then I should see the product title 
   	  And the sale price should reflect the discount advertised on the homepage 

The product and discount change daily, so instead of matching literal text, the first custom step will use a css selector to find the discount amount and product title.

To start implementing, run the scenario to generate stubs for the custom steps. Note: --append-snippets can be used to write the output directly to the FeatureContext.php file.

It wouldn’t be uncommon to find the first custom step implemented with something like:

  /**
  * @When /^I click the Daily Deal "([^"]*)" link$/
  */
  public function iClickTheDailyDealLink($linkText) {
    $page = $this->getSession()->getPage();
    // Limit to the Daily deal block
    $el = $page->find('css','#daily');
    // Find the title for use in the next step
    $this->product = $el->find('css','h2')->getText();
    // Find the discount amount for use in the next step
    $this->discount = $el->find('css','span#dd-discount')->getText();
    // Go to the product page
    $link = $el->findLink($linkText);
    if (empty($link)) {
        throw new Exception('Link not found');
    }
    $link->click();
  }

There are many ways to improve this step definition, but with respect to exceptions, the first and perhaps most important thing is:

Every Action Needs an Exception

Do not act on something without verifying that it has returned a value. For example, combining find() with getText() above will result in “PHP Fatal error: Call to a member function getText() on a non-object” if the find() does not succeed. That can be addressed easily enough during active development, but when the test is running as part of continuous integration or under pressure before a big deployment, a fatal error will quite frustratingly kick you out of the whole test run.
To prevent fatal errors and provide precise feedback, supply exceptions:

  /**
   * @When /^I click the Daily Deal "([^"]*)" link$/
   */
  public function iClickTheDailyDealLink($linkText) {
    $page = $this->getSession()->getPage();
    $el = $page->find('css','#daily');
    if(empty($el)) {
      throw new Exception("Element not found.");
    }
    // find the title for use in a later step
    $product = $el->find('css','h2');
    if(empty($product)) {
      throw new Exception("Element not found.");
    }
    $this->product = $product->getText();
    // find the discount amount for use in a later step
    $discount = $el->find('css','span#dd-discount');
    if(empty($discount)) {
      throw new Exception("Element not found.");
    }
    if (empty($link)) {
          throw new Exception('Link not found');
    }
    $link->click();
  }

More Detailed Feedback

“Link not found” can be plenty of information when you’re actively working on the code that provides the link, and while the project is fresh in your mind, but later it leaves a lot of detective work. To diagnose a failure in the step above, you need to navigate to the application and inspect the elements to see what’s actually happening. Navigating the click path is simple in this example, but it can be quite complicated and time consuming, and it can also require a noticeable cognitive shift to get back into the project head space.

Even more important, later in the life cycle of the project, it’s very possible that the first person to try to interpret a test failure won’t be a developer at all, and may well be someone who never knew the project context. For all these reasons, cultivating the habit of providing context will contribute greatly to the long-term value of automation.

Include What You Were Looking For

Custom step definitions which do nothing more than narrow down a web page to a specific section and then verify or act on an element with it are so common, it’s probably worth abstracting the message with a helper function and improving the feedback:

  public function selectorNotFound($selector) {
    return sprintf("The selector '%s 'was not found.", $selector);
  }

…
    // narrow to the Daily Deal block
    $selector = '#daily';
    $el = $page->find('css',$selector);
    if (empty($el)) {
      throw new Exception ($this->selectorNotFound($selector));
    }

Output:

The selector "#daily" was not found.

Print the URL

One very straightforward habit is to provide the URL of the page where the failure occurred. This is so frequently useful, it’s worth creating a helper function for this, too:

  public function url() {
    return sprintf("Error on '%s'. \n", $this->getSession()->getCurrentUrl());
  }

Here’s an implementation of the next step, combining these ideas:

  /**
   * @Then /^I should see the product title$/
   */
  public function iShouldSeeTheProductTitle() {
    $page = $this->getSession()->getPage();
    $selector = 'h1';
    $title = $page->find('css',$selector);
    if(empty($title)) {
      throw new Exception ($this->url() . $this->selectorNotFound($selector));
    }
    $title = $title->getText();
    if($title !=  $this->product) {
      throw new Exception ($this->url());
    }
  }

If there’s a change in markup, you’ll see:

  Error on http://example.com/turnip-twaddler.
  The selector "h1" was not found.

If there’s no matching product title, you’ll see:

  Error on http://seven.l/turnip-twaddler.
  ‘Turnip Twaddler’ does not match ‘Karrot Kutter’

Creating specific and very readable exceptions opens the possibility of keeping developers focused on coding – and offloading the reproduction and triage of failures to people on your team who spend more time with the application functionality in the first place. It requires some initial adjustments, but once the habits are in place, those habits pay off in the long term.

Advertisement

From our blog

Entity Storage, the Drupal 8 Way

In Drupal 7 the Field API introduced the concept of swappable field storage.

The Drupal 6 to 8 Upgrade Challenge - Part 2

Having concluded the readiness assessment, we turn next to migrating the content and configuration. In reality, there’s little chance that we would migrate anything but the blogs from our old site. For the sake of giving Migrate in Core a workout with real conditions, however, we’re going to upgrade with core’s Migrate Drupal module rather than rebuilding.

The Drupal 6 to 8 Upgrade Challenge - Part 1

Nathaniel Catchpole , the Drupal 8 release maintainer and Tag1 Senior Performance Engineer, suggested that Drupal shops everywhere could support the

DrupalCon Austin

The entertainment industry is not for the faint of heart.

Drupal Watchdog Joins the Linux New Media Family
Drupal Watchdog 6.01 is the first issue published by Linux New Media.

Drupal Watchdog 6.01 is the first issue published by Linux New Media. Come see the Drupal Watchdog team at DrupalCon 2016!

Drupal Watchdog was founded in 2011 by Tag1 Consulting as a resource for the Drupal community to share news and information. Now in its sixth year, Drupal Watchdog is ready to expand to meet the needs of this growing community.

Drupal Watchdog will now be published by Linux New Media, aptly described as the Pulse of Open Source.

Welcome to DrupalCon Barcelona - The Director's Cut

For all you schedule-challenged CEOs – and ADHD coders – this Abbreviated Official Director’s Cut is just what the doctor ordered.

Welcome to DrupalCon - The Barcelona Edition

Did we have fun in Barcelona?
OMG, yes!

Did we eat all the tapas on the menu and wash them down with pitchers of sangria?
Yes indeed!

Recursive Closures and How to Get Rid of Them

This came up while manipulating taxonomy children and their children recursively, so it’s as not far from Drupal as you’d think.