de Shashi Kumar Raja

Introducere în testarea bazată pe proprietăți în Python

Introducere in testarea bazata pe proprietati in Python

În acest articol vom învăța o abordare unică și eficientă a testării numită testare bazată pe proprietăți. Noi vom folosi Piton , pytest și Ipoteză pentru a implementa această abordare de testare.

Articolul va fi folosit concepte de bază pitest pentru a explica testarea bazată pe proprietăți. Vă recomand citiți acest articol să îți spulberi rapid cunoștințele cele mai pitite.

Vom începe cu unitatea convențională / metoda de testare funcțională cunoscută sub numele de testarea bazată pe exemple pe care majoritatea dintre noi îl folosim. Încercăm să îi găsim neajunsurile, apoi trecem la abordarea bazată pe proprietăți pentru a elimina aceste neajunsuri.

Fiecare mare truc magic constă din trei părți sau acte. Prima parte se numește „Angajamentul”. Magul îți arată ceva obișnuit: un pachet de cărți, o pasăre sau un om. El îți arată acest obiect. Poate că vă cere să-l inspectați pentru a vedea dacă este într-adevăr real, nealterat, normal. Dar desigur … probabil că nu este.

Partea 1: Testare bazată pe exemple

Abordarea testării bazate pe exemple are următorii pași:

  • a primit o intrare de test Eu
  • când este trecut la funcție sub test
  • ar trebui să returneze o ieșire O

Deci, practic oferim o intrare fixă ​​și ne așteptăm la o ieșire fixă.

Pentru a înțelege acest concept în termeni laici:

1611033969 60 Introducere in testarea bazata pe proprietati in Python
O mașină testată

Să presupunem că avem o mașină care ia plastic de orice formă și orice culoare ca intrare și produce o bilă de plastic perfect rotundă de aceeași culoare cu ieșirea.

1611033970 132 Introducere in testarea bazata pe proprietati in Python
Fotografie de Greyson Joralemon pe Unsplash

Acum, pentru a testa această mașină folosind teste bazate pe exemple, vom urma abordarea de mai jos:

  1. luați un plastic brut de culoare albastră (date de testare fixe)
  2. alimentează plasticul la mașină
  3. așteptați o minge de plastic de culoare albastră ca ieșire (ieșire de testare fixă)

Să vedem aceeași abordare într-un mod programatic.

Condiție preliminară: asigura-te ca ai Piton (ver 2.7 sau mai sus) și pytest instalat.

Creați o structură de directoare ca aceasta:

- demo_tests/    - test_example.py

Vom scrie o mică funcție sum în interiorul fișierului test_example.py . Aceasta acceptă două numere – num1 și num2 – ca parametri și returnează adunarea ambelor numere ca rezultat.

def sum(num1, num2):    """It returns sum of two numbers"""    return num1 + num2

Acum, să scriem un test pentru a testa această funcție sumă urmând metoda convențională.

import pytest
#make sure to start function name with testdef test_sum():    assert sum(1, 2) == 3

Aici puteți vedea că trecem cele două valori 1 și 2 și așteptându-se să revină suma 3.

Rulați testele trecând la demo_tests folder și apoi rulează următoarea comandă:

pytest test_example.py -v
Introducere in testarea bazata pe proprietati in Python
1 test de trecere

Este suficient acest test pentru a verifica funcționalitatea fișierului sum funcţie?

S-ar putea să vă gândiți, desigur că nu. Vom scrie mai multe teste folosind pytest parametrize caracteristică care va executa acest lucru test_sum funcție pentru toate valorile date.

import pytest
@pytest.mark.parametrize('num1, num2, expected',[(3,5,8),              (-2,-2,-4), (-1,5,4), (3,-5,-2), (0,5,5)])def test_sum(num1, num2, expected):        assert sum(num1, num2) == expected
1611033970 38 Introducere in testarea bazata pe proprietati in Python
Toate cele 5 teste trec

Folosirea a cinci teste a dat mai multă încredere în ceea ce privește funcționalitatea. Toți trecătorii se simt ca fericire.

Dar, dacă priviți mai atent, facem același lucru pe care l-am făcut mai sus, dar pentru un număr mai mare de valori. Încă nu acoperim mai multe dintre cazurile de margine.

Deci, am descoperit primul punct de durere cu această metodă de testare:

Problema 1: exhaustivitatea testului depinde de persoana care scrie testul

Aceștia pot alege să scrie 5 sau 50 sau 500 de cazuri de testare, dar rămân nesiguri dacă au acoperit în condiții de siguranță majoritatea cazurilor marginale, dacă nu chiar toate.

Acest lucru ne aduce la al doilea punct de durere:

Problema 2 – Teste non-robuste din cauza înțelegerii neclare / ambigue a cerințelor

Când ni s-a spus să scriem a noastră sum funcție, ce detalii specifice au fost transmise?

Ni s-a spus:

  • la ce tip de intrare ar trebui să se aștepte funcția noastră?
  • cum ar trebui să se comporte funcția noastră în scenarii de intrare neașteptate?
  • ce fel de ieșire ar trebui să returneze funcția noastră?

Pentru a fi mai precis, dacă luați în considerare sum funcție pe care am scris-o mai sus:

  • știm dacă num1, num2 ar trebui să fie un int sau float? Pot fi, de asemenea, trimise ca tip string sau orice alt tip de date?
  • ce este minim și maxim valoarea num1 și num2 pe care ar trebui să le sprijinim?
  • cum ar trebui să se comporte funcția dacă obținem null intrări?
  • ar trebui ca ieșirea returnată de funcția sumă să fie int sau float sau string sau orice alt tip de date?
  • în ce scenarii ar trebui să afișeze mesaje de eroare?

De asemenea în cel mai rău caz din abordarea scrisă a cazurilor de test de mai sus este că aceste cazuri de test pot fi păcălit să treacă de funcțiile buggy.

Haideți să le scriem din nou sum funcționează într-un mod în care sunt introduse erorile, dar testele pe care le-am scris până acum încă trec.

def sum(num1, num2):    """Buggy logic"""       if num1 == 3 and num2 == 5:        return 8    elif num1 == -2 and num2  == -2 :        return -4    elif num1 == -1 and num2 == 5 :        return 4    elif num1 == 3 and num2 == -5:        return -2    elif num1 == 0 and num2 == 5:        return 5
1611033971 803 Introducere in testarea bazata pe proprietati in Python
Toate testele sunt încă promovate

Acum să ne scufundăm în testarea bazată pe proprietăți pentru a vedea cum aceste puncte de durere sunt atenuate acolo.

Al doilea act se numește „Turnul”. Magicianul ia ceva obișnuit și îl face să facă ceva extraordinar. Acum căutați secretul … dar nu îl veți găsi, pentru că, desigur, nu căutați cu adevărat. Nu prea vrei să știi. Vrei să fii păcălit.

Partea 2: Testarea bazată pe proprietăți

Introducere și generare de date de testare

Testarea bazată pe proprietăți a fost introdusă pentru prima dată de Verificare rapida cadru în Haskell. Conform verifica rapid ‘documentația, care este o altă bibliotecă de testare bazată pe proprietăți-

Cadrele de testare bazate pe proprietăți verifică veridicitatea proprietăților. O proprietate este o declarație de genul:

pentru toți (x, y, …)

cum ar fi condițiile prealabile (x, y, …)

proprietatea (x, y, …) este adevărată.

Pentru a înțelege acest lucru, ne întoarcem la exemplul nostru de mașină generatoare de bile de plastic.

Abordarea de testare bazată pe proprietăți a mașinii respective va fi:

  1. luați o gamă largă de materiale plastice ca intrare (all(x, y, …))
  2. asigurați-vă că toate sunt colorate (precondition(x, y, …))
  3. ieșirea satisface următoarea proprietate (property(x, y, …)) –
1611033971 939 Introducere in testarea bazata pe proprietati in Python
Fotografie de Melanie Magdalena pe Unsplash
  • ieșirea are formă rotundă / sferică
  • ieșirea este colorată
  • culoarea ieșirii este una dintre culorile prezente în banda de culori

Observați cum avem din valorile fixe de intrare și ieșire generalizat datele și rezultatele noastre de testare în așa fel încât proprietatea ar trebui să fie adevărată pentru toate intrările valide. Acesta este testarea bazată pe proprietăți.

De asemenea, observați că atunci când gândim în termeni de proprietăți trebuie să gândim mai mult și într-un mod diferit. Ca atunci când ne-a venit ideea că, din moment ce rezultatul nostru este o minge, ar trebui să aibă o formă rotundă, o altă întrebare vă va pune – dacă mingea trebuie să fie goală sau solidă?

Deci, făcându-ne să ne gândim mai bine și să ne întrebăm mai multe despre cerință, abordarea de testare bazată pe proprietate ne face ca implementarea cerinței să fie robustă.

Acum, să revenim la funcția noastră sumă și să o testăm utilizând abordarea bazată pe proprietăți.

prima întrebare care apare aici este: care ar trebui să fie aportul sum funcţie?

Pentru scopul acestui articol, vom presupune că orice pereche de numere întregi din setul întreg este o intrare validă.

1611033971 924 Introducere in testarea bazata pe proprietati in Python
Sistemul de coordonate carteziene

Deci, orice set de valori întregi care se află în sistemul de coordonate de mai sus va fi o intrare validă pentru funcția noastră.

urmatoarea intrebare este: cum se obțin astfel de date de intrare?

Răspuns acest lucru este: o bibliotecă de testare bazată pe proprietăți vă oferă funcția de a genera un set uriaș de date de intrare dorite după o condiție prealabilă.

În Python, Ipoteză este o bibliotecă de testare a proprietăților care vă permite să scrieți teste împreună cu pytest. Vom folosi această bibliotecă.

Întreaga documentație a Ipotezei este frumoasă scrisă și disponibilă ➡️ aici și vă recomand să o parcurgeți.

Pentru a instala Hypothesis:

pip install hypothesis

și suntem buni să folosim ipoteza cu pytest.

Acum, să rescriem test_sum funcție – pe care am scris-o mai devreme – cu noi seturi de date generate de Hypothesis.

from hypothesis import given
import hypothesis.strategies as st
import pytest
@given(st.integers(), st.integers())def test_sum(num1, num2):    assert sum(num1, num2) == num1 + num2
  • Prima linie pur și simplu importă given din Ipoteza. @given decorator preia funcția noastră de testare și o transformă într-una parametrizată. Când este apelat, aceasta va rula funcția de testare pe o gamă largă de date potrivite. Acesta este principalul punct de intrare în ipoteza.
  • A doua linie importă strategies din Ipoteza. strategiile oferă caracteristica de a genera date de testare. Ipoteza oferă strategii pentru majoritatea tipurilor încorporate cu argumente pentru a constrânge sau a ajusta rezultatul. De asemenea, strategiile de ordin superior pot fi compuse pentru a genera tipuri mai complexe.
  • Puteți genera oricare sau combinați următoarele lucruri folosind strategii:
'nothing','just', 'one_of','none','choices', 'streaming','booleans', 'integers', 'floats', 'complex_numbers', 'fractions','decimals','characters', 'text', 'from_regex', 'binary', 'uuids','tuples', 'lists', 'sets', 'frozensets', 'iterables','dictionaries', 'fixed_dictionaries','sampled_from', 'permutations','datetimes', 'dates', 'times', 'timedeltas','builds','randoms', 'random_module','recursive', 'composite','shared', 'runner', 'data','deferred','from_type', 'register_type_strategy', 'emails'
  • Aici am generat integers()stabiliți folosind strategii și l-ați transmis @given.
  • Deci, a noastră test_sum funcția ar trebui să ruleze pentru toate iterațiile intrării date.

Să-l rulăm și să vedem rezultatul.

1611033971 15 Introducere in testarea bazata pe proprietati in Python

S-ar putea să vă gândiți, nu văd nicio diferență aici. Ce este atât de special la această alergare?

Ei bine, pentru a vedea diferența magică, trebuie să ne testăm setarea verbose opțiune. Nu confundați acest detaliu cu -v opțiunea de pytest.

from hypothesis import given, settings, Verbosity
import hypothesis.strategies as stimport pytest
@settings(verbosity=Verbosity.verbose)@given(st.integers(), st.integers())def test_sum(num1, num2):    assert sum(num1, num2) == num1 + num2

settings ne permite să modificăm comportamentul implicit al testului de ipoteză.

Acum să reluăm testele. Include, de asemenea -s de data aceasta pentru a captura fluxul de ieșire în pytest.

pytest test_example.py -v -s
1611033972 762 Introducere in testarea bazata pe proprietati in Python
1611033972 459 Introducere in testarea bazata pe proprietati in Python
Măriți și vedeți cazurile generate

Uită-te la numărul mare de cazuri de testare generate și rulate. Aici puteți găsi tot felul de cazuri, cum ar fi 0, numere mari și numere negative.

S-ar putea să vă gândiți, este impresionant, dar nu găsesc perechea mea preferată de teste (1,2) aici. Ce se întâmplă dacă vreau să ruleze asta?

Ei bine, nu vă temeți, ipoteza vă permite să rulați de fiecare dată un set dat de cazuri de test, dacă doriți, utilizând @example decorator.

from hypothesis import given, settings, Verbosity, example
import hypothesis.strategies as stimport pytest
@settings(verbosity=Verbosity.verbose)@given(st.integers(), st.integers())@example(1, 2)def test_sum(num1, num2):    assert sum(num1, num2) == num1 + num2
1611033972 222 Introducere in testarea bazata pe proprietati in Python
Un exemplu este întotdeauna inclus în test.

De asemenea, observați că fiecare alergare va fi mereu generați un nou caz de test amestecat urmând strategia de generare a testului, astfel randomizând testul.

Deci, acest lucru rezolvă primul nostru punct de durere – exhaustivitatea cazurilor de testare.

Mă gândesc greu să vin cu proprietăți de testat

Până în prezent, am văzut o magie a testării bazate pe proprietăți, care generează datele de testare dorite din mers.

Acum să ajungem la partea în care trebuie să ne gândim bine și într-un mod diferit pentru a crea astfel de teste care sunt valabile pentru toate intrările de testare dar unic pentru sum funcţie.

1 + 0 = 10 + 1 = 15 + 0 = 5-3 + 0 = -38.5 + 0 = 8.5

Ei bine, asta e interesant. Pare a adăuga 0 la un număr rezultă același număr ca suma. Aceasta se numește identitate proprietate a adaosului.

Să vedem încă unul:

2 + 3 = 53 + 2 = 5
5 + (-2) = 3-2 + 5 = 3

Se pare că am găsit încă o proprietate unică. În plus, ordinea parametrilor nu contează. Plasate la stânga sau la dreapta semnului + dau același rezultat. Aceasta se numește proprietate comutativă a adunării.

Mai este încă una, dar vreau să veniți cu ea.

Acum, vom scrie din nou test_sum pentru a testa aceste proprietăți:

from hypothesis import given, settings, Verbosity
import hypothesis.strategies as stimport pytest
@settings(verbosity=Verbosity.verbose)@given(st.integers(), st.integers())def test_sum(num1, num2):    assert sum(num1, num2) == num1 + num2
    # Test Identity property    assert sum(num1, 0) = num1     #Test Commutative property      assert sum(num1, num2) == sum(num2, num1)
1611033973 172 Introducere in testarea bazata pe proprietati in Python
Toate testele au trecut.

Testul nostru este acum exhaustiv – am transformat și testele pentru a le face mai robuste. Astfel, am rezolvat al doilea punct de durere: cazuri de testare non-robuste.

Doar de dragul curiozității, să încercăm să păcălim acest test cu acel cod buggy pe care l-am folosit înainte.

1611033973 46 Introducere in testarea bazata pe proprietati in Python
De data asta nu e de râs.

Așa cum spune un vechi proverb – păcălește-mă o dată, rușine-te, păcălește-mă de două ori, rușine-mă.

Puteți vedea că a surprins o eroare. Falsifying example: test_sum(num1=0, num2=0). Înseamnă pur și simplu că proprietatea noastră așteptată nu a fost valabilă pentru aceste perechi de cazuri de testare, deci eșecul.

Dar încă nu ați bate din palme. Pentru că a face ceva să dispară nu este suficient; trebuie să-l aduci înapoi. De aceea, fiecare truc magic are un al treilea act, cel mai greu, partea pe care o numim „Prestigiul”.

Partea 3: Reducerea eșecurilor

Se micșorează este procesul prin care Ipoteza încearcă să producă exemple citibile de om atunci când constată un eșec. Ia un exemplu complex și îl transformă într-unul mai simplu.

Pentru a demonstra această caracteristică, să adăugăm încă o proprietate la test_sum funcție care spune num1 ar trebui să fie mai mic sau egal cu 30.

from hypothesis import given, settings, Verbosity
import hypothesis.strategies as stimport pytest
@settings(verbosity=Verbosity.verbose)@given(st.integers(), st.integers())def test_sum(num1, num2):    assert sum(num1, num2) == num1 + num2
    # Test Identity property    assert sum(num1, 0) = num1     #Test Commutative property      assert sum(num1, num2) == sum(num2, num1)    assert num1 <= 30

După rularea acestui test, veți obține un jurnal de ieșire interesant pe terminal aici:

collected 1 item
test_example.py::test_sum Trying example: test_sum(num1=0, num2=-1)Trying example: test_sum(num1=0, num2=-1)Trying example: test_sum(num1=0, num2=-29696)Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=-1763, num2=47)Trying example: test_sum(num1=6, num2=1561)Trying example: test_sum(num1=-24900, num2=-29635)Trying example: test_sum(num1=-13783, num2=-20393)
#Till now all test cases passed but the next one will fail
Trying example: test_sum(num1=20251, num2=-10886)assert num1 <= 30AssertionError: assert 20251 <= 30
#Now the shrinking feature kicks in and it will try to find the simplest value for which the test still fails
Trying example: test_sum(num1=0, num2=-2)Trying example: test_sum(num1=0, num2=-1022)Trying example: test_sum(num1=-165, num2=-29724)Trying example: test_sum(num1=-14373, num2=-29724)Trying example: test_sum(num1=-8421504, num2=-8421376)Trying example: test_sum(num1=155, num2=-10886)assert num1 <= 30AssertionError: assert 155 <= 30
# So far it has narrowed it down to 155
Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=64, num2=0)assert num1 <= 30AssertionError: assert 64 <= 30
# Down to 64
Trying example: test_sum(num1=-30, num2=0)Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=0, num2=0)Trying example: test_sum(num1=31, num2=0)
# Down to 31
Trying example: test_sum(num1=-30, num2=0)Falsifying example: test_sum(num1=31, num2=0)FAILED
# And it finally concludes (num1=31, num2=0) is the simplest test data for which our property doesn't hold true.
1611033973 575 Introducere in testarea bazata pe proprietati in Python
Se micșorează în acțiune.

Încă o caracteristică bună – o să-și amintească acest eșec pentru acest test și va include acest caz de test special stabilit în viitoarele rulări pentru a vă asigura că aceeași regresie nu se strecoară.

Aceasta a fost o introducere ușoară a magiei testării bazate pe proprietăți. Vă recomand tuturor să încercați această abordare în testarea de zi cu zi. Aproape toate limbajele de programare majore au suport pentru testarea bazată pe proprietăți.

Puteți găsi întregul cod utilizat aici în pagina mea? github repo.

Dacă ți-a plăcut conținutul arată câteva ❤️