IMPORTANT: Per accedir als fitxer de subversion: http://acacha.org/svn (sense password). Poc a poc s'aniran migrant els enllaços. Encara però funciona el subversion de la farga però no se sap fins quan... (usuari: prova i la paraula de pas 123456)

Ningú discuteix la necessitat de realitzar proves per comprovar el correcte funcionament d'una aplicació. El que ja és un altre tema és com realitzar aquestes proves ja que existeixen multitud de formes de fer des del mètodes més tradicionals per fases (sent la fase de proves una fase posterior a les fases de disseny/especificació i implementació de codi) o sistemes completament orientats als tests com TDD o BDD.

Aquest article mira d'explicar els sistemes més coneguts a l'hora de realitzar proves en aplicacions de programari.

Definició

Testing és el procés d'avaluació d'un sistema i dels seus components amb l'objectiu de saber si satisfà els requeriments especificats. Amb paraules més simples, realitzar tests és executar un sistema per tal d'identificar errors, funcionalitats no implementades, bugs, etc

According to ANSI/IEEE 1059 standard, Testing can be defined as - A process of analyzing a software item to detect the differences between existing and required conditions (that is defects/errors/bugs) and to evaluate the features of the software item.

Introducció i motivació

Automatitzar els tests d'una aplicació té múltiples objectius i és molt important entendre aquests motius per tal de conèixer (i assumir) la motivació per tal de realitzar una tasca que evidentment genera una feina extra i que per tant necessita d'una justificació. Els tests poden semblar "redundants" o innecessaris sobretot per què com a desenvolupadors ens trobem durant la creació dels tests ens trobem escrivint un codi extra que prova el nostre propi codi i que això ho podem precisament provant el codi sense programar. La gràcia està en:

  • Automatitzem les proves. És a dir per realitzar una sola prova val la pena fer un test? Segurament no. Però la pregunta no és està la pregunta és només feu un test de la vostra aplicació? El normal és que l'aplicació estigui viva és a dir vagui creixent i que cada cop que afegim noves funcionalitats ens haguem d'assegurar que aquestes noves funcionalitats no afecten el codi ja escrit. A la llarga el temps invertit en escriure un test s'amortitza pel temps estalviat fent proves de l'aplicació
  • Refactor: gairebé tots els procediment de desenvolupament de codi utilitzant el que es coneix com a Refactorització. La refactorizació és la modificació de codi ja existent no tant per a crear noves funcionalitats o resoldre errors (bugs) com per a millorar el codi (millorar la comprensió, la seguretat, el rendiment,etc). Els processos de refactorització tenen l'inconvenient que poden provocar que el codi deixi de funcionar. Amb les proves de test automatitzades podem tornar a executar ràpidament els test per comprovar que les nostres refactoritzacions no han afectat el codi

Jargon

  • Assertion/Expectation: s'utilitzen com a base dels test amb mètodes tipus assertTrue(Boolea) que comprova si tal com s'espera un boolea és boolea o no o altres mètodes com assertsEquals(var1,var2) que comprova si dos valors són iguals.
  • Automated Test: Realitzar tests és una tasca automàtica, repetitiva i tediosa per aquesta raó és molt convenient automatitzar. Això no vol dir que s'eliminin les persones encarregades dels tests (human tester o QA team) sinó que s'encarreguen més de tasques relacionades amb els tests més humanes i de més "qualitat".
  • Setup / Exercise / Verify / Teardown: Qualsevol tests té quatre parts/fases:
  • Setup : preparar el que faci falta per realitzar el test
  • Exercise : executar el test
  • Verify: comparar el resultat obtingut amb el resultat esperat
  • Teardown: netejar tot el que s'hagi creat temporalment i de forma extra per tal de realitzar els tests és a dir tornar el sistema a l'estat abans de la fase de Setup.
  • Test Fixture: Un Fixture representa tota la informació que un test necessita per tal de ser executat. Un fixture pot ser tant senzill com crear un objecte senzill amb un new o quelcom tant complicat com omplir una base de dades i/o utilitzar interfícies d'usuari. Està relacionat amb el concepte de Factoria que són classes que precisament utilitzem per a crear un objecte per a un test.
  • System Under Test (SUT): representa el que s'ha de provar per a un test específic pot ser una classe (un unit test) o tota una aplicació. El SUT per tant es defineix sempre des de la perspectiva del test que es vol realitzar.
  • Testing Framework: Eina/llibreria que facilita la creació de tests per a un llenguatge de programació concret. El concepte és de Kent Beck a principis dels 90 que va acabar sent la base de SmalltalkUnit o Sunit per al llenguatge SmallTalk.
  • xUnit framework: on x indica el llenguatge, per exemple PHPUnit, JUnit per a Java, ShUnit per a shell scripts, etc.
  • Test Case: Originalment definit com la unitat més petita de test (Kent Beck). Actualment utilitzem "test methods" com a les unitats de tes més petites i anomenem Test Case a un conjunt de mètodes relacionats amb unà sèrie de Test (per exemple els tests d'una classe). També sovint l'anomenem simplement test.
  • Test Method: és la part més petita de l'arquitectura del sistema de test. Sol ser el mètode d'una classe que conté un conjunt tests relacionats amb una classe (el SUT). Un mètode de test implementa les 4 fases abans comentades: setup / exercise / verify / teardown.
  • DOC Dependent-On Component: Representa el components del sistema dels que depèn el sistema que estem provant (SUT). Per exemple una classe sota test pot dependre d'altres classes. A estes dependències a vegades se les anomena col·laboradors. Possiblement està és una de les parts més complexes d'entendre al començar a utilitzar test frameworks. Vegeu l'apartat test doubles (mocking, stubbing, dummies, etc

Recursos:

Assertion Flavor

Solen haver 3 opcions:

Vegeu assertion library

Test Doubles

Test Double és un concepte que fa referència a objectes falsos ("fake") que substitueixen als originals (objectes reals que s'utilitzen durant l'execució de l'aplicació) durant la realització dels tests. Els frameworks de test com PHPUnit o PHPSpec ofereixen ajudes per a utilitzar Test Doubles. Per exemple PHPSpec porta incorporat la llibreria Prophecy ( a highly opinionated mocking framework. opinionated = dogmàtico | testarudo).

L'objectiu és la isolation és a dir aconseguir que els tests d'una classe concreta no depenguin dels tests dels seus col·laboradors (o classes de les que depèn).

Veiem alguns conceptes:

  • Collaborator: Està molt relacionat amb les dependències i de fet se solen definir es col·laboradors d'una classe a partir de Injecció de dependències. Quan realitzem tests els col·laboradors o classes externes de la classe que estem provant (SUT) són susceptibles de ser substituïts per dobles (Test Double).
  • Expectations & promises:
  • Expectations: Relacionat amb el mocks o el mocking. Són expectactions per que comprovem coses com "this method should be called", és a dir el mètode d'un col·laborador ha de ser cridat. Per tant només volem comprovar que es crida el mètode però no ens interessa realment comprovar que fa i si ho fa bé (per això el col·laborador segurament ja té els seus propis tests)
  • Promises. Està relacionat amb els Stubs i l'Stubbing. Es vol comprovar que "this method will return this value" és a dir que al cridar a un mètode d'un col·laborador ha de tornar un valor concret.

Els següents apartats mostren exemples de diferents tipus de Test Doubles amb exemples concrets realitzats amb PHPSpec.

Recursos:

Mocking

Que vol dir mocking en anglès (en termes no informàtics)? Vegeu les imatges que us mostra Google si cerqueu Mocking:

https://www.google.es/search?q=Mocking&espv=2&biw=1375&bih=767&source=lnms&tbm=isch&sa=X&ei=28H-VL2AOsXXasi0gqAF&ved=0CAYQ_AUoAQ

Té mútliples sentits ([1]) com parodiar, imitar o burlar-se però en termes informàtics farà més referència a simulat, fingit, actuat, pràctica.

Quan realitzem Unit Tests normalment no estem interessats en els objectes o dependències del nostre codi sinó que volem centrar-nos en provar el codi propi i en unitat petites de test. Fàcilment al programar ens trobarem amb el problema de que fer amb estes dependències. Vegeu un exemple, una classe encarregada del registre d'un usuari a una aplicació (web):

  • Volem provar la funcionalitat register
  • Al registrar crearem un nou registre a la base de dades. La base de dades és un clar exemple de dependència del nostre codi.
  • Al registrar també enviarem un correu de benvinguda. Sovint s'utilitza un agent extern (Mailer) per enviar el email. Un altre dependència.

En aquest cas si fem un test unitari ens hem de centrar en provar el nostre codi propi i no pas el codi de base de dades ni el d'enviament de emails i per tant farem una simulació (mocking) d'aquestes classes.

Conceptes relacionats que són importants:

  • Injecció de dependències aka DI o Dependency Injection. Per a facilitar les proves i aplicar paradigmes de dissenys recomanables és interessant utilitzar la injecció de dependències per tal de definir els objectes dependents del nostre codi els qual simularem (mock). A l'exemple el repositori de la base de dades i el enviador de correus electrònics (Mailer)
  • Base de dades. És interessant aplicar el patró de disseny Repository.

Vegeu un exemple al següent projecte de github:

https://github.com/acacha-org/acacha-laracasts-testing-jargon
<?php

namespace spec\Acacha\Laracasts\TestingJargon;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Acacha\Laracasts\TestingJargon\UserRepository;
use Acacha\Laracasts\TestingJargon\Mailer;

class AcachaRegistersUserSpec extends ObjectBehavior
{
    function let(UserRepository $repository, Mailer $mailer)
    {
        $this->beConstructedWith($repository,$mailer);
    }
    
    function it_is_initializable()
    {
        $this->shouldHaveType('Acacha\Laracasts\TestingJargon\AcachaRegistersUser');
    }

    function it_creates_a_new_user(UserRepository $repository){

        $user = ['username' => 'John','email' => '[email protected]'];

        $repository->create($user)->shouldBeCalled();

        $this->register($user);
    }

    function it_sends_a_welcome_email(Mailer $mailer) {
        $user = ['username' => 'John','email' => '[email protected]'];

        $mailer->sendWelcome('[email protected]')->shouldBeCalled();

        $this->register($user);
    }
}


Stubs

Test doubles aka Stubs

Recursos

Qui realitza els tests?

It depends on the process and the associated stakeholders of the project(s). In the IT industry, large companies have a team with responsibilities to evaluate the developed software in context of the given requirements. Moreover, developers also conduct testing which is called Unit Testing. In most cases, the following professionals are involved in testing a system within their respective capacities:

  • Software Tester
  • Software Developer -> Unit Testing
  • Project Lead/Manager
  • End User

Different companies have different designations for people who test the software on the basis of their experience and knowledge such:

  • Software Tester
  • Software Quality Assurance Engineer
  • QA Analyst
  • ...

It is not possible to test the software at any time during its cycle. The next two sections state when testing should be started and when to end it during the SDLC.

Mites

http://www.tutorialspoint.com/software_testing/software_testing_myths.htm

Nivells

Dos grans tipus/nivells de tests:

  • Functional testing
  • Non-Functional Testing

Recursos:

Functional Tests

Unit Tests

Unit Test

Aquest tipus de tests són realitzats pels desenvolupadors de codi i treballen a baix nivell. Sovint són tests realitzats per als propis desenvolupadors per tal de poder comprovar que el que estan implementat a baix nivell compleix amb la funcionalitat lògica esperada. Aquests tests es realitzen sovint durant la mateix afase de creació de codi i es poden realitzar al mateix temps que s'escriu codi (sovint associat a la idea de Test Driven Development) però per múltiples raons també es pot crear primer codi i després realitzar els tests unitaris. Fins i tot existeixen sistemes com el BDD (Behavious Driven Development) on primer s'especifiquen els tests de funcionalitat i a partir d'aquests es crear el codi, és a dir s'utilitzen els propis tests com a eina de disseny del codi.

Per tant cal tenir en compte que els test unitàries estan relacionats amb els desenvolupadors i no se solen executar en fase posteriors ni hi sol intervindré el equip de test o de Q&A.

Un objectiu important dels test unitaris és la isolació (isolate) és a dir assegurar-se que els tests són independents de les dependències dels objectes de forma que es podin executar de forma unitària o independent. Altres sistemes se solen aplicar a posteriori com els test d'integració per comprovar que els components (unitats) del sistema funcionen correctament junts.

Limitacions

  • Els tests no poden abarcar cada possible errors (bug) de l'aplicació i això és aplicable concretament també als test unitaris.
  • Hi ha un límit de tests i d'escenaris que es poden aplicar per tal de verificar una unitat de codi. Un cop arribat a aquest punt no poden ni s'ha d'anar més enllà i cal deixar els unit tests i passar a fer altres tipus de test com tests d'acceptació o d'integració.

Algunes notes i exemples a mode de resum:

  • Un exemple típic de test unitari és provar un mètode (public) d'una classe passant un paràmetre i comprovant el resultat que torna el mètode.
  • Es realitzen a baix nivell.
  • Cal tenir en compte la diferència entre realitzar test del nostre codi a desenvolupar codi basant-nos en els test (p.ex. TDD o BDD).
  • Cal pensar els Unit Test com a proves de petites peces de funcionalitat. Per tant cal vigilar que no estiguem provant mètodes "FAT" ni grans quantitats de línies de codi de cop. De fet l'ús de bons patrons de disseny i el seguiment de tècniques com SOLID facilitant tant la correcte creació i manteniment de codi com els propis tests.

Podeu veure un exemple de Test unitari a:

Algun dels frameworks més coneguts de tests unitaris són:

  • xUnit: on la X depèn del llenguatge de programació. Per exemple PHPUnit, JUnit,ShUnit.
  • PHPSpec sistema que permet realitzar tests unitaris seguint la filosofia BDD.

Recursos:

Integration tests

Algun dels frameworks més coneguts de tests d'integració són:

Recursos

Acceptance Tests

Un test d'acceptació és un test que permet validar/aceptar un codi com a correcte per passar-lo a explotació. En equips de desenvolupament grans aquests tipus de test els realitzar el Quality Assurance Team que s'encarrega de comprovar que l'aplicació compleix amb els requeriments del client final.

Aquests tests es poden realitzar de forma natural/manual o també es poden mirar d'automatitzar utilitzant frameworks específicament dissenyats amb aquest objectiu.

Recursos:

Tests no funcionals

This section is based upon testing an application from its non-functional attributes. Non-functional testing involves testing a software from the requirements which are nonfunctional in nature but important such as performance, security, user interface, etc.

Some of the important and commonly used non-functional testing types are discussed below.

Performance Testing

It is mostly used to identify any bottlenecks or performance issues rather than finding bugs in a software. There are different causes that contribute in lowering the performance of a software:

  • Network delay
  • Client-side processing
  • Database transaction processing
  • Load balancing between servers
  • Data rendering
  • Performance testing is considered as one of the important and mandatory testing type in terms of the following aspects:

Speed (i.e. Response Time, data rendering and accessing) Capacity Stability Scalability Performance testing can be either qualitative or quantitative and can be divided into different sub-types such as Load testing and Stress testing.

Load Testing

It is a process of testing the behavior of a software by applying maximum load in terms of software accessing and manipulating large input data. It can be done at both normal and peak load conditions. This type of testing identifies the maximum capacity of software and its behavior at peak time.

Most of the time, load testing is performed with the help of automated tools such as Load Runner, AppLoader, IBM Rational Performance Tester, Apache JMeter, Silk Performer, Visual Studio Load Test, etc.

Virtual users (VUsers) are defined in the automated testing tool and the script is executed to verify the load testing for the software. The number of users can be increased or decreased concurrently or incrementally based upon the requirements.

Stress Testing

Stress testing includes testing the behavior of a software under abnormal conditions. For example, it may include taking away some resources or applying a load beyond the actual load limit.

The aim of stress testing is to test the software by applying the load to the system and taking over the resources used by the software to identify the breaking point. This testing can be performed by testing different scenarios such as:

Shutdown or restart of network ports randomly Turning the database on or off Running different processes that consume resources such as CPU, memory, server, etc. Usability Testing Usability testing is a black-box technique and is used to identify any error(s) and improvements in the software by observing the users through their usage and operation.

According to Nielsen, usability can be defined in terms of five factors, i.e. efficiency of use, learn-ability, memory-ability, errors/safety, and satisfaction. According to him, the usability of a product will be good and the system is usable if it possesses the above factors.

Nigel Bevan and Macleod considered that usability is the quality requirement that can be measured as the outcome of interactions with a computer system. This requirement can be fulfilled and the end-user will be satisfied if the intended goals are achieved effectively with the use of proper resources.

Molich in 2000 stated that a user-friendly system should fulfill the following five goals, i.e., easy to Learn, easy to remember, efficient to use, satisfactory to use, and easy to understand.

In addition to the different definitions of usability, there are some standards and quality models and methods that define usability in the form of attributes and sub-attributes such as ISO-9126, ISO-9241-11, ISO-13407, and IEEE std.610.12, etc.

UI vs Usability Testing

UI testing involves testing the Graphical User Interface of the Software. UI testing ensures that the GUI functions according to the requirements and tested in terms of color, alignment, size, and other properties.

On the other hand, usability testing ensures a good and user-friendly GUI that can be easily handled. UI testing can be considered as a sub-part of usability testing.

Security Testing

Security testing involves testing a software in order to identify any flaws and gaps from security and vulnerability point of view. Listed below are the main aspects that security testing should ensure:

Confidentiality Integrity Authentication Availability Authorization Non-repudiation Software is secure against known and unknown vulnerabilities Software data is secure Software is according to all security regulations Input checking and validation SQL insertion attacks Injection flaws Session management issues Cross-site scripting attacks Buffer overflows vulnerabilities Directory traversal attacks Portability Testing Portability testing includes testing a software with the aim to ensure its reusability and that it can be moved from another software as well. Following are the strategies that can be used for portability testing:

Transferring an installed software from one computer to another. Building executable (.exe) to run the software on different platforms. Portability testing can be considered as one of the sub-parts of system testing, as this testing type includes overall testing of a software with respect to its usage over different environments. Computer hardware, operating systems, and browsers are the major focus of portability testing. Some of the pre-conditions for portability testing are as follows:

Software should be designed and coded, keeping in mind the portability requirements. Unit testing has been performed on the associated components. Integration testing has been performed. Test environment has been established.

Formes de realitzar tests

Test Driven Development (TDD)

Vegeu Test Driven Development

Behavior Driven Development (BDD)

Coverage

Es necessita la extensió Xdebug i php-code-coverage ([2]):

$ composer require phpunit/php-code-coverage
 $ phpunit --coverage-text=./coverage.txt
PHPUnit 4.8.24 by Sebastian Bergmann and contributors.
Warning:	The Xdebug extension is not loaded
		No code coverage will be generated.

.........................................

Time: 4.72 seconds, Memory: 28.00Mb

OK (41 tests, 120 assertions)

El informe en format text té un format similar a:

$ cat coverage.txt 


Code Coverage Report:      
  2016-05-05 10:17:50      
                           
 Summary:                  
  Classes: 67.44% (29/43)  
  Methods: 77.42% (72/93)  
  Lines:   73.33% (220/300)

\App::Group
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  3/  3)
\App::Notification
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
\App::User
  Methods:  66.67% ( 4/ 6)   Lines:  71.43% (  5/  7)
\App\Console::Kernel
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
\App\Events::UserHasRegistered
  Methods:  50.00% ( 1/ 2)   Lines:  80.00% (  4/  5)
\App\Exceptions::Handler
  Methods:  50.00% ( 1/ 2)   Lines:  90.91% ( 10/ 11)
\App\Http\Controllers::GroupsController
  Methods: 100.00% ( 8/ 8)   Lines: 100.00% ( 28/ 28)
\App\Http\Controllers::GroupsNotificationsController
  Methods:  75.00% ( 3/ 4)   Lines:  93.33% ( 14/ 15)
\App\Http\Controllers::GroupsUsersController


També tenim un format en XML anomenat Clover que és utilitzat per múltiples llenguatges de programació i sistemes de proves. Per configurar el report en format clover al fitxer phpunit.xml afegiu:

<logging>
        <!-- and this is where your report will be written -->
        <log type="coverage-clover" target="./clover.xml"/>
    </logging>

I suposem també que teniu algun apartat similar a:

<filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
            <exclude>
                <file>./app/Http/routes.php</file>
            </exclude>
        </whitelist>
    </filter>

IMPORTANT: El fitxer phpunit.xml de Laravel ja conté aquesta secció, només us cal afegir l'etiqueta XML logging

Ara al executar:

$ phpunit

Se us crearà un fitxer clover.xml a l'arrel del vostre projecte.

O també podeu executar:

$ phpunit --coverage-clover clover.xml

Recursos:

PHPStorm

PHPStorm permet executar els tests unitaris de PHPUnit amb coverage. Vegeu:

https://laracasts.com/series/how-to-be-awesome-in-phpstorm/episodes/23

Eines i llibreries

PHP

IDEs

PHPStorm

Vegeu Phpstorm#Testing i els articles sobre PHPUnit, PHPSpec, Behat i Codeception per tenir més informació sobre testing a PHP.

Testing Laravel

Vegeu Testing Laravel

Exemples

Testing Laravel Authentication

Podeu veure un exemple de tests al paquet adminlte-laravel:

https://github.com/acacha/adminlte-laravel/blob/master/tests/AcachaAdminLTELaravelTest.php

Vegeu també

Enllaços externs