Quick Testing Tips: Write Unit Tests Like Scenarios – Matthias Noback

I’m a big fan of the BDD Books by Gáspár Nagy and Seb Rose, and I’ve read a lot about writing and improving scenarios, like Specification by Example by Gojko Adzic and Writing Great Specifications by Kamil Nicieja. I can recommend reading anything from Liz Keogh as well. Trying to apply their suggestions in my development work, I realized: specifications benefit from good writing. Writing benefits from good thinking. And so does design. Better writing, thinking, designing: this will make us do a better job at programming. Any effort put into these activities has a positive impact on the other areas, even on the code itself.

Unit tests vs automated scenarios

For instance, when you write a test in your favorite test runner (like PHPUnit), you’ll write code. You’ll focus on technology, and on implementation details (methods, classes, argument types, etc.):

$config = Mockery::mock(Config::class);
$config->shouldReceive('get')
    ->with('reroute_sms_to_email')
    ->andReturn('developers@org.nl');

$fallbackMailer = Mockery::mock(Mailer::class);
$fallbackMailer->shouldReceive('send')
    ->andReturnUsing(function (Mail $mail) {
        self::assertEquals('The message', $mail->plainTextBody());
        self::assertEquals('SMS for 0612345678', $mail->subject());
    });

$smsSender = new SmsSender($config, $fallbackMailer);
$smsSender->send('0612345678', 'The message');

It takes a lot of reading and interpreting before you even understand what’s going on here. When you write a scenario first, you can shift your focus to a higher abstraction level. It’ll be easier to introduce words from the business domain as well:

Given the system has been configured to reroute all SMS messages to the email address developers@org.nl
When the system sends an SMS
Then the SMS message will be sent as an email to developers@org.nl instead

When automating the scenario steps it will be natural to copy the words from the scenario into the code, establishing the holy grail of Domain-Driven Design – a Ubiquitous Language; without too much effort. And it’s definitely easier to understand, because you’re describing in simple words what you’re doing or are planning to do.

Most of the projects I’ve seen don’t use scenarios like this. They either write technology-focused scenarios, like this (or the equivalent using Browserkit, WebTestCase, etc.):

Given I am on "/welcome"
When I click "Submit"
Then I should see "Hi"

Or they don’t specify anything, but just test everything using PHPUnit.

Writing scenario-style unit tests

Although it may seem like having any kind of test is already better than having no tests at all, if you’re making an effort to test your code, I think your test deserves to be of a high quality. When aiming high, it’ll be smart to take advantage of the vast knowledge base from the scenario-writing community. As an example, I’ve been trying to import a number of style rules for scenarios into PHPUnit tests. The result is that those tests now become more useful for the (future) reader. They describe what’s going on, instead of just showing which methods will be called, what data will be passed, and what the result of that is. You can use simple tricks like:

  1. Givens should be in the past tense
  2. Whens should be in the present tense
  3. Thens should be in the future tense (often using “should” or “will”)

But what if you don’t want to use Behat or another tool that supports Gherkin (the formalized language for these scenarios)? The cool thing is, you can use “scenario language” in any test, also in unit tests. The trick is to just use comments. This is the unit test above rewritten with this approach:

// Given the system has been configured to reroute all SMS messages to the email address developers@org.nl
$config = Mockery::mock(Config::class);
$config->shouldReceive('get')
    ->with('reroute_sms_to_email')
    ->andReturn('developers@org.nl');

// When the system sends an SMS
$fallbackMailer = Mockery::spy(Mailer::class);
$smsSender = new SmsSender($config, $fallbackMailer);
$smsSender->send('0612345678', 'The message');

// Then the SMS message will be sent as an email to developers@org.nl instead
$fallbackMailer->shouldHaveReceived('send')
    ->with(function (Mail $mail) {
        self::assertEquals('The message', $mail->plainTextBody());
        self

Truncated by Planet PHP, read more at the original (another 3134 bytes)

Aller à la source
Author: