Wellcome to the wonderful world of unit testing

38
Welcome to the wonderful world of Unit Testing Marçal Berga

Transcript of Wellcome to the wonderful world of unit testing

Page 1: Wellcome to the wonderful world of unit testing

Welcome to the wonderful world of Unit Testing

Marçal Berga

Page 2: Wellcome to the wonderful world of unit testing

[email protected]

Twitter

email

Page 3: Wellcome to the wonderful world of unit testing

Què farem?1. Unit Testing

a. Què és?

b. Què no és?

c. Avantatges

d. Mocks i Stubs

e. Creant codi testejable

2. Unit tests en PHP

a. Creant Unit Tests

b. PHPUnit, la mare dels ous

i. Instal·lació **ii. Assertionsiii. Mockejantiv. Excepcionsv. Mètodes especialsvi. Anotacionsvii. Configuració

c. Code coverage (TODO: afegir codeCoverageIgnore)

d. Corrent els nostres tests

3. Test Driven Developmenthttps://github.com/merciberga/unit_test

Page 4: Wellcome to the wonderful world of unit testing

1. Unit Testing

Page 5: Wellcome to the wonderful world of unit testing

● Test de blocs individuals de codi● Aïllats de la resta de codi● Executats en entorns controlats

○ Segons uns paràmetres definits○ Simulant el comportament de les dependències

class EstupidoFlandersTest {

public function testCanGetFresisuis() {

assertEquals("fresa", $estupidoFlanders->getFresisuis());

}

}

a) Què és?

Page 6: Wellcome to the wonderful world of unit testing

b) Què no és?● No són funcions del programa● No comproven la base de dades● No són tests funcionals● No indiquen que el software funciona en conjunt

No es un test unitari si...● Es comunica amb la base de dades.● Es comunica amb altres serveis.● No es poden córrer de forma asíncrona.● No testeja una unitat específica de codi.● No comprova un retorn (o error).

Page 7: Wellcome to the wonderful world of unit testing

c) Avantatges de Unit Testing● Permet la detecció d’errors de forma més ràpida i senzilla● Facilita el refactor del codi● Ens força a utilitzar tècniques SOLID● Demostra l’evolució del codi● Mola escriure codi amb tests unitaris● Preveu els canvis inesperats en el codi● Genera codi més robust

Page 8: Wellcome to the wonderful world of unit testing

d) Mocks i stubs● Són simulacions de les classes originals.● Sobreescriuen els mètodes originals utilitzats.● Dónen un comportament previsible a l’objecte.● Pemet l’aïllament del test.● Són díficils de diferenciar.

Page 9: Wellcome to the wonderful world of unit testing

d) Mocks i stubs

class Usuari { protected $id; protected $email; protected $nom;

public function getId() { return $this->id; } public function getEmail() { return $this->email; } public function getNom() { return $this->nom; } }

class UsuariMock extends Usuari {

public function getId() {

return 12345;

}

public function getEmail() {

return "[email protected]";

}

public function getNom() {

return "John Doe";

}

}

Page 10: Wellcome to the wonderful world of unit testing

e) Creant Codi testejable● No és codi testejable si

○ La classe és enorme.○ La complexitat condicional té dos o més nivells.○ La classe té lògica que no és de la tasca que ha de fer.○ Trobem la paraula “new” dins del codi.○ El constructor no crea les instàncies necessàries.○ Ens comuniquem amb la base de dades.

● S’ha d’evitar○ L’ús de privates i static.○ Utilitzar objectes per accedir a altres objectes (excepte factories).○ l’accés directe a variables globals ($_SERVER, $_SESSION…).

Page 11: Wellcome to the wonderful world of unit testing

class UserManager {

public function registerUser($username, $password) {

$dbConnect = mysql_connect("mysqlServer", "root", "root");

$searchUser = mysql_query("SELECT * FROM usuari WHERE email = " . $username . "", $dbConnect);

if(!empty($searchUser)) {

mysql_query("INSERT INTO usuari (username, password) VALUES ('".$username."', '".$password."')");

}else{

throw new \Exception( "l'usuari ja existeix");

}

}

public function userLogin($username, $password) {

$dbConnect = mysql_connect("mysqlServer", "root", "root");

$searchUser = mysql_query("SELECT * FROM usuari WHERE email = " . $username . "", $dbConnect);

if(!empty($searchUser)) {

if($password = $searchUser['password']) {

$datetime = new dateTime();

$_SESSION['token'] = sha1($username . $password . $datetime);

header('Location: /loginSuccess.php');

}

}else {

throw new \Exception( "l'usuari ja existeix");

}

}

}

Page 12: Wellcome to the wonderful world of unit testing

class UserManager {

protected $userRepository;

protected $globals;

public function __construct(Repository $userRepository, Globals $globals) {

$this->userRepository = $userRepository;

$this->globals = $globals;

}

public function registerUser($username, $password) {

$user = $this->userRepository->find($username);

if(!empty($user) {

$user->setUsername($username);

$user->setPassword($password);

$userRepository->save($user);

}else{

throw new UserAlreadyExistsException();

}

}

public function userLogin($username, $password) {

$user = $this->userRepository->find($username);

if(!empty($user)) {

if($password = $user->getPassword()) {

$datetime = new dateTime();

$this->globals->setSession('token', sha1($username . $password . $datetime));

return $user;

}else {

throw new IncorrectPasswordException();

}

}else {

throw new UserAlreadyExistsException();

}

}

}

Page 13: Wellcome to the wonderful world of unit testing

2. Unit testing i php

Page 14: Wellcome to the wonderful world of unit testing

a) Creant unit tests en php

Page 15: Wellcome to the wonderful world of unit testing

b) PHPUnit, la mare dels ous...● PHPUnit va ser creat el 2004 (actualment a v5.3.*).

● El seu creador va ser Sebastian Bergmann

● PHPUnit ofereix les eines necessàries per a crear tests unitaris.

● Està basat en xUnit (API de test per a .NET)

● phpunit.de

● https://github.com/sebastianbergmann/phpunit

No, no és un meme, és el Sebastian

Page 16: Wellcome to the wonderful world of unit testing

class RegisterUserUseCaseTest extends \PHPUnit_Framework_TestCase

{

public function testCanRegisterUser() {

$userRepositoryMock =

$this->getMockBuilder(\UserRepository::class)

->disableOriginalConstructor()->getMock();

$userRepositoryMock->expects($this->once())

->method('findByUsername')

->with('testUsername')

->will($this->returnValue(null));

$userRepositoryMock->method('save')

->will($this->returnValue(true));

$emailSenderMock =

$this->getMockBuilder(\EmailSender::class)

->disableOriginalConstructor()->getMock();

$emailSenderMock->expects($this->once())

->method('sendEmail')

->will($this->returnValue(1));

$registerUserUseCase = new

RegisterUserUseCase($userRepositoryMock, $emailSenderMock);

$result =

$registerUserUseCase->registerUser('testUsername');

$this->assertEquals(200, $result);

}

class RegisterUserUseCase {

public function __construct(

EntityRepository $userRepository,

EmailSender $emailSender

) {

$this->userRepository = $userRepository;

$this->emailSender = $emailSender;

}

public function RegisterUser($username) {

if(!$this->userRepository->findByUsername($usernam

e)) {

$user = new User();

$user->setUsername($username);

$userRepository->save($user);

$emailSender->sendEmail($user);

}

}

}

Page 17: Wellcome to the wonderful world of unit testing

Composer:

composer require phpunit/phpunit

"require": {

"phpunit/phpunit": "4.*"

},

i) Instal·lació de PHPUnit

.phar:

wget https://phar.phpunit.de/phpunit.phar

chmod +x phpunit.phar

sudo mv phpunit.phar /usr/local/bin/phpunit

Page 18: Wellcome to the wonderful world of unit testing

ii) Assertions

class TestSomethingCoolYouJustDeveloped {

public function testYourAwesomeMethod() {

$someOtherServiceMock =

$this->getMockBuilder(CoolService::class)

->disableOriginalConstructor()

->getMock();

$someOtherServiceMock->method('doFunnyStuff')

->will($this->returnValue('just a string...'));

$this->assertEquals('just a string...',

$someOtherServiceMock->doFunnyStuff(), $message);

}

}

Les Assertions comparen el resultat obtingut amb el resultat esperat.

Page 19: Wellcome to the wonderful world of unit testing

Les assertions només avisen quan el resultat no és correcte.

● assertEquals($expected, $result) -> Compara que els dons inputs són iguals.

● assertNull($result) -> Comproba que el resultat és NULL.

● assertArrayHasKey($key, $result) -> Comproba que l’array conté la clau.

● assertInstanceOf(MyClass::class, $result) -> Comproba si retorna un tipus

MyClass.

● assertTrue($result) / assertFalse($result) -> Comproba true / false

Podem definir un missatge opcional passant-lo com a últim paràmetre.

ii) Assertions

Page 20: Wellcome to the wonderful world of unit testing

iii) Mockejant

class TestSomethingCoolYouJustDeveloped {

public function testYourAwesomeMethod() {

$someOtherServiceMock = $this->getMockBuilder(CoolService::class)

->disableOriginalConstructor()

->getMock();

$someOtherServiceMock->expects($this->once())

->method('doFunnyStuff')

->with($this->equalTo('OneAwesomeParameter'))

->will($this->returnValue('just a string...'));

}

}

- Simulen el comportament dels objectes.- Sobreescriu els mètodes originals.- Té un comportament controlat.- Permet diferent complexitat de simulació.- Podem testejar l’ús dels mètodes.

Page 21: Wellcome to the wonderful world of unit testing

$someClassMock = $this->getMockBuilder(SomeClass::class)

->disableOriginalConstructor()

->setMethods(array['method1', 'method2', ...])

->getMock();

//CONFIGURACIÓ DEL MOCK

$someClassMock->expects($this->once())

->method('method1')

->with($this->equalsTo($params))

->will($this->returnValue('retorn'));

A la

doc

umen

taci

ó (p

hpun

it.de

) pod

em tr

obar

més

info

rmac

ió d

els

test

dou

bles

o m

ocks

->expects($this->once()) //Només una crida del mètode

->expects($this->exactly($numero)) //$num vegades la crida del mètode

->expects($this->any()) //Qualsevol núm de crides al mètode

iii) Mockejant

Page 22: Wellcome to the wonderful world of unit testing

->with($this->equalsTo($valor)) //paràmetre idèntic a $valor

->with($this->greaterThan($valor)) //Més gran que $valor

->with($this->anything()) //Qualsevol paràmetre

->with([

$this->equalsTo($valor1)),

$this->greatherThan($valor2)),

])

Podem crear arrays per a tenir diferents possibilitats:

Configurant els paràmetres d’entrada del mètode esperats

iii) Mockejant

Page 23: Wellcome to the wonderful world of unit testing

->will($this->returnValue($valor))

->will($this->returnSelf())

->will($this->returnCallback('function'))

$anotherCoolMock = $this->getMockBuilder(CoolClass::class)

->setMethods(['anyMethodYoudLike'])

->getMock();

$anotherCoolMock->method('anyMethodYoudLike')

->will($this->onConsecutiveCalls(1,2,3,4,5));

iii) Mockejant

On consecutive calls● Torna un resultat segons la seva posició en l’array.● No llança error si un resultat no s’utilitza.● És interessant usar-lo amb expects($this->exactly($n)).

Page 24: Wellcome to the wonderful world of unit testing

$couplesValueMap = [

['Homer', 'Marge'],

['Apu', 'Mandjula'],

['Seymour', 'Edna'],

['Ned', 'Maude']

];

$characterMock->method('findCouple')

->will($this->returnValueMap($couplesValueMap));

iii) Mockejant

Value maps● Relacionen un retorn amb uns paràmetres.● Retorna el valor només si tots els paràmetres són els indicats.● Si el el paràmetres no són utilitzats no llaça error.

Page 25: Wellcome to the wonderful world of unit testing

iv) Excepcions

● El llançament d’excepcions forma part del programa.● PHPUnit permet esperar excepcions.● Si esperem una excepció, el test falla si no és llançada.● Un mock també pot llançar excepcions.

public function testExceptionIsThrownWhenMakingMethodCrash() {

$this->setExpectedException(DumpException::class);

$this->assertTrue($this->crashAnyTime->mustAnExceptionBeThrown(true));

}

Page 26: Wellcome to the wonderful world of unit testing

v) Mètodes especials

setUp():

- S’executa un cop abans de cada test.

- Serveix per a executar codi que necessitem a cada test de forma

automàtica.

setUpBeforeClass():

- S’executa un cop abans de cada classe de test.

- És una classe estàtica.

- És útil per a establir connexions o valors que utilitzarem en els tests.

Page 27: Wellcome to the wonderful world of unit testing

tearDown():

- S’executa un cop després de cada test.

- Serveix per a netejar les variables i valors creats o modificats

durant el test.

tearDownAfterClass():

- S’executa un cop després de cada classe de test.

- És una classe estàtica.

- És útil per a tancar connexions utilitzades durant els tests.

v) Mètodes especials

Page 28: Wellcome to the wonderful world of unit testing

vi) Anotacions

/** * @anotation valorAnotacio */

@group // @author

● Categoritza el test amb el nom donat a l’anotació. ● Podem indicar quins tests volem llançar.

php vendor/phpunit/phpunit/phpunit --group=grupQueEsTesteja

Page 29: Wellcome to the wonderful world of unit testing

@dependsCrea una dependència entre tests.

public function testOne() {

$arrayValue = array('keyOne' => null);

//do something cool with this array

$this->assertArrayHasKey('keyOne', $arrayValue);

return $arrayValue;

}

/**

* @depends testOne

*/

public function testTwo($arrayValue) {

if($arrayValue['keyOne'] == null) {

$arrayValue['keyOne'] = 'value!';

}

$this->assertEquals('value!', $arrayValue['keyOne']);

return $arrayValue;

}

vi) Anotacions

Page 30: Wellcome to the wonderful world of unit testing

@dataProviderCrida un “proveïdor” de paràmetres per al test. El test es correrà tants cops com arrays de paràmetres tingui el provider.

/**

* @dataProvider dataProvider

*/

public function testWithThatStrangeProvider($a, $b, $expected) {

$calculator = new Calculator();

$this->assertEquals($expected, $calculator->add($a, $b));

}

public function dataProvider() {

return [

[1, 2, 3],

[10, 15, 25],

[5, 13, 18]

];

}

vi) Anotacions

Page 31: Wellcome to the wonderful world of unit testing

vii) configuració

● PHPUnit utilitza xml per a la configuració● S’utilitza per:

○ Configuració interna○ Configuració de test-suites○ Definició de variables de servidor○ Definició d’arxius a cobrir pel coverage○ Variables natives de PHPUnit○ ...

Page 32: Wellcome to the wonderful world of unit testing

c) Code Coverage● Indica el grau d’execució durant els tests.● No cal concentrar-nos en obtenir el 100%.● És un indicador de l’estat del desenvolupament.

Principi de Pareto

Page 33: Wellcome to the wonderful world of unit testing

d) Corrent els nostres testsphp vendor/phpunit/phpunit/phpunit -c <arxiu xml config> (<arxiu del test>) (--<opcio=valor>)

php vendor/phpunit/phpunit/phpunit -c phpunit.conf.xml --group=nomDelGrup

php vendor/phpunit/phpunit/phpunit -c phpunit.conf.xml --code-coverage=text

Page 34: Wellcome to the wonderful world of unit testing

3. Test Driven Development

Page 35: Wellcome to the wonderful world of unit testing

a) TDD● Cicles de programació curts● Requereixen la creació de tests abans del desenvolupament● Facilita la creació de codi robust● El codi resultant és 100% testejable

Page 36: Wellcome to the wonderful world of unit testing

a) TDD

Page 37: Wellcome to the wonderful world of unit testing

a) TDD

Page 38: Wellcome to the wonderful world of unit testing

Moltes Gràcies!