Testing

Un marc comú per Som energia

David García Garzón

Introducció

Objectius

Establir un marc comú entre nosaltres

Tipus de tests, vocabulari, mantras…

Diferents aproximacions per fer-los

Cóm abordar testos complexos

Sense TDD?

Com ho feiem sense TDD?

  • Afegim funcionalitat
  • Mostrem resultats al main
  • Comprovem si son bons
  • Següent funcionalitat

Només tornem a comprovar funcionalitats del passat quan entrem en mode paranoid.

Sindrome del ‘no ho toquis’

I les UI?

Clickem i clickem fins que peta

Itineraris de proves

Molt feixuc

Automatització

Evitem la validació manual

Permet executar els testos moltes vegades

Evitem regresions, ens envalentona

Frameworks: unittest, nose, pytest, jest, mamba

Com automatitzar

Fem codi que:

  • Construeixi la situació de test (fixture)
  • Comprobi que el resultat és l’esperat
  • Només ens alerti si no ho és

Si el test és codi, ¿qui testeja el codi de test?

Si el test és codi, caldrà mantenir-ho

Abans o després?

Automatitzar un test quan el codi ja funciona:

  • Fa mandra, ja funciona
  • Com sabem que el test funciona?

Si fem el test quan la funcionalitat encara no hi és, estarem comprovant que realment ho detecta.

Per codi antic, que volem cobrir, hi ha estratègies.

Els mantras

Red - Green - Refactor

SetUp - Exercise - Assert - TearDown

Duplicate - Fill - Relay - Clean

Ordre del matí

  1. Tipus de testos
  2. TDD
  3. Trobant els casos
  4. Escrivint testos
  5. Refactoritzant
  6. Solucionant pollos

Tipus de testos

Validació

Test supervisats:
Un humà ha de validar que els resultats son bons

Test automatitzats:
L’ordinador fa la validació

Tests amb referència validada:
Es comprova la sortida del cas la primera vegada y cada cop que canvia el comportament

Transparència

Com de conscients som dels detalls de la implementació quan escrivim el test?

Testos de caixa negra
Testos de caixa grisa
Testos de caixa blanca

Abast (I)

Unitaris vs Funcionals

Component vs Integració

Integració Bottom-up vs Big bang

Important: Els testos d’integració no han de testejar tots els casos dels components només que es delega de forma correcta en els components.

Abast (II)

Aceptació: El que fa l’usuari per donar-ho per bó

Extrem a extrem: Integración total del sistema en entorn realístic

Desplegament: Els que es fan en posar a producció

  • Smoke: Arrenca, respon… molt ràpids.
  • Sanity: Els canvis aplicats hi son

Motivació

Per què l’afegeixes?

  • De progressió: comprova noves funcionalitats
  • De regresió: quan els seguim executant
  • De correccio: per aillar un bug detectat
  • Exploratori: com funciona un codi de tercers?

Cas d’ús

Test de camí daurat:
Comprova la condició d’èxit principal

Test d’extensió:
Comprova una condició d’èxit divergent

Test de fallada:
Comprova una condició de fallada

Segons si cal executar

Dinàmics:
Necessiten executar el codi per fer la comprovació

Estàtics:
Testos que es fan analitzant el codi no executant-ho

Test no funcionals

Comprova requeriments no funcionals:

Usabilitat

Accessibilitat

Compatibilitat

Eficiencia

Seguretat

Rendiment

Test de rendiment

Test de Volum
funciona per tots els tamanys de dades?

Test de Càrrega
funciona per tots els nivells d’us?

Test d’Escalabilitat
s’agafa més recursos quan cal?

Test de Pic
respon be a pics alts puntuals?

Test d’Estress
quin és el límit operacional?

Test de Remull
es degrada mantenint la càrrega un bon temps?

Test de Seguretat

Test de Penetració
El fan els white hat hackers seguint els seus propis procediments

Test de Vulnerabilitat
Cerca de vulnerabilitats conegudes o típiques

TDD

Red-Green-Refactor

Red: Fem un test que falli

Green: Fem lo minim per que passi ràpid

Refactor: Millorem el codi, reduint entropia i preparant per seguent red

Red

Escrivim un nou test que falli

  • ens fa pensar en la interfície primer
  • les pensem des del test, interfícies testables
  • donarà errors (ERROR) fins que creem classes i mètodes buits fins arribar a la fallada (FAIL)
  • el FAIL és el test del test, obligatori!!

Criteri de limitació:
si no podem fer que el test falli, no calia fer-lo

Green

Fem lo minim per que passi ràpid

  • No ens preocupa si el codi és correcte
  • Si no ho podem fer rapid,
    • tirem enrera el red i refactoritzem per facilitar-ho
    • o plantejem un altre test més assequible
  • Molta cura de no afegir funcionalitat no coberta

Refactor

Millorem el codi

  • No afegim funcionalitat
  • Reduim entropia i duplicació al codi
  • O fem espai per la pròxima funcionalitat
  • Es poden fer diversos abans del seguent Red
  • Passem els testos a cada canvi

Trobant
casos de test

Quan hem testejat prou?

Llei del retorn decreixent.

Compte, no son gratis, si fem masses testos…

Costa implementar-los
Costa mantenir-los
L’execució dels testos triga més

Afegirem només casos que aportin quelcom

Técniques

Montecarlo
Generació aleatoria de casos
(casos petits? casinos?)

Anàlisi de fluxe

Branch testing
cada cami al codi un test

Test de cobertura
eina que detecta codi no exercitat pels testos

Anàlisi domini d’entrada

Valors frontera
A banda i banda d’on canvia el comportament

Particions equivalents
Un cas per partició de comportament equivalent

Montecarlo

És molt difíci donar amb cas de test extrany.

Important: guardar el cas fallat, per reproduir-lo fora del montecarlo

Molt costós d’executar

Hi ha eines per generar-ne

Per definir el domini aleatori, cal un análisi de domini mínim

Ideal per tests de rendiment

Branch testing

Assignar un cas d’us a cada branch de codi
if, for

Compte: No considera comportament divergent per dades quan cridem a terceres funcions o operadors

Exemple: floor(x) vs x//1 amb negatius

Coverage testing

Es fa amb eines d’anàlisi de codi

Serveix per detectar alguns casos no previstos

Al final fas branch testing, amb les seves limitacións.

pip install pytest-cov
pytest --cov mymodule mymodule
coverage html # to dump an html report at covhtml/
coveralls # to publish in [coveralls.io](https://coveralls.io) (for travis.yaml)

Anàlisi de domini d’entrada

Tàndem: Valors frontera + particions equivalents

Un cop definits els valors frontera, generem les particions equivalents

Fem servir els valors frontera com a representants de la partició.

Dos casos son equivalents si tenen el mateix comportament

Escrivint els testos

Estructura d’un test

Testing com diagrama d’estats

Comandes i consultes

És práctic tenir els mètodes dividits en

comandes (setters) que modifiquen estat
Els fem servir al setUp, tearDown

consultes (getters) que no alteren l’estat
Els fem servir per l’assert

Inputs i outputs

Estil xUnit (TDD)

# xUnit (unittest, nose, pytest)
class MyClass_Test(unittest.TestCase):

    def test_myMethod_inConditionA_returns666(self):
        sut = MyClass()
        sut.setConditionA()
        self.assertEqual(666, sut.myMethod())

Estil BDD

# Mamba (mamba, behave, pexpect)
with description("having an instance of MyClass") as self:
    instance = MyClass()
    with context("in condition A"):
        instance.setConditionA()
        with it("returns 666"):
            expect(instance.myGetter()).to(equal(666))

Nomenclatura

Objectiu: identificar el propòsit del test

Testejem myMethod de MyClass en una condició de test concreta (i esperem alguna cosa):

MyClass_Test.test_myMethod_whenThisHappens_fails
MyClass_Test.test__my_method__whenThisHappens_fails

classe - mètode - condicio - (opcional) conseqüència

¿On poso els tests?

¿Carpeta tests o de costat?

¿Prefix test_ o sufix _test?

Editar sempre en parells codi + test
Directori o prefix els allunya

Carpeta test comú a la comunitat Python
Separa testos de codi de producció

Al setup.py podem separar per suffix

Test Helpers

El codi de test cal mantenir-ho també.
Codi replicat és el dimoni del manteniment.

Extreure codi comú.

Desofusca la intenció del test:
treu detalls d’en mig darrera d’un bon nom.

Consell: per una millor abstracció, esperar a tenir alguns testos que la necessitin

Modifiquem el test, testejem-lo provocant fallades

Refactoritzant

Receptes

Fowler te receptes per modificar un artefacte d’implementació en una direcció

“Artefactes” poden ser un atribut, un literal, un mètode, un condicional, la signatura…

Reversibles: Sovint hi ha de complementaris

Passos comuns

Com si fos una bastida cal tenir sempre el codi funcionant, que l’edifici no caigui en cap moment:

  • Duplicar l’estructura vella
  • Farcir l’estructura nova (duplicar setters)
  • Recolzar-se en l’estructura nova (getters)
  • Netejar restos de l’estructura vella

Sobredisseny

Dona cabuda a possibles funcionalitats futures que encara no suportem.
Tradicionalment: “disseny previsor”, pero

  • Complexitat innecessària i potser mal dirigida
  • Codi més difícil de mantenir

En TDD, només es fa en el refactoring previ a la introducció de la funcionalitat

Si n’hi ha, és bo refactoritzar per treure’l.
Recorda: És reversible.

Optimitzacións

Un altre ús bon del refactoring.
Amb els testos, dona molta seguretat.

Compte: Tendeix a codi ménys flexible.

Com ho apliquem?
No aplicar-ho d’hora.
Esperar a que estigui quasi complert.
Amb profilers, només als colls d’ampolla.
Mantenir la resta flexible.

Solucionant pollos

Els pollos

M’he oblidat testejar i ho vull cobrir a posteriori

Vull pendre el control d’un sistema que no tè testos

Un component que depén d’altre

El resultat és massa gran per ficar-ho al codi

El comportament és aleatori o fràgil

Vull automatitzar testos d’una interficie

Testejant codi ja fet

Si es tracta d’un cas, que hem implementat sense test

Modifiquem el codi que sembla no testejat per no fer el que fa

Si aixeca un test, estava cobert, sinó, fem el test que l’aixequi.

Code guided tests

Si no és un cas sino tot un mètode

  • Comentar el codi fet
  • Anar afegint el codi per pedaços
  • Mantenim l’antic de referència

I/O Indirectes

Algunes entrades i sortides es fan amb tercers, no pas amb el test.

Test doubles

Dummy object: Es demana un parametre, no es fa servir

Test Stub: Retorna respostes enlatadas. Ignorant parametres.

Test Spy: Stub que, a més, registra les crides que s’en fan. El test les comprova.

Mock Object: Li programes una expectació i el ja fa la comprovació.

Fake Object: Implementación funcional de la interficie.
pe. Simular una base de datos o api con objetos en memoria

Verified Fake Object:

Perills dobles

Els dobles son codi

Si l’objecte doblat evoluciona cal actualitzar-los

Si fem un Fake que passa els mateixos testos, ens adonarem!

Fragilitat

Un test és fràgil quan és possible que en algún moment falli, tot i que la funcionalitat no s’hagi trencat.

  • Depén de dades de producció
  • Depén de la data actual
  • Depén de esdeveniments aleatoris

Desfragilitzant

Dades de producció

  • Comprovar de casos
  • Cercador de casos
  • Extractor de casos
  • Casos sintètics
  • Double de font de dades

Aleatori

  • Generador substituible
  • Injecció de dependencies
  • Context handler

Data actual

  • Parametre today
  • Mockup datetime

Back-2-Back tests

Utilitat

Per agafar control codi no cobert
Per comparar sortides difícils d’escriure

Com funcionen

Es seleccionen entrades de casos significatius

Es genera una sortida per ells (referència)

Es compara automaticament (diff) si canvia o no

Mecanisme per acceptar una nova referència

Prenent control

Quan falla un B2B d’un procés llarg, no ajuda gaire a saber on esta la falla.

Normalment te a veure amb els canvis que hem fet.

Si no, interessa fer drops de dades intermitges.

Els punts de drop és un bon punt per posterior disecció del procés en mòduls testejables partint dels B2B.

Data driven tests

Quan els casos de tests es poden paremetritzar, entrada sortida

Un mateix codi per diferents dades

Sovint els usuaris poden escriure la taula

Important documentar que aporta cada dada

Helpers de setup i assert, tambe fan feina

Frameworks (ddt per unittest)