Testing: Test-Driven Development in PHP

A popular programming technique that makes extensive use of tests is called test- driven development (TDD). The big idea of TDD is that when you have a new feature to implement, you write a test for the feature before you write the code. The test is your expression of what you expect the code to do. Then you write the code for the new feature so that the test passes.

While not ideal for every situation, TDD can be helpful for providing clarity on what you need to do and helping you build a comprehensive set of tests that cover your code. As an example, we can use TDD to add an optional feature to the restaurant_check() function that tells it to include the tax in the total amount when calculating the tip. This feature is implemented as an optional fourth argument to the function. A true value tells restaurant_check() to include the tax in the tip- calculation amount. A false value tells it not to. If no value is provided, the function should behave as it already does.

First, the test. We need a test that tells restaurant_check() to include the tax in the tip-calculation amount and then ensures that the total check amount is correct. We also need a test that makes sure the function works properly when it is explicitly told not to include the tax in the tip-calculation amount. These two new test methods are shown in Example 13-9. (For clarity, just the two new methods are shown, not the whole test class.)

Example 13-9. Adding tests for new tip-calculation logic

public function testTipShouldlncludeTaxQ {

$meal = 100;

$tax = 10;

$tip = 10;

// 4th argument of true says that the tax should be included

// in the tip-calculation amount

$result = restaurant_check($meal, $tax, $tip, true);

$this->assertEquals(121, $result);

}

public function testTipShouldNotIncludeTax() {

$meal = 100;

$tax = 10;

$tip = 10;

// 4th argument of false says that the tax should explicitly

// NOT be included in the tip-calculation amount

$result = restaurant_check($meal, $tax, $tip, false);

$this->assertEquals(120, $result);

}

It should not be surprising that the new test testTipShouldIncludeTax() fails:

PHPUnit 4.8.11 by Sebastian Bergmann and contributors.

…F.

Time: 138 ms, Memory: 13.50Mb

There was 1 failure:

1) RestaurantCheckTest::testTipShouldIncludeTax

Failed asserting that 120.0 matches expected 121.

RestaurantCheckTest.php:40

FAILURES!

Tests: 5, Assertions: 5, Failures: 1.

To get that test to pass, restaurant_check() needs to handle a fourth argument that controls the tip-calculation behavior, as shown in Example 13-10.

Example 13-10. Changing tip calculation logic

function restaurant_check($meal, $tax, $tip, $include_tax_in_tip = false)

{

$tax_amount = $meal * ($tax / 100 );

if ($include_tax_in_tip)

{

$tip_base = $meal + $tax_amount;

} else {

$tip_base = $meal;

}

$tip_amount = $tip_base * ($tip / 100);

$total_amount = $meal + $tax_amount + $tip_amount;

return $total_amount;

}

With the new logic in Example 13-10, the restaurant_check() function reacts to its fourth argument and changes the base of what the tip is calculated on accordingly. This version of restaurant_check() lets all the tests pass:

PHPUnit 4.8.11 by Sebastian Bergmann and contributors.

Time: 120 ms, Memory: 13.50Mb

OK (5 tests, 5 assertions)

Because the test class includes not just the new tests for this new functionality but all of the old tests as well, it ensures that existing code using restaurant_check() before this new feature was added continues to work. A comprehensive set of tests provides reassurance that changes made to the code don’t break existing functionality.

Source: Sklar David (2016), Learning PHP: A Gentle Introduction to the Web’s Most Popular Language, O’Reilly Media; 1st edition.

Leave a Reply

Your email address will not be published. Required fields are marked *