RANUX 12.12.2010 12:14

PythonУрок 1.1 TDD для начинающих на Python

Представляю вашему вниманию первую часть первого урока посвящённую разработке через тестирование. Надеюсь будет полезно:) Старался написать максимально простым языком:) Комментарии приветствуются.

Добро пожаловать на серию уроков по Test Driven Development. Для тех кто ещё не знает что такое TDD: Разрабо?тка че?рез тести?рование (англ. test-driven development) — техника программирования, при которой модульные тесты для программы или её фрагмента пишутся до самой программы и, по существу, управляют её разработкой. Основная идея - реализовать правильно работающий функционал, обеспечить возможность повторного использования и простого изменения структуры исходного кода, при этом не изменяя его поведения (рефакторинг)

Давайте в начале рассмотрим из каких
шагов состоит TDD:
Красный. Подготавливаем файл с тестом,
создаём не работающий тест
Зелёный. Заставляем тест сработать
Рефакторинг Удаляем дублирование
Помимо этих основных шагов я применяю в начале нулевой шаг - «исследование и хакинг» в случае, когда я с трудом представляю, как мне пройти последующие 3-и шага. Давайте пройдёмся по этим шагам на примере реализации простой задачи, например вычисления факториала.
Исследование и хакинг. Что нам известно о
факториале?
Давайте разберём пример:
n! = 1 * 2 * 3 * … * n
0! = 1 1! = 1 2! = 2 3! = 6 4! = 24 и так далее

Предположим, что я впервые открыл интерпретатор python и пока с трудом представляю себе как этом можно
реализовать. :) Попробуем поэксперементировать и немного похакать)
Но для начала, составим список того, что нам понадобиться сделать.
Умножение
Функции
Условные операторы
Аргументы функции
Рекурсия
Запускаем консоль и заходим в python.

>>> 2 * 3
6
Умножение
Функции
Условные операторы
Аргументы функции
Рекурсия
С умножением всё ясно. Теперь попробуем написать какую-нибудь простую функцию:

1
2
3
4
5
>>> def f():
... print "hello"
...
>>> f()
hello



Отлично, наша функция отработала. Не забываем про отступы в функциях, те перед print "hello" у меня стоят два пробела.
Посмотрим как функция будет возвращать наше значение:


1
2
3
4
5
>>> def f():
... return "hello"
...
>>> f()
'hello'



С этим разобрались.
Умножение
Функции
Условные операторы
Аргументы функции
Рекурсия
Теперь посмотрим как работают условные операторы и аргументы функции:

 1
2
3
4
5
6
7
8
9
10
>>> def f(flag):
... if flag:
... return True
... else:
... return False
...
>>> f(1)
True
>>> f(0)
False



Если наш флаг присутствует, то возвращаем True, в противном случае False.
Умножение
Функции
Условные операторы
Аргументы функции
Рекурсия
Что же такое рекурсия. Рекурсия — это функция вызывающая сама себя. Давайте разберёмся:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> def f(n):
... if n == 0:
... return 0
... else:
... print "n: %s" % n
... return f(n-1)
...
>>> f(5)
n: 5
n: 4
n: 3
n: 2
n: 1
0



В месте return f(n-1) функция будет вызывать сама себя, но n при этом будет уменьшаться на единицу. Конструкция print "n: %s" % n
вставляет n в строку и форматирует его как строковое значение.
Умножение
Функции
Условные операторы
Аргументы функции
Рекурсия
Достаточно поэкспериментировав, мы готовы перейти к разработке через тестирование нашей функции факториала :)
Что-бы упростить задачу, воспользуемся
doctest-ами.

Снова составим список того, что нам надо
сделать:
Эксперимент с doctest
Вычисление факториала
Созадём файл factor.py содержащий:

1
2
3
4
5
#!/usr/bin/python
# -*- coding: UTF-8 -*-

if __name__ == '__main__':
pass



Попробуйте его запустить из консоли: python
factor.py
Отлично, пока что у нас нечего не выводиться, но файл выполнился.

Попробуем написать тест для простой функции. Doctest записывается в документации к функции, те сразу после названия функции.

Смотрим пример:
 1
2
3
4
5
6
7
8
9
10
11
12
def f(a, b):
'''
Функция складывает два числа
>>> f(1,2)
3
'''
pass

if __name__ == '__main__':
import doctest
doctest.testmod()
pass



Оператор pass означает, что делать нечего не надо. Попробуем запустить: python factor.py

В результате видим:
**********************************************************************
File "factor.py", line 11, in __main__.f
Failed example:
f(1,2)
Expected:
3
Got nothing
**********************************************************************
1 items had failures:
1 of 1 in __main__.f
***Test Failed*** 1 failures.

Отлично, мы проделали первый шаг TDD - «Красный». Приступим к зелёному .

1
2
3
4
5
6
7
def f(a, b):
'''
Функция складывает два числа
>>> f(1,2)
3
'''
return 1 + 2



Запускаем и видим, что наша функция прошла тест, второй шаг «Зелёный» вроде пройден, но что если мы попробуем вызвать
f(2, 3). Изменим функцию и посмотрим результат:

1
2
3
4
5
6
7
8
9
def f(a, b):
'''
Функция складывает два числа
>>> f(1,2)
3
>>> f(2,3)
5
'''
return 1 + 2



Запускаем python factor.py
**********************************************************************
File "factor.py", line 13, in __main__.f
Failed example:
f(2,3)
Expected:
5
Got:
3
**********************************************************************
1 items had failures:
1 of 2 in __main__.f
***Test Failed*** 1 failures

Что и следовало ожидать, мы хотим 2 + 3 = 5, но получили 3. Теперь у нас достаточно тестов чтобы реализовать нашу функцию. Приступим:

1
2
3
4
5
6
7
8
9
def f(a, b):
'''
Функция складывает два числа
>>> f(1,2)
3
>>> f(2,3)
5
'''
return a + b




Запускаем python factor.py и видим, что тесты прошли. Отлично. Мы написали первую функию в рамках TDD и разобрались с doctest-ом.

На этом я заканчиваю первую часть первого урока. Во второй части разберёмся как реализовать функцию вычисления
факториала.
Эксперимент с doctest
Вычисление факториала
Домашнее задание
Напишите функцию разницы. Тест для функции:
 1
2
3
4
5
6
7
8
9
10
11
def minus(a, b):
'''
Функция разницы
>>> f(4,2)
2
>>> f(2,4)
-2
>>> f(10,3)
7
'''
pass


Попробуйте написать функцию вычисления факториала используя подход, который я описал для функции сложения. Если не получиться, то попробуйте реализовать как сможете.


Тэги: python tdd тестирование
+ 17 -
Похожие Поделиться

wiz 12.12.2010 12:26 #
имхо лучше не встраивать всю эту дребедень в __main__, а пользоваться правильными аргументами: python -m doctest factor.py

потмоу что тесты приходят и уходят, а код остаётся.
RANUX 12.12.2010 12:49 #
В следующем уроке учтём :)
wiz 12.12.2010 12:29 #
вместо pass и подобных загушек рекомендую использовать честные raise NotImplementedError

также, многие редакторы подсвечивают строки типа TODO и FIXME
wiz 12.12.2010 12:30 #
Жду продолжения, своими идеями поделюсь когда будут затрагиваться более продвинутые аспекты.
ZogG 12.12.2010 17:17 #
спасибо, плюсанул, но...
если пост ссылка, то зачем копировать всё?
и если уж скопировал, то кат всеравно раньше ставь.
RANUX 12.12.2010 17:19 #
Всё. Разобрался. Спасибо)) Я просто не сразу понял, что за кат))
knyazeff 12.12.2010 17:26 #
Есть мнение, что пока еще не разобрался :-)
knyazeff 12.12.2010 17:27 #
Умолкаю...
RANUX 12.12.2010 17:34 #
Учимся потихоньку)
ZogG 12.12.2010 17:30 #
вот так отлично, я бы даже перед самим катом добавил бы:
«продолжение внутри» или «кто заинтересовался, продолжение под катом»
RANUX 12.12.2010 17:33 #
Вроде и так можно нажать на "Подробнее" )
RANUX 12.12.2010 17:34 #
Точнее "Полностью"))
Elvis 12.12.2010 18:13 #
Похоже что все вышеописанное - это часть стандартной документации unittest.
Дело в том, что полным новичкам не нужно TDD, а более продвинутые уже все это и так знают.

Я бы посоветовал вам написать:
- про mock библиотеки
- про nosetest, как запускать его с django
- про инструменты BDD
- про интеграцию code coverage с nosetest
- рассказать о том, какин нужно писать тесты, а какие не нужно (антипаттерны).

С этими вещами приходится сталкиваться в реальной разработке и мне кажется, что статья с описанием подобных вещей была бы полезнее.

exelens 12.12.2010 18:28 #
Я бы посоветовал вам написать:

про что мог сам бы написать?
Elvis 12.12.2010 19:27 #
В ближайшие три недели - ни про что, на днях уезжаю в жаркие страны...
А так, могу написать про питон, джангу, много чего, если в теме будет заинтересовано сообщество.
Sylar 12.12.2010 20:34 #
Пиши конечно.
ЗЫ: Удачного отдыха :)
RANUX 12.12.2010 19:20 #
В прошлом посте http://welinux.ru/post/4684/ я попытался привести пример более сложного способа тестирования. Но была просьба начать с вещей по проще. В TDD главное понять основу. Всю методику я не расскажу для этого есть соответствующие книги того же Кента Бэка, но вот своим опытом я пытаюсь поделиться:))
wiz 12.12.2010 23:24 #
Новичкам надо сразу давать программирование, как инженерную дисциплину, а то в последнее время народ начал такой говнокод, что индусы плачут, его читая.

К слову, мы у себя в конторе прям сразу же с первого же занятия ввели TDD. И знаете, нормально восприняли, благо что в питоне с этим всё нормально - и доктесты, и юнит и всё что хочешь. И при этом не надо ничего перекомпиливать.

Так.. О чём это я? А!.. Ну, собственно, я тоже хотел всё это посоветовать, но видимо, у RANUX уже есть какая-то программа в плане, так что ждём новых постов и там уже обсудим.

А вообще конечно было бы неплохо сварганить "новый курс молодого бойца", осветив в нём всё, что не преподают в институтах, и с чем начинающий програмер сталкивается на предприятиях.
kstep 13.12.2010 03:02 #
Да, TDD и прочими юнит-тестами такая беда: если их с самого начала не начать юзать, то потом чем больше кода, тем сложнее его ввести =(
wiz 13.12.2010 12:01 #
тем сложнее его ввести

в моск
RANUX 13.12.2010 07:28 #
Я тоже всегда хотел работать в конторе где TDD и Python, но пока-что приходиться рыться в системах с быдлокодом:( В общем, удовольствия от быдлокодерских систем не испытываю, скорее одно сплошное раздражение, а покрывать тестами и рефакторить в одиночку желания нет.
Так.. О чём это я? А!.. Ну, собственно, я тоже хотел всё это посоветовать, но видимо, у RANUX уже есть какая-то программа в плане, так что ждём новых постов и там уже обсудим.

Идей полно, вот только времени не всегда хватает, но в любом случае продолжение будет:)
Minoru 14.12.2010 05:25 #
А вообще конечно было бы неплохо сварганить "новый курс молодого бойца", осветив в нём всё, что не преподают в институтах, и с чем начинающий програмер сталкивается на предприятиях.
Я бы такой пост с удовольствием почитал, даже если бы это был просто список ссылок на актуальные подходы, алгоритмы, книги и прочее.
knyazeff 12.12.2010 21:03 #
Мужики! Я прошу прощения за оффтопик, а может кто-нибудь написать про программирование под андроид, полагаю, это по теме ресурса.
Вот только не про то, как поставить эклипс и прикрутить к нему андроид SDK и плагины, а реально работающую программу разобрать.
Вот это было бы нереально круто!
kmarks 13.12.2010 10:34 #
Возьми сам и напиши. Не думаю что очень сложно.
kmarks 13.12.2010 13:51 #
Спасибо за статью! Всё выполнил.
Задание по факториалу сделал, но почему-то не удалось выполнить полную проверку на натуральность числа. Подробности на pastie.org

В следующей части, пожалуйста, не забудьте снова напомнить про все шаги TTD (а то вылетит из головы как что и зачем) и научить пользоваться аргументами, переданными из командной строки:
fact.py -n 5 //так вроде по POSIX правильно
Sylar 14.12.2010 01:25 #
Ты, наверное, хотел
if n >= 0 and n % 1 == 0

По аргументам -- доки по argparse, getopt.
Вот тут еще человек скрипт выкладывал с опциями.
kmarks 14.12.2010 12:48 #
Точно, n % 1 == 0
А я что-то тупанул, хотя смысл операции остатка от деления понял как надо.
Спасибо.