Testing Workshop

Types of Tests

  • Unit
  • Integration
  • Acceptance

Unit
Test

 - atomic behaviour

- verifies correctness of behaviour

Unit
Test

 - atomic behaviour

- verifies correctness of behaviour

When unit test fails
you should know exactly why it fails

Let's use term
Isolated Tests
instead

Examples of Units

  • Product - manages product lifecycle
  • Credentials - authentication/authorization
  • CurrencyRate - converts currencies between  rates
  • SalesLimit - Calculates merchant sales limit
  • ProductRepository - Persistence of products

Integration Tests

Tests of interaction between components

Integration Tests

Tests of interaction between components

ARE A SCUM

Integration Tests

Tests of interaction between components

ARE A SCUM

J.B. Rainsberger

Integration Tests are a Scum

Internal and External

Quality

Growing object-oriented software guided by tests, Steve Freeman and Nat Pryce

Acceptance Tests

Tests of system behaviour from user perspective

Provides External quality

Could be written by QA

We are going to talk only about isolated tests

Isolation means boundaries

Unit Tests are help with

  • Requirements understanding
  • Top down decomposition
  • Your object API design
  • improving internal quality of your system

Unit Tests are not

  • Silver Bullet - you still need to think
  • for finding bugs - it allows to reduce mistakes.
  • QA replacement - it's only about internal quality
  • expensive - if your code is good enough

Good Unit Tests Are

  • Isolated
  • Checking one behaviour at a time
  • Repeatable
  • Independent
  • Readable
  • Doesn't contains logic of loops
  • Has meaningful name of test case!

Names...

// BAD

public function testPasses()
{
    $obj = new MyClass();
    
    $this->assertEquals(42, $obj->answer());
}

Names...

// GOOD

public function testGivesAnswerForLifeUniversAndEvrything()
{
    $deepThought = new DeepThought();
    
    $this->assertEquals(42, $deepThought->answer());
}

Unit test allowed to change

only when requirements changes

Unit test allowed to change

only when requirements changes

And they will...

Levels of unit tests

If we are testing classes, we could have this levels of complexity if class has:
 

  1. No dependencies, no state, just algorithm
  2. No dependencies, but has state
  3. At least one dependency, no state
  4. At least one dependency and also rely on it's internal state

Less efforts!

Simpler tests

Primitive Obsession

class RegiserUserAction
{
    private $users;
    private $emailValidator;
    private $passwordEncoder;
    private $notifier;

    // constructor...

    public function registerUser(string $email, string $password)
    {
        if (!$this->emailValidator->isEmailValid($email)
            throw new InvalidEmailAddress($email);

        $user = User::builder()
           ->withEmail($email)
           ->withPassword($this->passwordEncoder->encode($password)
           ->build();

        $this->users->add($user);
        $this->notifier->send(new WelcomeMessage($email));
    }

}

Eliminate Unnecessary Dependencies 

class RegiserUserAction
{
    private $users;

    // constructor...

    public function registerUser(EmailAddress $email, Password $password)
    {
        $user = User::builder()
           ->withEmail($email)
           ->withPassword($password)
           ->build();

        $this->users->add($user);
    }

}

Remove dependency from random factors

public function getActiveDiscount(): Discount
{
     $now = new \DateTime();
     // ...   
}

// pass data instead

public function getActiveDiscount(\DateTime $now): Discount
{
     // ...   
}

No Loops!

Use parametrized tests!

/**
 * @dataProvider subtractionData
 */ 
public function testSupportsSubtraction(int $b, int $expected)
{
    $number = new Number(10);
    $actual = $number->sub($b);
    
    $this->assertEquals($expected, $actual);
}

public function subtractionData()
{
    return [
        [1, 9],
        [2, 8],
        [7, 3],
    ];
}

Command Query Separation

public function command(): void
{
    // do something with internal state
}

public function query(): int
{
    return $this->someInternalState;
}

public function exceptThisCase(): int
{
    return array_pop($this->someCollectionOfIntegers);
}
  1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
  2. Any live cell with two or three live neighbours lives on to the next generation.
  3. Any live cell with more than three live neighbours dies, as if by overpopulation.
  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

Game of life

Roulet Weel

the wheel will spin for twenty seconds and the ball will stop on a random number

TDD Workshop

By Sergey Protko

TDD Workshop

  • 163