TDD или как я стараюсь писать код

1,845 views

Published on

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

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,845
On SlideShare
0
From Embeds
0
Number of Embeds
97
Actions
Shares
0
Downloads
19
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

TDD или как я стараюсь писать код

  1. 1. TDDили как я стараюсь писать кодВладимир Филоновlabbler.com
  2. 2. Теория TDD“Разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода к соответствующим стандартам.” © Wikipedia.org
  3. 3. ТестыРефакто- Код ринг
  4. 4. Зачем все это?Говорят, что TDD помогает•  Улучшить качество кода•  Уменьшить количество ошибок и багов•  Ускорить разработку
  5. 5. В чем сила, брат?
  6. 6. В чем разница? Тесты КодРефакто- Рефакто- ринг Код Тесты ринг
  7. 7. IMHO
  8. 8. Вектор мышленияСначала код:•  Разработчик концентрируется на отдельных частях кода больше чем на общем дизайне•  К моменту написания тестов разработчик уже устает•  Тесты пишутся уже с учетом особенностей реализации, в том числе и костылей, если таковые присутствуют
  9. 9. 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)
  10. 10. 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").
  11. 11. Ситуацииclass OneFieldForm(forms.Form): value = forms.CharField()if “value” in self.request.POST and self.request.POST[“value”]:
  12. 12. TDD•  При написании тестов, мы не отвлекаемся на детали реализации•  Более чёткое и целостное представление о дизайне кода•  Выше скорость написания кода•  Хорошего кода ;)•  Меньше рефакторинга
  13. 13. Смена исполнителя•  Проще понять, что уже сделано, а что еще нет – достаточно запустить тесты•  Тесты как документация – проще разобраться в уже написанном коде•  Более отчуждаемый код
  14. 14. Человечные интерфейсы•  Сразу смотрим на задачу с позиции пользователя интерфейсов•  Не делаем допущений, основанных на знании деталей реализации•  Ниже порог вхождения, меньше подводных камней•  Более отчуждаемый код
  15. 15. Мотивация
  16. 16. Обычное течение разработки•  Начало. Уровень мотивации высокий, все рвутся в бой•  Понеслась –  Имплементация –  Передача в QA –  Баги –  Дебагинг –  Мотивация = - 1* Баги
  17. 17. TDD•  Тесты пишутся на первой волне энтузиазма•  Для написания кода после тестов появляются дополнительные стимулы: –  Четко поставленная цель: пройти все тесты –  Каждый пройденный тест – достижение –  Это поддерживает положительный настрой
  18. 18. •  Стимулы для написания тестов после кода –  Требуется очень хорошая самоорганизация –  Формальных требований может быть недостаточно для написания хороших тестов
  19. 19. Acceptance TDD
  20. 20. •  Пишем приёмочные тесты и ставим задачи команде•  Приёмочные тесты – тесты верхнего уровня абстракции•  Сами пишем тесты на невалидные входные данные•  Кто, кроме нас? :)•  Сам я ещё не пробовал, но очень хочу
  21. 21. Как писать тесты до кода?
  22. 22. •  От абстракций верхнего уровня - к абстракциям нижних –  Глобальные вещи (views) -> API, DAO -> утилитарные методы•  Проще показать на примереСделаем виртуальную библиотеку.Книга - название, автор, ISBNЧитательЧтобы получить билет, надо зарегистрироваться. Для простоты, вкачестве номера будем использовать django.contrib.auth.models.User.idЧитатели могут брать/сдавать книги, но не более 2х одновременно.Если книгу кто-то взял, то другому ее не получить.Брать книги можно по:•  1. Название+Автор•  2. ISBN
  23. 23. #Книги: title, author, year, isbn##Капитал, Кащей Б.С., 942 г., 1234-5678-9#100 диетических блюд из репы, Прекрасная В., 1142 г.,# 4321-8765-9#Ковка подков, Гефест, 2675 г. до н.э. 9876-5432-1#Бустâн, Абу Мухаммад Муслих ад-Дин ибн Абд Аллах Саади# Ширази, 1257 г., 6789-2345-1class 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/")
  24. 24. 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)
  25. 25. 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))
  26. 26. #Поробуем третью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))
  27. 27. 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): """ Попробуем взять книгу, которой нет в библиотеке """#...#Возврат книги, возврат не взятой книги,#возврат книги не из библиотеки
  28. 28. Полученные тесты можно использовать для ATDD
  29. 29. 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): """ Есть ли эта книга у читателя """
  30. 30. def test_get_user_books(self): """ Все книги которые есть у читателя """def test_get_user_books_count(self): """ Сколько книг у читателя """def test_book_is_owned(self): """ Взяли ли кто-то эту книгу """
  31. 31. Та-дааа!
  32. 32. Эпилог
  33. 33. •  Более продуманный дизайн кода к моменту начала реализации•  Как следствие - более чистый код•  Возможно, более быстрая реализация•  Лучшее покрытие тестами (как по качеству, так и по количеству)•  Дополнительный источник мотивации в процессе•  Более отчуждаемый код•  Меньше багов, а значит и меньше итераций «QA- багфиксинг»
  34. 34. Спасибо! И…Немного саморекламы =)
  35. 35. https://labbler.commailto: vladimir@labbler.com
  36. 36. Спасибо!

×