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!
TDD Lunch and learn - Session 3
By Guyllaume Cardinal
TDD Lunch and learn - Session 3
Presentation done for Cogeco to show the basics of TDD. Now that Unit testing is done, we need some Integration tests to make sure classes properly interact together!
- 653