Upload
marcal-berga
View
93
Download
1
Embed Size (px)
Citation preview
Welcome to the wonderful world of Unit Testing
Marçal Berga
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
1. 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?
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).
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
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.
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";
}
}
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…).
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");
}
}
}
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();
}
}
}
2. Unit testing i php
a) Creant unit tests en php
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
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);
}
}
}
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
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.
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
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.
$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
->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
->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)).
$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.
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));
}
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.
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
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
@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
@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
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○ ...
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
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
3. Test Driven Development
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
a) TDD
a) TDD
Moltes Gràcies!