LEARNING TDD/BDD

Session 3 - Integration Tests

Presented by Guyllaume Cardinal for Cogeco Connexion

What we've seen so far

Basics

TDD vs BDD

Benefits of TDD

Coding principles: SOLID, GRASP Single Responsability principle

Code smells

What we've seen so far

Unit Tests

Tests a single unit (class)

Focus on behavior

Mocks are used to isolate what is being tested

Testing Pyramid

So, what's an integration test?

We have tests for each classes, but we need to make sure they properly interact together.

So, what's an integration test?

So, what's an integration test?

Mocks can only go so far

(without becoming unmanageable)

You need to confirm proper behavior

There will be some overlap with your unit tests, but very few

We test for a feature's proper integration

(I feel no shame doing this pun)

So, what's an integration test?

<?php

/** A million imports **/

class FizBuzzTest extends TestCase {

  /**
   * @test
   */
  public function it_should_print_an_array() {
    $fizzBuzzArrayPrinter = new FizBuzzArrayPrinter(new FizBuzz());

    $result = $fizzBuzzArrayPrinter->print(15, 1);

    $this->assertTrue(is_array($result));
    $this->assertContains(11, $result);
    $this->assertContains('fiz', $result);
    $this->assertContains('buzz', $result);
    $this->assertContains('fizbuzz', $result);
  }
}

What's different here?

Naming

  • We don't name the test like the class, cause we're not testing one class. We're testing a whole behavior
  • This helps us focus our test's goal: testing a feature's integration

What's different here?

Real Objects Only

  • No mocks are used here. We test the actual behavior of classes interacting together.
  • Hence, these tests are more brittle. To parry this, we reduce the the surface of our code they cover.

Warning!

Database and Web Services become an issue!

Interaction with these is slow and very unreliable

It's also a sign of binding in your code

What's the solution?

It should already be in place! Abstractions to the rescue:

<?php

class EntityRepository
{

  /**
   * @var DatabaseManager
   */
  protected $dbManager;

  public function __construct(DatabaseManager $dbManager) {
    $this->dbManager = $dbManager;
    $this->dbManager->queryBuilder()->get('');
    // And whatever stuff
  }
}

Yay! Abstractions!

You're now using an abstraction rather than interacting directly with your database or web service.

Suddenly you're able to inject test versions of these dependencies instead of the real ones.

Almost like a mock, but you're using real code and real data.

You can now have a DatabaseManager and TestDatabaseManager who both connect to something different.

Yay! Abstractions!

<?php

namespace spec\Integration;

class FizBuzzTest extends TestCase {

  /**
   * @test
   */
  public function it_should_use_an_abstraction() {
    $databaseManager = new TestDatabaseManager(
      new SQLiteDatabaseAdapter(),
      'localhost:10000',
      'user',
      'password'
    );
    $repository = new EntityRepository($databaseManager);

    /** And we test! */
  }
}

How does that all apply to our FizzBuzz example?

Onwards to coding!

Made with Slides.com