1 of 32

Test Driven Development

  • когда надо
  • когда не надо

2 of 32

Павел Калашников, SimbirSoft

  • 5 лет опыта коммерческой разработки
  • все 5 лет использую TDD
  • применял (или не применял) TDD к примерно 30 проектам

3 of 32

TL;DR

too long; didn't read

4 of 32

Интеграционные тесты

5 of 32

6 of 32

TDD-обещания

  • ускорение скорости разработки
  • показ тестов заказчику до реализации
  • высокое покрытие функционала тестами
  • меньше отладки
  • проработанная архитектура приложения

7 of 32

8 of 32

9 of 32

10 of 32

TDD-разочарование

  • только продуктовым компаниям
  • не в нашем стеке технологий
  • вообще не работает
  • только на backend
  • не в этой предметной области

11 of 32

TDD нужен НЕ всегда

Применять или не применять TDD зависит от:

  • человека, который ставит и принимает задачи (“заказчика”)
  • ценностей команды разработчиков
  • ответственности менеджмента

12 of 32

Критерии использования TDD

13 of 32

Описанная структура интерфейсов

Многие тестовые фреймворки поддерживают возможность создания схемы, которая будет актуальна для всех интерфейсов

it "returns valid json matching the candidate types schema" do� get candidates_path, :params => {}, :headers => headers�� expect(response).to match_response_schema("candidates", :strict => true)�end

14 of 32

Один подход к реализации интерфейсов

def show� candidate = Candidate.find(params[:id])�� render(� :json => candidate,� :serializer => CandidateSerializer,� :status => :ok� )�end

15 of 32

Один подход к реализации интерфейсов

def show� user = User.find(params[:id])�� render(� :json => user,� :serializer => UserSerializer,� :status => :ok� )�end

16 of 32

В задаче описана реализация

  • GET /bandings -> Lists the bandings for the current account
  • POST /bandings -> Creates a banding for the current account
  • GET /bandings/:id -> Gets a banding for the current account
  • PATCH /bandings/:id -> Updates a banding for the current account

17 of 32

Критерии

  • в проекте чётко описана структура интерфейсов (JSON-API, REST-API и т.д.)
  • в проекте однотипный подход к реализации интерфейсов
  • “заказчик” ставит задачу, описывая в ней интерфейс взаимодействия
  • задачи ставятся в стиле: одна подзадача - один тест
  • в стеке технологий есть высокоуровневный тестовый фреймворк (необязательное условие)
  • нет отдельной команды QA

18 of 32

TDD & QA

Методология предполагает, что тесты пишут программисты

19 of 32

Когда TDD не нужен

20 of 32

Задачи без должной проработки

  • Написание тестов
  • Реализация функционала
  • Рефакторинг
  • Переписывание тестов
  • Переделывание функционала
  • Снова рефакторинг

21 of 32

Простой пример:

форма регистрации

22 of 32

Сложные вычисления

Сложные вычисления

-> Сложно предсказать результат

-> Сложно написать тест до реализации

-> Много времени на написание теста

23 of 32

Простой пример:

BI

24 of 32

Безопасность

Обеспечение безопасности сегодня требует участия человека

-> Автоматическое тестирование не очень полезно

-> Тесты до реализации ещё менее полезно

25 of 32

TDD не нужен!

  • “заказчик” ставит задачи без должной проработки
  • реализуется функционал, связанный со сложными вычислениями
  • реализуется функционал, связанный с большими нагрузками
  • реализуется функционал, связанный с безопасностью

26 of 32

Copy Paste Driven Development + TDD

Условия: описанные интерфейсы + описанные принципы реализации интерфейсов

Порядок действий:

  • Копируем тесты
  • Переименовываем endpoints и названия сущностей (2 команды в VIM)
  • Прогоняем тесты
  • Копируем реализацию
  • Переименовываем названия сущностей (2 команды в VIM)
  • Прогоняем тесты
  • “Работаем напильником”

27 of 32

RSpec or Minitest + FactoryGirl + webdriver

Фреймворки для интеграционного тестирования с декларативно-императивным синтаксисоМ

  • RSpec or Minitest or Cucumber
  • FactoryGirl or another
  • Selenium or PhantomJS

28 of 32

RSpec

it "returns a candidate with the correct ID" do

candidate = create :candidate� get candidate_path candidate�� expect(response.body).to include_json(� "data" => {� "id" => candidate.id.to_s,� "type" => "candidates"� }� )

end

29 of 32

RSpec

it "returns a user with the correct ID" do

user = create :user� get user_path user�� expect(response.body).to include_json(� "data" => {� "id" => user.id.to_s,� "type" => "users"� }� )

end

30 of 32

RSpec

[ :user, :candidate ] do |model_name|

it "returns a #{model_name} with the correct ID" do

object = create model_name� get send "#{model_name}_path", object�� expect(response.body).to include_json(� "data" => {� "id" => object.id.to_s,� "type" => model_name.pluralize(:en)� }� )

end

end

31 of 32

RSpec + Capybara

scenario 'Create new open opportunity and add teachers to it' do

visit opportunities_path

click_on "New opportunity"

fill_in "Name", with: "New opportunity"

fill_in "Amount in $", with: 100

click_on 'Create Opportunity'

fill_in "opportunity_teacher[email]", with: "test@example.com"

click_on "Add teacher"

expect(page).to have_content("test@example.com")

end

32 of 32

@kalashnikovisme

http://pavelkalashnikov.tumblr.com