TDD

53b0434aded1fb944ec3037c382158c1?s=47 Moscow Python Meetup
October 25, 2012
1.5k

 TDD

53b0434aded1fb944ec3037c382158c1?s=128

Moscow Python Meetup

October 25, 2012
Tweet

Transcript

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

  2. Теория TDD “Разработка через тестирование (англ. test-driven development, TDD) —

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

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

    кода •  Уменьшить количество ошибок и багов •  Ускорить разработку
  5. В чем сила, брат?

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

    Тесты
  7. IMHO

  8. Вектор мышления Сначала код: •  Разработчик концентрируется на отдельных частях

    кода больше чем на общем дизайне •  К моменту написания тестов разработчик уже устает •  Тесты пишутся уже с учетом особенностей реализации, в том числе и костылей, если таковые присутствуют
  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. 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. Ситуации class OneFieldForm(forms.Form): value = forms.CharField() if “value” in self.request.POST

    and \ self.request.POST[“value”]:
  12. TDD •  При написании тестов, мы не отвлекаемся на детали

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

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

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

  16. Обычное течение разработки •  Начало. Уровень мотивации высокий, все рвутся

    в бой •  Понеслась – Имплементация – Передача в QA – Баги – Дебагинг – Мотивация = - 1* Баги
  17. TDD •  Тесты пишутся на первой волне энтузиазма •  Для

    написания кода после тестов появляются дополнительные стимулы: – Четко поставленная цель: пройти все тесты – Каждый пройденный тест – достижение – Это поддерживает положительный настрой
  18. •  Стимулы для написания тестов после кода – Требуется очень хорошая

    самоорганизация – Формальных требований может быть недостаточно для написания хороших тестов
  19. Acceptance TDD

  20. •  Пишем приёмочные тесты и ставим задачи команде •  Приёмочные

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

  22. •  От абстракций верхнего уровня - к абстракциям нижних – 

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

  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. def test_get_user_books(self): """ Все книги которые есть у читателя """

    def test_get_user_books_count(self): """ Сколько книг у читателя """ def test_book_is_owned(self): """ Взяли ли кто-то эту книгу """
  31. Та-дааа!

  32. Эпилог

  33. •  Более продуманный дизайн кода к моменту начала реализации • 

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

  35. https://labbler.com mailto: vladimir@labbler.com

  36. Спасибо!