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

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

Embed Size (px)

DESCRIPTION

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

Citation preview

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

TDD

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

Владимир Филонов labbler.com

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

Теория TDD

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

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

Тесты

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

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

Зачем все это?

Говорят, что TDD помогает

•  Улучшить качество кода

•  Уменьшить количество ошибок и багов

•  Ускорить разработку

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

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

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

В чем разница?

Тесты

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

Код

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

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

IMHO

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

Вектор мышления

Сначала код:

•  Разработчик концентрируется на отдельных частях кода больше чем на общем дизайне

•  К моменту написания тестов разработчик уже устает

•  Тесты пишутся уже с учетом особенностей реализации, в том числе и костылей, если таковые присутствуют

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

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)

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

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

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

Ситуации class OneFieldForm(forms.Form): value = forms.CharField()

if “value” in self.request.POST and \ self.request.POST[“value”]:

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

TDD

•  При написании тестов, мы не отвлекаемся на детали реализации

•  Более чёткое и целостное представление о дизайне кода

•  Выше скорость написания кода

•  Хорошего кода ;)

•  Меньше рефакторинга

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

Смена исполнителя

•  Проще понять, что уже сделано, а что еще нет – достаточно запустить тесты

•  Тесты как документация – проще разобраться в уже написанном коде

•  Более отчуждаемый код

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

Человечные интерфейсы

•  Сразу смотрим на задачу с позиции пользователя интерфейсов

•  Не делаем допущений, основанных на знании деталей реализации

•  Ниже порог вхождения, меньше подводных камней

•  Более отчуждаемый код

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

Мотивация

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

Обычное течение разработки

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

•  Понеслась – Имплементация – Передача в QA

– Баги

– Дебагинг – Мотивация = - 1* Баги

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

TDD

•  Тесты пишутся на первой волне энтузиазма

•  Для написания кода после тестов появляются дополнительные стимулы:

– Четко поставленная цель: пройти все тесты

– Каждый пройденный тест – достижение

– Это поддерживает положительный настрой

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

•  Стимулы для написания тестов после кода

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

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

Acceptance TDD

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

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

•  Приёмочные тесты – тесты верхнего уровня абстракции

•  Сами пишем тесты на невалидные входные данные

•  Кто, кроме нас? :)

•  Сам я ещё не пробовал, но очень хочу

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

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

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

•  От абстракций верхнего уровня - к абстракциям нижних –  Глобальные вещи (views) -> API, DAO -> утилитарные методы

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

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

#Книги: 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/")

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

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)

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

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

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

#Поробуем третью 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))

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

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

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

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

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

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

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

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

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

Та-дааа!

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

Эпилог

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

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

•  Как следствие - более чистый код

•  Возможно, более быстрая реализация

•  Лучшее покрытие тестами (как по качеству, так и по количеству)

•  Дополнительный источник мотивации в процессе

•  Более отчуждаемый код

•  Меньше багов, а значит и меньше итераций «QA-багфиксинг»

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

Спасибо! И…

Немного саморекламы =)

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

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

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

Спасибо!