En simpel introduktion til testdrevet udvikling med Python

Jeg er en selvlært begynderudvikler, der er i stand til at skrive enkle apps. Men jeg skal tilstå. Det er umuligt at huske, hvordan alt er forbundet i mit hoved.

Denne situation forværres, hvis jeg kommer tilbage til den kode, jeg har skrevet efter et par dage. Viser sig, at dette problem kunne løses ved at følge en testdrevet udviklingsmetode (TDD).

Hvad er TDD, og ​​hvorfor er det vigtigt?

I lægmandssprog anbefaler TDD at skrive tests, der kontrollerer funktionaliteten af ​​din kode, inden du skriver den aktuelle kode. Først når du er tilfreds med dine tests og de funktioner, den tester, begynder du at skrive den faktiske kode for at opfylde de betingelser, der er pålagt af testen, der gør det muligt for dem at bestå.

At følge denne proces sikrer, at du omhyggeligt planlægger den kode, du skriver, for at bestå disse tests. Dette forhindrer også muligheden for at skrive prøver bliver udsat til et senere tidspunkt, da de måske ikke anses for nødvendige i forhold til yderligere funktioner, der kunne oprettes i løbet af den tid.

Test giver dig også selvtillid, når du begynder at omformere kode, da du er mere tilbøjelig til at fange bugs på grund af den øjeblikkelige feedback, når test udføres.

Hvordan kommer jeg i gang?

For at begynde at skrive tests i Python bruger vi det unittestmodul, der følger med Python. For at gøre dette opretter vi en ny fil mytests.py, der indeholder alle vores tests.

Lad os begynde med den sædvanlige "hej verden":

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

Bemærk, at vi importerer helloworld()funktion fra mycodefil. I filen mycode.pyinkluderer vi oprindeligt bare koden nedenfor, som opretter funktionen, men ikke returnerer noget på dette trin:

def hello_world(): pass

Kørsel python mytests.pygenererer følgende output på kommandolinjen:

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)

Dette indikerer tydeligt, at testen mislykkedes, hvilket var forventet. Heldigvis har vi allerede skrevet testene, så vi ved, at det altid vil være der for at kontrollere denne funktion, hvilket giver os tillid til at spotte potentielle fejl i fremtiden.

For at sikre, at koden passerer, kan vi ændre mycode.pytil følgende:

def hello_world(): return 'hello world'

Kører python mytests.pyigen får vi følgende output i kommandolinjen:

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

Tillykke! Du har lige skrevet din første test. Lad os nu gå over til en lidt sværere udfordring. Vi opretter en funktion, der giver os mulighed for at oprette en brugerdefineret numerisk listeforståelse i Python.

Lad os begynde med at skrive en test til en funktion, der opretter en liste med specifik længde.

I filen mytests.pyville dette være en metode 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)

Dette vil teste, at funktionen create_num_listreturnerer en liste over længde 10. Lad os oprette funktion create_num_listi mycode.py:

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

Kørsel python mytests.pygenererer følgende output på kommandolinjen:

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)

Dette er som forventet, så lad os gå videre og ændre funktion create_num_listi mytest.pymed henblik på at bestå prøven:

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

Udførelse python mytests.pypå kommandolinjen viser, at den anden test også nu er bestået:

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

Let’s now create a custom function that would transform each value in the list like this: const * ( X ) ^ power . First let’s write the test for this, using method test_custom_func_ that would take value 3 as X, take it to the power of 3, and multiply by a constant of 2, resulting in the value 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)

Let’s create the function custom_func_x in the file 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

As expected, we get a fail:

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)

Updating function custom_func_x to pass the test, we have the following:

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

Running the tests again we get a pass:

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

Finally, let’s create a new function that would incorporate custom_func_x function into the list comprehension. As usual, let’s begin by writing the test. Note that just to be certain, we include two different cases:

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)

Now let’s create the function custom_non_lin_num_list in 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

As before, we get a fail:

.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)

In order to pass the test, let’s update the mycode.py file to the following:

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)]

Running the tests for the final time, we pass all of them!

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

Congrats! This concludes this introduction to testing in Python. Make sure you check out the resources below for more information on testing in general.

The code is available here on GitHub.

Useful resources for further learning!

Web resources

Below are links to some of the libraries focusing on testing in Python

25.3. unittest - Unit testing framework - Python 2.7.14 documentation

The Python unit testing framework, sometimes referred to as "PyUnit," is a Python language version of JUnit, by Kent…docs.python.orgpytest: helps you write better programs - pytest documentation

Rammen gør det let at skrive små tests, men skalerer til understøttelse af kompleks funktionel test til applikationer og ... docs.pytest.org Velkommen til hypotese! - Hypotese 3.45.2 dokumentation

Det fungerer ved at generere tilfældige data, der matcher din specifikation og kontrollere, at din garanti stadig holder i, at ... hypothesis.readthedocs.io unittest2 1.1.0: Python Package Index

De nye funktioner i unittest backported til Python 2.4+. pypi.python.org

YouTube-videoer

Hvis du foretrækker ikke at læse, anbefaler jeg at se følgende videoer på YouTube.