de Dmitry Rastorguev

O introducere simplă la Test Driven Development cu Python

O introducere simpla la Test Driven Development cu Python

Sunt un dezvoltator autodidact care începe să scrie aplicații simple. Dar am o confesiune de făcut. Este imposibil să-mi amintesc cum totul este interconectat în capul meu.

Această situație se agravează dacă revin la codul pe care l-am scris după câteva zile. Se pare că această problemă ar putea fi depășită urmând un Test Driven Development (TDD) metodologie.

Ce este TDD și de ce este important?

În termeni simpli, TDD recomandă scrierea testelor care ar verifica funcționalitatea codului dvs. înainte de a scrie codul real. Numai când sunteți mulțumit de testele dvs. și de caracteristicile pe care le testează, începeți să scrieți codul propriu-zis pentru a satisface condițiile impuse de test care le-ar permite să treacă.

Urmarea acestui proces vă asigură că planificați cu atenție codul pe care îl scrieți pentru a trece aceste teste. Acest lucru împiedică, de asemenea, posibilitatea amânării testelor de scriere la o dată ulterioară, deoarece acestea nu ar putea fi considerate necesare în comparație cu caracteristicile suplimentare care ar putea fi create în acel moment.

Testele vă oferă, de asemenea, încredere atunci când începeți refactorizarea codului, deoarece este mai probabil să prindeți bug-uri datorită feedback-ului instantaneu atunci când testele sunt executate.

O introducere simpla la Test Driven Development cu Python
Fluxul general de lucru al TDD

Cum să începeți?

Pentru a începe scrierea testelor în Python vom folosi unittest modul care vine cu Python. Pentru a face acest lucru, creăm un fișier nou mytests.py, care va conține toate testele noastre.

Să începem cu „lumea bună” obișnuită:

import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self):        self.assertEqual(hello_world(), 'hello world')

Observați că importăm helloworld() funcție din mycode fişier. În dosar mycode.py inițial vom include doar codul de mai jos, care creează funcția, dar nu returnează nimic în această etapă:

def hello_world():    pass

Alergare python mytests.py va genera următoarea ieșire în linia de comandă:

F
====================================================================
FAIL: test_hello (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 7, in test_hello
self.assertEqual(hello_world(), 'hello world')
AssertionError: None != 'hello world'
--------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)

Acest lucru indică în mod clar că testul a eșuat, ceea ce era de așteptat. Din fericire, am scris deja testele, așa că știm că va fi întotdeauna acolo să verificăm această funcție, ceea ce ne oferă încredere în identificarea potențialelor erori în viitor.

Pentru a ne asigura că codul trece, să schimbăm mycode.py la următoarele:

def hello_world():    return 'hello world'

Alergare python mytests.py din nou obținem următoarea ieșire în linia de comandă:

.
--------------------------------------------------------------------
Ran 1 test in 0.000s
OK

Felicitări! Tocmai ai scris primul test. Să trecem acum la o provocare ceva mai dificilă. Vom crea o funcție care ne va permite să creăm un număr personalizat înțelegerea listei în Python.

Să începem prin a scrie un test pentru o funcție care ar crea o listă de lungime specifică.

În dosar mytests.py aceasta ar fi o metodă test_custom_num_list:

import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self):        self.assertEqual(hello_world(), 'hello world')        def test_custom_num_list(self):        self.assertEqual(len(create_num_list(10)), 10)

Acest lucru ar testa faptul că funcția create_num_list returnează o listă cu lungimea 10. Să creăm funcția create_num_list în mycode.py:

def hello_world():    return 'hello world'
def create_num_list(length):    pass

Alergare python mytests.py va genera următoarea ieșire în linia de comandă:

E.
====================================================================
ERROR: test_custom_num_list (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 14, in test_custom_num_list
self.assertEqual(len(create_num_list(10)), 10)
TypeError: object of type 'NoneType' has no len()
--------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (errors=1)

Acest lucru este așa cum era de așteptat, așa că hai să mergem mai departe și să schimbăm funcția create_num_list în mytest.py pentru a trece testul:

def hello_world():    return 'hello world'
def create_num_list(length):    return [x for x in range(length)]

Executarea python mytests.py pe linia de comandă demonstrează că al doilea test a trecut și acum:

..
--------------------------------------------------------------------
Ran 2 tests in 0.000s
OK

Să creăm acum o funcție personalizată care să transforme fiecare valoare din listă astfel: const * ( X ) ^ power . Mai întâi să scriem testul pentru aceasta, folosind metoda test_custom_func_ care ar lua valoarea 3 ca X, o va duce la puterea lui 3 și se va înmulți cu o constantă de 2, rezultând valoarea 54:

import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self):        self.assertEqual(hello_world(), 'hello world')
def test_custom_num_list(self):        self.assertEqual(len(create_num_list(10)), 10)        def test_custom_func_x(self):        self.assertEqual(custom_func_x(3,2,3), 54)

Să creăm funcția custom_func_x în dosar mycode.py:

def hello_world():    return 'hello world'
def create_num_list(length):    return [x for x in range(length)]
def custom_func_x(x, const, power):    pass

Așa cum era de așteptat, avem un eșec:

F..
====================================================================
FAIL: test_custom_func_x (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 17, in test_custom_func_x
self.assertEqual(custom_func_x(3,2,3), 54)
AssertionError: None != 54
--------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (failures=1)

Funcția de actualizare custom_func_x pentru a trece testul, avem următoarele:

def hello_world():    return 'hello world'
def create_num_list(length):    return [x for x in range(length)]
def custom_func_x(x, const, power):    return const * (x) ** power

Rularea testelor din nou obținem o aprobare:

...
--------------------------------------------------------------------
Ran 3 tests in 0.000s
OK

În cele din urmă, să creăm o nouă funcție care să includă custom_func_x funcționează în înțelegerea listei. Ca de obicei, să începem prin a scrie testul. Rețineți că, pentru a fi siguri, includem două cazuri diferite:

import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self):        self.assertEqual(hello_world(), 'hello world')
def test_custom_num_list(self):        self.assertEqual(len(create_num_list(10)), 10)
def test_custom_func_x(self):        self.assertEqual(custom_func_x(3,2,3), 54)
def test_custom_non_lin_num_list(self):        self.assertEqual(custom_non_lin_num_list(5,2,3)[2], 16)        self.assertEqual(custom_non_lin_num_list(5,3,2)[4], 48)

Acum să creăm funcția custom_non_lin_num_list în mycode.py:

def hello_world():    return 'hello world'
def create_num_list(length):    return [x for x in range(length)]
def custom_func_x(x, const, power):    return const * (x) ** power
def custom_non_lin_num_list(length, const, power):    pass

La fel ca înainte, avem un eșec:

.E..
====================================================================
ERROR: test_custom_non_lin_num_list (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 20, in test_custom_non_lin_num_list
self.assertEqual(custom_non_lin_num_list(5,2,3)[2], 16)
TypeError: 'NoneType' object has no attribute '__getitem__'
--------------------------------------------------------------------
Ran 4 tests in 0.000s
FAILED (errors=1)

Pentru a trece testul, să actualizăm fișierul mycode.py fișier la următoarele:

def hello_world():    return 'hello world'
def create_num_list(length):    return [x for x in range(length)]
def custom_func_x(x, const, power):    return const * (x) ** power
def custom_non_lin_num_list(length, const, power):    return [custom_func_x(x, const, power) for x in range(length)]

Rularea testelor pentru ultima dată, le trecem pe toate!

....
--------------------------------------------------------------------
Ran 4 tests in 0.000s
OK

Felicitări! Aceasta încheie această introducere a testării în Python. Asigurați-vă că verificați resursele de mai jos pentru mai multe informații despre testare în general.

Codul este disponibil aici pe GitHub.

Resurse utile pentru învățarea ulterioară!

Resurse web

Mai jos sunt linkuri către unele dintre bibliotecile axate pe testarea în Python

25.3. unittest – Unitate de testare cadru – Python 2.7.14 documentație
Cadrul de testare a unității Python, denumit uneori „PyUnit”, este o versiune în limbă Python a JUnit, de Kent …docs.python.orgpytest: vă ajută să scrieți programe mai bune – documentare pytest
Cadrul simplifică scrierea unor teste mici, dar scalează pentru a susține testarea funcțională complexă pentru aplicații și …docs.pytest.orgBine ați venit la Ipoteză! – Documentare ipoteză 3.45.2
Funcționează prin generarea de date aleatorii care corespund specificațiilor dvs. și verificarea faptului că garanția dvs. se menține în continuare în …ipoteză.readthedocs.iounittest2 1.1.0: Index pachet Python
Noile caracteristici în backport unic la Python 2.4+.pypi.python.org

Videoclipuri YouTube

Dacă preferați să nu citiți, vă recomand să urmăriți următoarele videoclipuri pe YouTube.