2015-03-07 03 Сергей Александрович. 50 оттенков красного

Preview:

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-тесты независимыми от функционаладругих классов/методов;Используйте тесты как спецификацию для вашего кода;

СпасибоВопросы?

Recommended