TDD или как я стараюсь писать код
-
Upload
vladimir-filonov -
Category
Software
-
view
92 -
download
1
Transcript of TDD или как я стараюсь писать код
TDD
или как я стараюсь писать код
Владимир Филонов labbler.com
Теория TDD
“Разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода к соответствующим стандартам.” © Wikipedia.org
Тесты
Код Рефакто-ринг
Зачем все это?
Говорят, что TDD помогает
• Улучшить качество кода
• Уменьшить количество ошибок и багов
• Ускорить разработку
В чем сила, брат?
В чем разница?
Тесты
Код Рефакто-ринг
Код
Рефакто-ринг Тесты
IMHO
Вектор мышления
Сначала код:
• Разработчик концентрируется на отдельных частях кода больше чем на общем дизайне
• К моменту написания тестов разработчик уже устает
• Тесты пишутся уже с учетом особенностей реализации, в том числе и костылей, если таковые присутствуют
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)
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") .
Ситуации class OneFieldForm(forms.Form): value = forms.CharField()
if “value” in self.request.POST and \ self.request.POST[“value”]:
TDD
• При написании тестов, мы не отвлекаемся на детали реализации
• Более чёткое и целостное представление о дизайне кода
• Выше скорость написания кода
• Хорошего кода ;)
• Меньше рефакторинга
Смена исполнителя
• Проще понять, что уже сделано, а что еще нет – достаточно запустить тесты
• Тесты как документация – проще разобраться в уже написанном коде
• Более отчуждаемый код
Человечные интерфейсы
• Сразу смотрим на задачу с позиции пользователя интерфейсов
• Не делаем допущений, основанных на знании деталей реализации
• Ниже порог вхождения, меньше подводных камней
• Более отчуждаемый код
Мотивация
Обычное течение разработки
• Начало. Уровень мотивации высокий, все рвутся в бой
• Понеслась – Имплементация – Передача в QA
– Баги
– Дебагинг – Мотивация = - 1* Баги
TDD
• Тесты пишутся на первой волне энтузиазма
• Для написания кода после тестов появляются дополнительные стимулы:
– Четко поставленная цель: пройти все тесты
– Каждый пройденный тест – достижение
– Это поддерживает положительный настрой
• Стимулы для написания тестов после кода
– Требуется очень хорошая самоорганизация – Формальных требований может быть недостаточно для написания хороших тестов
Acceptance TDD
• Пишем приёмочные тесты и ставим задачи команде
• Приёмочные тесты – тесты верхнего уровня абстракции
• Сами пишем тесты на невалидные входные данные
• Кто, кроме нас? :)
• Сам я ещё не пробовал, но очень хочу
Как писать тесты до кода?
• От абстракций верхнего уровня - к абстракциям нижних – Глобальные вещи (views) -> API, DAO -> утилитарные методы
• Проще показать на примере Сделаем виртуальную библиотеку. Книга - название, автор, ISBN Читатель Чтобы получить билет, надо зарегистрироваться. Для простоты, в качестве номера будем использовать django.contrib.auth.models.User.id Читатели могут брать/сдавать книги, но не более 2х одновременно. Если книгу кто-то взял, то другому ее не получить. Брать книги можно по: • 1. Название+Автор • 2. ISBN
#Книги: 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/")
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)
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))
#Поробуем третью 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))
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): """ Попробуем взять книгу, которой нет в библиотеке """ #... #Возврат книги, возврат не взятой книги, #возврат книги не из библиотеки
Полученные тесты можно использовать для ATDD
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): """ Есть ли эта книга у читателя """
def test_get_user_books(self): """ Все книги которые есть у читателя """ def test_get_user_books_count(self): """ Сколько книг у читателя """ def test_book_is_owned(self): """ Взяли ли кто-то эту книгу """
Та-дааа!
Эпилог
• Более продуманный дизайн кода к моменту начала реализации
• Как следствие - более чистый код
• Возможно, более быстрая реализация
• Лучшее покрытие тестами (как по качеству, так и по количеству)
• Дополнительный источник мотивации в процессе
• Более отчуждаемый код
• Меньше багов, а значит и меньше итераций «QA-багфиксинг»
Спасибо! И…
Немного саморекламы =)
https://labbler.com mailto: [email protected]
Спасибо!