Writing maintainable unit tests

As the last part, before leaving the unit tests behind, I wanted to show some additional features provided by Codeception that have been already introduced in Chapter 1, The Testing Mindset.

Codeception has been created with modularity and flexibility in mind, so anything else is your responsibility. In particular, you might have already noticed that our UserTest class has grown quite a bit.

So, what would happen if a change in the interface or in the way our model works breaks our tests?

It's quite clear, especially if you're working in a team or even more if your code gets handed over to other people to maintain, that you need clear rules so that everybody agrees on how to write the code, as a starter, and tests.

I've already highlighted in Chapter 4, Isolated Component Testing with PHPUnit, that one of the very basic things I've started doing with the teams I've worked with and with my own code, is to define precise and simple rules, which aim at the clarity and readability of the code. I've seen way too many "developer rockstars" that show off how good they are at writing compressed code, nesting variables, and hiding multiple assignments. Writing code that ends up being obfuscated might be fun if that code is a throwaway.

Code legibility ends up being one of the ways I've seen companies select candidates, and a very quick test is to have someone read your code and be able to get what it does without asking.

Tests shouldn't be treated with less care than your application code: if done properly, tests represent a way of documenting how things are supposed to be working and how they should be used. As soon as your class provides more and more functionality, your test classes will start to grow and you need to be prepared to face a refactor and introduce regression testing when a change in the application happens or a bug is introduced.

Using BDD specification testing

Codeception provides a nice and compact tool called Specify, which we have already introduced previously.

With PHPUnit alone, we only have methods to split our tests; using Specify, we have another layer of organization: The method becomes our story and our specification blocks our scenarios.

Just for documentation purposes, it should be noted that PHPUnit has its own BDD-compatible syntactic sugar with the given(), when() and then() methods, as explained at https://phpunit.de/manual/3.7/en/behaviour-driven-development.html. You can still use this syntax, if you are more used to it.

As a clearer example, we can group all validation rules within the same test and split the definition of what we're doing by using Specify blocks, as follows:

use Specify;

public function testValidationRules()
{
    $this->specify(
        'user should not validate if no attribute is set',
        function () {
 verify_not($this->_user->validate());
 }
    );

    $this->specify(
        'user should validate if all attributes are set', 
        function () {
            $this->_user->attributes = [
                'username'=>'valid username',
                'password'=>'valid password',
                'authkey' =>'valid authkey'
            ];
            verify_that($this->_user->validate());
        }
    );
}

As we can see, we are now aggregating two of our previous tests within a single method and grouping them within two blocks of specify() calls.

Specify is defined as a trait; this is the reason why you need to both use the namespace in the outermost global scope and load it within the test class:

namespace testscodeceptionunitmodels;

use CodeceptionSpecify;
use yiicodeceptionTestCase;
// other imported namespaces

class UserTest extends TestCase
{
    use Specify;
    
    // our test methods will follow
    // we can now use $this->specify()
}

As you can see specify() requires only two arguments: a simple description of the scenario that we are about to define, and an anonymous function that contains the assertions we want to do.

At this point, we can either use the PHPUnit classic assertions we've used until now or try to empower BDD style assertions. Verify, a small and nifty package, will provide you this capability, allowing you to use methods such as verify(), verify_that(), and verify_not().

From the earlier specified scenarios, consider the following line of code:

verify_not($this->_user->validate());

This is exactly the same as using the PHPUnit assertion:

$this->assertFalse($this->_user->validate());

In order to perform more elaborate assertions, we can instead use verify() in the following way:

$this->specify(
    'user with username too long should not validate',
    function () {
        $this->_user->username = 'this is a username longer than 24 characters';

        verify_not($this->_user->validate('username'));
        verify($this->_user->getErrors('username'))->notEmpty();
    }
);

Tip

There are plenty of other assertions that can be used and can be found at the project homepage at https://github.com/Codeception/Verify.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset