This presentation is about how to write tests faster, how to use real objects in tests which allow us to refactor a lot of code without worrying about "refactoring" other tests.
1. How to write
not breakable unit tests?
Based on PHPUnit
by Rafal Ksiazek, IT Leader
https://github.com/harpcio
2. Agenda
• The old aproach - mocking everything
– Pros and cons
• The new aproach – using real objects
– Pros and cons
• What we should mock?
• The magic of real objects
• How to mock?
3. The old aproach – mock everything
<?php
namespace Model;
class Car {
public function __construct(EngineInterface $engine) {
$this->engine = $engine;
}
public function run() {
$this->engine->start();
$this->engine->accelerate(10);
}
public function getActualSpeed() {
return $this->engine->getSpeed();
}
}
<?php
namespace TestsModel;
class CarTest {
public function setUp() {
$this->engineMock = $this->getMock(
TestsManualEngine::class
);
$this->testedObject = new Car(
$this->engineMock
);
}
public function testRun() {
$this->engineMock->expects($this->once())
->method(`start`);
$this->engineMock->expects($this->once())
->method(`accelerate`);
$this->testedObject->run();
$this->assertSame(
10,
$this->testedObject->getActualSpeed()
);
}
….
}
4. The old aproach – pros and cons
Pros:
• when class Engine fail, the
CarTest will still be valid
(tests are separated)
Cons:
• writing tests are more time
consuming
• changing class Engine, we
need to change also class
CarTest
• there is no possibility that
we will find not tested
functionality (forgotten or
skiped) in class Engine
5. The new aproach – use real objects
<?php
namespace Model;
class Car {
public function __construct(EngineInterface $engine) {
$this->engine = $engine;
}
public function run() {
$this->engine->start();
$this->engine->accelerate(10);
}
public function getActualSpeed() {
return $this->engine->getSpeed();
}
}
<?php
namespace TestsModel;
class CarTest {
public function setUp() {
$this->testedObject = new Car(
new Engine()
);
}
public function testRun() {
$this->testedObject->run();
$this->assertSame(
10,
$this->testedObject->getActualSpeed()
);
}
….
}
6. The new aproach – pros and cons
Pros:
• writing tests are much faster
• changing class Engine, we
don’t need to change also
class CarTest
• there is possibility that we
will find not tested
functionality (forgotten or
skiped) in class Engine
Cons:
• when class Engine fail, the
CarTest will also fail (but
fixing class Engine will fix
also CarTest)
7. What we should mock?
• We should mock classes that cross the border
of business logic layer, for example:
– Repositories (data access layer)
– File managers (file system layer)
– Connectors (facebook, twitter, google oauth)
8. The magic of real objects
<?php
namespace MyAppTestsRepository;
class UsersArrayRepository implement UsersRepositoryInterface {
public function __construct(array $users = []) {
$this->container = $users;
}
public function delete(User $user) {
foreach($this->container as $key => $elem) {
if ($elem->getId() === $user->getId()) {
unset($this->container[$key]);
return true;
}
}
return false;
}
…
}
9. How to mock
<?php
namespace MyAppService;
class UsersCrud {
public function __construct(
UsersRepositoryInterface $usersRepository
) {
$this->usersRepository = $usersRepository;
}
public function delete($userId) {
$user = $this->usersRepository->find($userId);
if (!$user) {
throw new UserNotFoundException();
}
return $this->usersRepository->delete($user);
}
}
<?php
namespace MyAppTestsService;
class UsersCrud Test {
public function setUp() {
$this->testedObject = new UsersCrud (
new UsersArrayRepository(
12 => new User()
)
);
}
public function testDelete_WhenSuccess() {
$result = $this->testedObject->delete(12);
$this->assertTrue($result);
}
….
}
10. Summary
• changing name of method „delete” to
„deleteOnlyDeacitivatedUser” in UsersRepository will force
change in the class „UsersCrud” and „UsersRepositoryTest”,
but not in the „UsersCrudTest”
• so .. we can do a lot of refactoring without worrying about
tests, hundreds of classes which use UsersRepository uwill
change automatically by IDE and tests of these classes will still
be valid
• thx for watching!