Slide 1

Slide 1 text

TDD или как я стараюсь писать код Владимир Филонов labbler.com

Slide 2

Slide 2 text

Теория TDD “Разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода к соответствующим стандартам.” © Wikipedia.org

Slide 3

Slide 3 text

Тесты Код Рефакто- ринг

Slide 4

Slide 4 text

Зачем все это? Говорят, что TDD помогает •  Улучшить качество кода •  Уменьшить количество ошибок и багов •  Ускорить разработку

Slide 5

Slide 5 text

В чем сила, брат?

Slide 6

Slide 6 text

В чем разница? Тесты Код Рефакто- ринг Код Рефакто- ринг Тесты

Slide 7

Slide 7 text

IMHO

Slide 8

Slide 8 text

Вектор мышления Сначала код: •  Разработчик концентрируется на отдельных частях кода больше чем на общем дизайне •  К моменту написания тестов разработчик уже устает •  Тесты пишутся уже с учетом особенностей реализации, в том числе и костылей, если таковые присутствуют

Slide 9

Slide 9 text

class OneFieldForm(forms.Form): value = forms.IntegerField() class SimpleView(View): def get(self, *args, **kwargs): form = OneFieldForm() return render_template(form) def post(self, *args, **kwargs): form = OneFieldForm(self.request.POST) if form.is_valid(): #Какой-то сложный, утомительный код, который #использует значение из формы return render_to_response(“success.html") else: return render_template(form) def render_template(self, form): context = { "form": form,} return render_to_response("template.html", context)

Slide 10

Slide 10 text

class SimpleViewTestCase(TestCase): def test_simple_view(self): response = self.client.get(”/”) self.assertEqual(response.tempates[0].name, “template.html ") response2 = self.client.post(”/”) self.assertEqual(response2.tempates[0].name, “template.html") response3 = self.client.post(”/”, {“count”: 1}) self.assertEqual(response3.tempates[0].name, “success.html") .

Slide 11

Slide 11 text

Ситуации class OneFieldForm(forms.Form): value = forms.CharField() if “value” in self.request.POST and \ self.request.POST[“value”]:

Slide 12

Slide 12 text

TDD •  При написании тестов, мы не отвлекаемся на детали реализации •  Более чёткое и целостное представление о дизайне кода •  Выше скорость написания кода •  Хорошего кода ;) •  Меньше рефакторинга

Slide 13

Slide 13 text

Смена исполнителя •  Проще понять, что уже сделано, а что еще нет – достаточно запустить тесты •  Тесты как документация – проще разобраться в уже написанном коде •  Более отчуждаемый код

Slide 14

Slide 14 text

Человечные интерфейсы •  Сразу смотрим на задачу с позиции пользователя интерфейсов •  Не делаем допущений, основанных на знании деталей реализации •  Ниже порог вхождения, меньше подводных камней •  Более отчуждаемый код

Slide 15

Slide 15 text

Мотивация

Slide 16

Slide 16 text

Обычное течение разработки •  Начало. Уровень мотивации высокий, все рвутся в бой •  Понеслась – Имплементация – Передача в QA – Баги – Дебагинг – Мотивация = - 1* Баги

Slide 17

Slide 17 text

TDD •  Тесты пишутся на первой волне энтузиазма •  Для написания кода после тестов появляются дополнительные стимулы: – Четко поставленная цель: пройти все тесты – Каждый пройденный тест – достижение – Это поддерживает положительный настрой

Slide 18

Slide 18 text

•  Стимулы для написания тестов после кода – Требуется очень хорошая самоорганизация – Формальных требований может быть недостаточно для написания хороших тестов

Slide 19

Slide 19 text

Acceptance TDD

Slide 20

Slide 20 text

•  Пишем приёмочные тесты и ставим задачи команде •  Приёмочные тесты – тесты верхнего уровня абстракции •  Сами пишем тесты на невалидные входные данные •  Кто, кроме нас? :) •  Сам я ещё не пробовал, но очень хочу

Slide 21

Slide 21 text

Как писать тесты до кода?

Slide 22

Slide 22 text

•  От абстракций верхнего уровня - к абстракциям нижних –  Глобальные вещи (views) -> API, DAO -> утилитарные методы •  Проще показать на примере Сделаем виртуальную библиотеку. Книга - название, автор, ISBN Читатель Чтобы получить билет, надо зарегистрироваться. Для простоты, в качестве номера будем использовать django.contrib.auth.models.User.id Читатели могут брать/сдавать книги, но не более 2х одновременно. Если книгу кто-то взял, то другому ее не получить. Брать книги можно по: •  1. Название+Автор •  2. ISBN

Slide 23

Slide 23 text

#Книги: title, author, year, isbn # #Капитал, Кащей Б.С., 942 г., 1234-5678-9 #100 диетических блюд из репы, Прекрасная В., 1142 г., # 4321-8765-9 #Ковка подков, Гефест, 2675 г. до н.э. 9876-5432-1 #Бустâн, Абу Мухаммад Муслих ад-Дин ибн Абд Аллах Саади # Ширази, 1257 г., 6789-2345-1 class LibraryViewsTestCase(TestCase): def setUp(self): self.ivan = User.objects.get(username="ivan") self.maria = User.objects.get(username="maria") self.books = INITIAL_BOOKS_LIST def test_library_urls(self): self.assertEqual(reverse("library:take"), "/take/") self.assertEqual(reverse("library:return"), "/return/")

Slide 24

Slide 24 text

def test_get_book_view__unauth(self): """ Книжки можно раздавать только авторизованным пользователям """ response = self.client.post("/take/", {"isbn": "1234-5678-9"}) self.assertEqual(response.status_code, 301) self.client.login(username="ivan", password="durak") response = self.client.post("/take/", {"isbn": "1234-5678-9"}) self.assertEqual(response.status_code, 200)

Slide 25

Slide 25 text

def test_get_book_view__isbn(self): """ Наберем книжек по ISBN """ self.client.login(username="ivan", password="pozhaluista") #Возьмем книгу response = self.client.post("/take/", {"isbn": "1234-5678-9"}) self.assertEqual(response.status_code, 200) self.assertIn("book", response.context) self.assertEqual(response.context["book"]["title"], u"Капитал") self.assertEqual(response.templates[0].name, "take_success.html") self.assertEqual(get_user_books_count(self.ivan), 1) self.assertIn(self.books[0], get_user_books(self.ivan)) #Еще одну self.client.post("/take/", {"isbn": "9876-5432-1"}) self.assertEqual(get_user_books_count(self.ivan), 2) self.assertIn(self.books[0], get_user_books(self.ivan)) self.assertIn(self.books[2], get_user_books(self.ivan))

Slide 26

Slide 26 text

#Поробуем третью failed_response = self.client.post("/take/", {"isbn": "6789-2345-1"}) self.assertIn("book", response.context) self.assertEqual(response.context["book"]["title"], u"Бустâн") self.assertEqual(response.templates[0].name, "take_failed.html") self.assertIn("error", response.context) self.assertEqual(response.context["error"], u"Хватит уже") self.assertEqual(get_user_books_count(self.ivan), 2) self.assertIn(self.books[0], get_user_books(self.ivan)) self.assertIn(self.books[2], get_user_books(self.ivan))

Slide 27

Slide 27 text

def test_get_book_view__title_and_author(self): """ Берем книги по заголовку и автору """ def test_get_book_view__taken(self): """ Попробуем взять книгу, которую уже унес кто-то """ def test_get_book_view__again(self): """ Попробуем взять одну и ту же книгу дважды """ def test_get_book_view__unknown(self): """ Попробуем взять книгу, которой нет в библиотеке """ #... #Возврат книги, возврат не взятой книги, #возврат книги не из библиотеки

Slide 28

Slide 28 text

Полученные тесты можно использовать для ATDD

Slide 29

Slide 29 text

class LibriatyDaoTestCase(TestCase): """ Следующий уровень - функции, которые нам понадобятся, чтобы решить ситуации, описанные выше """ def test_get_book_by_isbn(self): """ Получаем их базы книгу по isbn """ def test_get_book_by_title_and_author(self): """ Получаем из базы книгу по isbn """ def test_user_has_book(self): """ Есть ли эта книга у читателя """

Slide 30

Slide 30 text

def test_get_user_books(self): """ Все книги которые есть у читателя """ def test_get_user_books_count(self): """ Сколько книг у читателя """ def test_book_is_owned(self): """ Взяли ли кто-то эту книгу """

Slide 31

Slide 31 text

Та-дааа!

Slide 32

Slide 32 text

Эпилог

Slide 33

Slide 33 text

•  Более продуманный дизайн кода к моменту начала реализации •  Как следствие - более чистый код •  Возможно, более быстрая реализация •  Лучшее покрытие тестами (как по качеству, так и по количеству) •  Дополнительный источник мотивации в процессе •  Более отчуждаемый код •  Меньше багов, а значит и меньше итераций «QA- багфиксинг»

Slide 34

Slide 34 text

Спасибо! И… Немного саморекламы =)

Slide 35

Slide 35 text

https://labbler.com mailto: [email protected]

Slide 36

Slide 36 text

Спасибо!