Upload
-
View
49
Download
1
Embed Size (px)
Citation preview
50 оттенков красногоИли тестирование без боли
Сергей Александрович, @darth_sim
Немного о себеМеня зовут СергейЯ разрабатываю backend у Злых МарсианЯ люблю писать тесты
Сегодня мы:Поговорим о том, зачем мы пишем тесты;Уменьшим объем тестов без потери качества;Уменьшим время написания и цену тестов;Упростим поддержку.
DisclaimerСпорить о тестах можно много и долго. Все сказанное
здесь - мое мнение, основанное на личном опыте
Спонсор многих слайдов
Почему нужно писатьтесты?
Почему нужно писать тесты?Чтобы беречь свое время при разработке;
Факт от Капитана: автоматические тесты выполняются напорядок быстрее ручных.
Почему нужно писать тесты?Чтобы беречь свое время при разработке;Чтобы не бояться что-то сломать;
Факт от Капитана: Все делают ошибки. Кто не делаетошибок, тот нагло врет.
Почему нужно писать тесты?Чтобы беречь свое время при разработке;Чтобы не бояться что-то сломать;Чтобы держать архитектуру приложения в форме.(касается в основном unit-тестов)
Почему нужно писать тесты?Тесты - это взгляд на код со стороны.
Код сложно тестировать? ⬇
Код сложен, запутан ⬇
Код нуждается в рефакторинге
Почему иногда мы не пишемтесты?
Потому что часто тесты выглядят вот так:
Пора навести порядок!
Test coverage
Test coverageОднажды программист спроcил Великого
Мастера: «Какого покрытия тестами я должендостичь?»
goo.gl/NH84c6
Test coverageНе дает никакого представления о том, насколькохорошо протестирован код;Показывает, какие места точно не протестированы, ноне наоборот;Не дает никакого представления о качестве кода;Не та метрика, за которой стоит гнаться.
Test coverage100% не стоит вашего времени;90% — это очень хорошее покрытие;70% вполне достаточно.
Test coverage“Мне платят за код, который работает, а не затесты, поэтому моя философия заключается в
том, чтобы тестировать настолько мало,насколько это возможно для достижения
нужного уровня уверенности„(Кент Бек)
Выбрасываем лишнее
Выбрасываем лишнееТесты некритичного кода
Если ошибка в коде не повлечет за собой серьезныхпоследствий, то тестирование этого участка кода совсем
не обязательно.
Выбрасываем лишнееТесты поведения сторонних библиотек
Большинство библиотек уже протестированыразработчиком;Если вас не устраивает, как они протестированы, лучшесделать контрибьют, чем держать тесты у себя.
Выбрасываем лишнееКосвенно выполненные проверки
Проверка на наличие классов и методов;Проверка количества аргументов функций;Проверка на отсутствие исключений.Иногда такие проверки необходимы, но такие случаиредки.
Выбрасываем лишнееТесты приватных методов
Приватные методы проверяются тестами открытыхметодов, в которых они вызываются
Выбрасываем лишнееТесты тривиального кода
“Если я не делаю ошибок какого-то рода, я нетестирую код на их наличие„
(Кент Бек)
Приводим оставшееся впорядок
Приводим оставшееся в порядок“Пишите код так, как будто сопровождатьего будет склонный к насилию психопат,
который знает, где вы живёте„(Мартин Голдинг)
Верно и для тестов.
Используйте говорящие именатестов
Тест — это спецификация с функцией самопроверки. Поназванию теста должно быть понятно, что конкретно он
проверяет.
Используйте говорящие именатестов
Bad:
it 'works' do # ... end
Good:
it 'sends email to the user' do # ... end
Используйте говорящие именатестов
Если фреймворк не позволяет в полной мере описать тестс помощью имени, напишите комментарий
# Sends email to the user def test_send_message # ... end
Один тест - одна проверкаПо выводу тестового фреймворка должно быть понятно,
какие конкретно действия выполняются не так, какожидалось.
Один тест - одна проверкаBad:
it 'creates message and sends it to the user via email' do # ... end
Good:
it 'creates message' do # ... end
it 'sends message to the user via email' do # ... end
Один тест - одна проверкаАналогично для фреймворков без возможности подробно
описать тест
# Creates message def test_send_message__message_creation # ... end
# Sends message to user def test_send_message__message_sending # ... end
Один тест - одна проверкаНекоторые фреймворки позволяют писать комментарии к
проверкам. В таком случае разделение не так важно.Пример для testify (go):
// Creates message func Test_sendMessage(t *testing.T) { // ... assert.Equal(t, expected, actual, "Should create message") // ... assert.Equal(t, expected, actual, "Should send message to user via email") // ... }
Используйте контекстыЕсли фреймворк позволяет задавать контекст
тестирования — пользуйтесь этой возможностью
Используйте контекстыBad:
it 'creates message when user is signed in' do sign_in(user) # ... end
Good:
context 'when user is signed in' do before { sign_in(user) }
it 'creates message' do # ... end end
Используйте контекстыЕсли фреймворк не поддерживает контексты, можно опять
обратиться к комментариям
# = When user is signed in ============================== # Creates message def test_send_message__signed_in__message_creation # ... end # = end When user is signed in
Правильные ожиданияПравильно подобраное ожидание - половина написанного
теста.
Правильные ожиданияПримеры хороших ожиданий:
Возвращаемое значение;Изменения состояния класса, видимого извне;Внешнее воздействие a.k.a. side effect;Обращение к сторонним объектам.
Правильные ожиданияПримеры плохих ожиданий:
Изменения внутренних переменных класса;Изменение состояния хранилища, используемого дляхранения состояния тестируемого объекта;Вызовы приватных методов.
Правильные ожиданияBad:
it "puts provided value to redis" do subject.set("the value") expect(REDIS.get("the key")).to eq("the value") end
Good:
it "saves provided value" do subject.set("the value") expect(subject.get).to eq("the value") end
Правильные ожиданияСтарайтесь максимально абстрагироваться от реализации
метода и сосредототочиться на результате
Mocks & stubsПалка о двух концах:
Помогают достичь нужного уровня изоляции;При злоупотреблении могут сделать тест бесполезным.
Mocks & stubsХорошие кандидаты:
Передаваемые на вход объекты;Сетевые службы;Функции с трудно прогнозируемым или трудновыводимым результатом, используемые в тестируемомметоде.
Mocks & stubsНужно очень осторожно подходить к стабу БД.
Если не уверены на 100%, не делайте этого
Работа с внешними связямиСитуация №1:
Функция foo объекта A (A.foo) проводит вычисления сосложной логикой, основываясь на результатах функцииbar объекта B (B.bar);B.bar в свою очередь тоже проводит вычисления сосложной логикой.
Необходимо протестировать метод A.foo
Работа с внешними связямиВариант решения №1:
Написать тест, учитывающий логику функции B.bar.
Нарушение DRY, повторное тестирование B.bar,тестирование логики, не относящейся к тестируемому
методу
Работа с внешними связямиВариант решения №2:
Создать условия для получения заранее известногорезультата B.bar, использовать этот результат для
тестирования A.foo.
Тест становится зависимым от логики B.bar.Изменение логики стороннего метода сломает тест.
Работа с внешними связямиВариант решения №3:
Сделать stub B.bar с известным результатом,использовать этот результат для тестирования A.foo.
Тест не зависит от логики B.bar
Работа с внешними связямиПроблема варианта №3: Изменение интерфейсафункции B.bar сломает код, но оставит тест ложно
положительным.
Решение: Это тот самый случай, когда чистый прогонфункции с проверкой на отсутствие исключений имеет
место быть.
Работа с внешними связямиСитуация №2:
Метод foo объекта A (A.foo) проводит вычисления сосложной логикой и затем вызывает метод bar объекта B(B.bar);B.bar в свою очередь тоже реализует сложную логику.
Необходимо протестировать метод A.foo
Работа с внешними связямиВариант решения №1:
Проверить side effect метода B.bar, учитывая его логику.
Нарушение DRY, повторное тестирование B.bar,тестирование логики, не относящейся к тестируемому
методу
Работа с внешними связямиВариант решения №2:
Проверить некоторую неизменную часть side effect'аметода B.bar, не зависящую от его логики. Пример: mailer
отправляет письмо с неизменным заголовком.
Тест не зависит от логики B.bar. Изменениеинтерфейса B.bar будет обнаружено сразу.
Работа с внешними связямиВариант решения №3:
Сделать stub B.bar, проверить факт его вызова послевыполнения A.foo.
Тест не зависит от логики B.bar
Имеет ту же проблему и аналогичное решение, что ивариант решения №3 предыдущей ситуации.
Гораздо лучше!
Поддерживаем порядок
Составьте договоренностиЕсли работаете в команде, составьте styleguide для тестов,
хотя бы на словах. Это существенно снизит порогвхождения в чужие тесты.
Test first!“Не доверяй тесту, который ты не видел
упавшим„(народная мудрость)
Почему test first?Если строить код на основе тестов, то у вас практическине возникнет проблем с тестируемостью;Хорошо организованные тесты позволяют продумать иописать структуру кода до реализации.
Почему test first?Главный аргумент
Не зная точной реализации, вы будете вынужденытестировать только интерфейс, что и требуется
Test firstПеред написанием тестов, постройте дерево с помощью
контекстов. Постарайтесь отобразить все возможныеварианты развития событий.
ИтогиНе гонитесь за test coverage;Не будьте параноиком, определите для себя, что нужнотестировать;Описывайте тесты так, чтобы другой человек мог понятьтестируемый функционал;Тестируйте интерфейс, а не реализацию;Делайте unit-тесты независимыми от функционаладругих классов/методов;Используйте тесты как спецификацию для вашего кода;
СпасибоВопросы?