Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TDD

1.6k

 TDD

Moscow Python Meetup
PRO

October 25, 2012
Tweet

More Decks by Moscow Python Meetup

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  7. IMHO

    View Slide

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

    View Slide

  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)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. Мотивация

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. Acceptance TDD

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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/")

    View Slide

  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)

    View Slide

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

    View Slide

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

    View Slide

  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):
    """
    Попробуем взять книгу, которой нет в библиотеке
    """
    #...
    #Возврат книги, возврат не взятой книги,
    #возврат книги не из библиотеки

    View Slide

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

    View Slide

  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):
    """
    Есть ли эта книга у читателя
    """

    View Slide

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

    View Slide

  31. Та-дааа!

    View Slide

  32. Эпилог

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  36. Спасибо!

    View Slide