Једноставан увод у Тест Дривен Девелопмент са Питхон-ом

Ја сам самоуки почетни програмер који уме да пише једноставне апликације. Али морам да признам. Немогуће је сетити се како је све у мојој глави међусобно повезано.

Ова ситуација се погоршава ако се вратим коду који сам написао након неколико дана. Испоставило се да би се овај проблем могао превазићи следећи методологију испитивања вођеног тестом (ТДД).

Шта је ТДД и зашто је важан?

Лаички речено, ТДД препоручује писање тестова који би проверили функционалност вашег кода пре него што напишете стварни код. Тек када сте задовољни својим тестовима и карактеристикама које тестира, започињете са писањем стварног кода како бисте задовољили услове постављене тестом који би им омогућили да прођу.

Праћење овог процеса осигурава вам пажљиво планирање кода који напишете како бисте прошли ове тестове. Ово такође спречава могућност да се писање тестова одложи на каснији датум, јер се они можда неће сматрати неопходним у поређењу са додатним функцијама које би могле бити створене током тог времена.

Тестови вам такође дају самопоуздање када започнете са рефакторингом кода, јер је већа вероватноћа да ћете ухватити грешке због тренутних повратних информација када се изврше тестови.

Како започети?

Да бисмо започели писање тестова на Питхону, користићемо unittestмодул који долази са Питхоном. Да бисмо то урадили, креирамо нову датотеку mytests.pyкоја ће садржати све наше тестове.

Почнимо са уобичајеним „здраво свету“:

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

Приметите да helloworld()функцију увозимо из mycodeдатотеке. У датотеку mycode.pyћемо првобитно укључити доњи код који ствара функцију, али у овој фази не враћа ништа:

def hello_world(): pass

Покретање python mytests.pyће генерисати следећи излаз у командној линији:

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)

Ово јасно указује на то да тест није успео, што се и очекивало. Срећом, већ смо написали тестове, па знамо да ће увек бити ту да провери ову функцију, што нам даје поверење у откривању потенцијалних грешака у будућности.

Да бисмо осигурали да код пролази, допуштамо да се променимо mycode.pyу следеће:

def hello_world(): return 'hello world'

Поновним покретањем python mytests.pyдобијамо следећи излаз у командној линији:

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

Честитамо! Управо сте написали свој први тест. Пређимо сада на мало тежи изазов. Направићемо функцију која ће нам омогућити да створимо прилагођено разумевање нумеричке листе у Питхону.

Почнимо са писањем теста за функцију која би креирала листу одређене дужине.

У датотеци mytests.pyби ово био метод 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)

Ово би тестирало да функција create_num_listвраћа листу дужине 10. Створимо функцију create_num_listу mycode.py:

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

Покретање python mytests.pyће генерисати следећи излаз у командној линији:

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)

Ово је како се очекује, тако да идемо напред и функција промене create_num_listу mytest.pyкако би се положио тест:

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

Извршење python mytests.pyна командној линији показује да је и други тест такође положен:

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

Оквир олакшава писање малих тестова, али вага које подржавају сложено функционално тестирање апликација и ... доцс.питест.орг Добродошли у хипотезу! - Документација о хипотези 3.45.2

Функционише тако што генерише случајне податке који одговарају вашој спецификацији и проверава да ли ваша гаранција још увек постоји у тој ... хипотхесис.реадтхедоцс.ио униттест2 1.1.0: Индекс пакета Питхон

Нове функције у униттест-у подржане су Питхон 2.4+. пипи.питхон.орг

Јутјуб Видеи

Ако више не желите да читате, препоручујем вам да погледате следеће видео записе на ИоуТубе-у.