Wellcome to the wonderful world of unit testing
-
Upload
marcal-berga -
Category
Software
-
view
93 -
download
1
Transcript of Wellcome to the wonderful world of unit testing
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!