Testing: Isolating What You Test in PHP

An important principle of productive testing is that the thing you’re testing should be as isolated as possible. Ideally, there is no global state or long-lived resource outside of your test function whose contents or behavior could change the results of the test function. Your test functions should produce the same result regardless of the order in which they are run.

Consider the validate_fom() function from Example 7-13. To validate incoming data, it examines the $_POST array and uses filter_input() to operate directly on INPUT_POST. This is a concise way to access the data that needs validating. However, in order to test this function, it looks like wed have to adjust values in the auto-global $_POST array. What’s more, that wouldn’t even help filter_input() work properly. It always looks at the underlying, unmodified submitted form data, even if you change the values in $_POST.

To make this function testable, it needs to be passed the submitted form data to vali­date as an argument. Then this array can be referenced instead of $_POST, and filter_var() can examine the array’s elements. Example 13-7 shows this isolated version of the validate_fom() function.

Example 13-7. Validating form data in isolation

function validate_form($submitted)

{

$errors = array();

$input = array();

$input[‘age’] = filter_var($submitted[‘age’] ?? NULL, FILTER_VALIDATE_INT);

if ($input[‘age’] === false)

{

$errors[] = ‘Please enter a valid age.’;

}

$input[‘price’] = filter_var($submitted[‘price’] ?? NULL,

FILTER_VALIDATE_FLOAT);

if ($input[‘price’] === false) {

$errors[] = ‘Please enter a valid price.’;

}

$input[‘name’] = trim($submitted[‘name’] ?? );

if (strlen($input[‘name’]) == 0) {

$errors[] = “Your name is required.”;

}

return array($errors, $input);

}

The first argument to filter_var() is the variable to filter. PHP’s normal rules about undefined variables and undefined array indices apply here, so the null coalesce oper­ator is used ($submitted[‘age’] ?? NULL) to provide NULL as the value being filtered if it’s not present in the array. Since NULL is not a valid integer or float, filter_var() returns false in those cases, just as it would if an invalid number was provided.

When the modified validate_form() function is called in your application, pass $_POST as an argument:

list ($form_errors, $input) = validate_form($_POST);

In your test code, pass it an array of pretend form input that exercises the situation you want to test and then verify the results with assertions. Example 13-8 shows a few tests for validate_form(): one that makes sure decimal ages are not allowed; one that makes sure prices with dollar signs are not allowed; and one that makes sure val­ues are returned properly if a valid price, age, and name are provided.

Example 13-8. Testing isolated form data validation

// validate_form() is defined in this file

include ‘isolate-validation.php’;

class IsolateValidationTest extends PHPUnit_Framework_TestCase {

public function testDecimalAgeNotValid() {

$submitted = array(‘age’ => ‘6.7’,

‘price’ => ‘100’,

‘name’ => ‘Julia’);

list($errors, $input) = validate_form($submitted);

// Expecting only one error — about age

$this->assertContains(‘Please enter a valid age.’, $errors);

$this->assertCount(1, $errors); 

}

public function testDollarSignPriceNotValid() {

$submitted = array(‘age’ => ‘6’,

‘price’ => ‘$52’,

‘name’ => ‘Julia’);

list($errors, $input) = validate_form($submitted);

// Expecting only one error — about age

$this->assertContains(‘Please enter a valid price.’, $errors);

$this->assertCount(1, $errors);

}

public function testValidDataOK() {

$submitted = array(‘age’ => ’15’,

‘price’ => ‘39.95’,

// Some whitespace around name that

// should be trimmed

‘name’ => ‘Julia’);

list($errors, $input) = validate_form($submitted);

// Expecting no errors

$this->assertCount(0, $errors);

// Expecting 3 things in input

$this->assertCount(3, $input);

$this->assertSame(15, $input[‘age’]);

$this->assertSame(39.95, $input[‘price’]);

$this->assertSame(‘Julia’, $input[‘name’]);

}

}

Example 13-8 uses a few new assertions: assertContains(), assertCount(), and assertSame(). The assertContains() and assertCount() assertions are useful with arrays. The first tests whether a certain element is in an array and the second checks the size of the array. These two assertions express the expected condition about the $errors array in the tests and about the $input array in the third test.

The assertSame() assertion is similar to assertEquals() but goes one step further. In addition to testing that two values are equal, it also tests that the types of the two values are the same. The assertEquals() assertion passes if given the string ‘130’ and the integer 130, but assertSame() fails. Using assertSame() in testValidDataOK() checks that the input data variable types are being set properly by filter_var().

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 *