tdd-overview-java.ppt

64
Что такое разработка через тестирование Павел Лазарев, EPAM

Transcript of tdd-overview-java.ppt

Page 1: tdd-overview-java.ppt

Что такое разработка через тестирование

Павел Лазарев, EPAM

Page 2: tdd-overview-java.ppt

Что это вообще такое?

• Test-driven-development – это методология разработки ПО

• TDD – это НЕ тестирование, это методология для разработчиков, не для тестировщиков

• Объектами TDD являются архитектура ПО и код реализации

• TDD относится к итеративным методикам разработки

Page 3: tdd-overview-java.ppt

Кто это придумал?

Martin Fowler

Kent Beck

Alberto Savoia

Page 4: tdd-overview-java.ppt

Как люди приходят к юнит-тестированию

• Слушаем авторитетов• Изучаем опыт других проектов• Ищем пути выхода из тупиковой ситуации в

проекте

Page 5: tdd-overview-java.ppt

Признаки критической ситуации• Огромное количество сложного и запутанного

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

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

• Наличие кода, который имеет известные и тяжелые недостатки, но эти недостатки страшно исправлять, так как «все посыпется», все вынуждены использовать такой код «как есть» или изобретать обходные пути

Page 6: tdd-overview-java.ppt

Примеры проектов C/C++ которые используют юнит-тестирование

• MySQL• Wine• Google LLVM• Google Protocol Buffers• Google Chrome• Tcl• Sqlite• MSBuild• …

Page 7: tdd-overview-java.ppt

Примеры проектов Java которые используют юнит-тестирование

• Spring• Hibernate• FitNesse• CruiseControl, Hudson• Ant• Log4J• …

Page 8: tdd-overview-java.ppt

За что платит заказчик?

• Заказчик нам платит за реализованный функционал. Что там внутри, его, как правило, не волнует

• То есть, нам платят прежде всего за правильный код

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

Page 9: tdd-overview-java.ppt

Для чего нужны юнит-тесты

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

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

Page 10: tdd-overview-java.ppt

Правильный код

– Выполняет все что необходимо– Не делает ничего лишнего– Удовлетворяет минимальным требованиям к

эффективности– Соответствует нефункциональным

требованиям– Нужны доказательства правильности

Page 11: tdd-overview-java.ppt

Хороший код

• Простой• Понятный• Расширяемый• Эффективный• Соответствует стандартам кодирования• Документированный– Контракты – Варианты использования

Page 12: tdd-overview-java.ppt

Код должен быть И хорошим И правильным ОДНОВРЕМЕННО

• Сразу сделать код хорошим и правильным чаще всего не получается

• Сосредотачиваемся в каждый момент времени на одной задаче

• 1-й шаг TDD: Делаем код правильным– Для этого пишем тесты

• 2-й шаг TDD: Делаем код хорошим– Для этого проводим рефакторинг

• Повторяем

Page 13: tdd-overview-java.ppt

Процесс TDD

Page 14: tdd-overview-java.ppt

Элементарный цикл TDD• Пишем тест на небольшой кусок функционала

– Материализуем требования к функциональному коду– Формируем спецификацию функционального кода– Документируем требования– Фиксируем внешний интерфейс функционального кода

• Пишем функциональный код– Реализуем требования, зафиксированные в тесте– Используем простейшее решение из возможных– Не делаем предварительную оптимизацию– Проектируем код так чтобы покрывать ТОЛЬКО зафиксированные

в тестах требования, и ничего сверх этого• Выполняем рефакторинг

– Чистим (cleanup) функциональный и тестовый код– Удаляем все неприятные запахи кода– Переосмысливаем дизайн и упрощаем код– Удаляем ненужный код

Page 15: tdd-overview-java.ppt

Что важнее - тесты или код?

• Правильность кода стоит выше его совершенства. Тесты несколько важнее кода.

• Сначала это кажется непривычным, но если подумать, это правильно.

• Код сам по себе не нужен. Он пишется ради чего-то.

• Юнит-тесты фиксируют цели, фиксируют что должен делать код, и это несколько важнее чем сам код.

Page 16: tdd-overview-java.ppt

Зачем нужно TDD?

Page 17: tdd-overview-java.ppt

Цель – максимально раннее обнаружение и исправление проблем, уже на этапе разработки

The pain is here! This is too late…

Page 18: tdd-overview-java.ppt

Is TDD a Waste of Time (Microsoft Research)

Page 19: tdd-overview-java.ppt

Технический долг• Не имея возможности делать рефакторинг, мы

постоянно накапливаем технический долг. В какой-то момент он не дает нам развивать систему дальше.

• Энтропия замкнутой системы может только возрастать. Для уменьшения энтропии нам надо периодически что-то удалять из системы. Единственный корректный способ удаления – рефакторинг.

• Единственный реальный способ рефакторинга – покрытие системы тестами

Page 20: tdd-overview-java.ppt
Page 21: tdd-overview-java.ppt

Превращаем страх в скуку

• Страх:– Опыт говорит: Не трогай этот код! Непонятно

что он делает, случайно что-то заденешь и что-то где-то наверняка отвалится!

• Скука:– Опыт TDD говорит: Видишь что-то плохо

пахнущее – исправляй! Если что-то не то заденешь, тесты сразу скажут.

Page 22: tdd-overview-java.ppt

Код на будущее

• Когда проектируется самолет, крайне важно обеспечить необходимую прочность. Но не менее важно не делать конструкцию излишне прочной, иначе он просто не взлетит.

• Закладывать как можно больше "на будущее" нас заставляет в основном невозможность что-то изменить после периода разработки.

• TDD позволяет сделать рефакторинг на любом этапе жизни системы, и такого рода запасы становятся просто не нужны.

Page 23: tdd-overview-java.ppt

Куда делось проектирование?

• “Используем простейшую структуру из возможных”. – TDD не знает, как это сделать. Это вопрос

проектирования. Используем все что возможно: шаблоны, опыт, экспертов, …

• “Когда система усложняется проводим рефакторинг”– TDD не знает, как это сделать. Это вопрос

проектирования.

Page 24: tdd-overview-java.ppt

Различные виды тестирования• Юнит-тестирование – это только часть общего процесса,

проверка комплектующих. Но без качественных комплектующих невозможно создать качественное изделие.

• Для достижения конечной цели– Нужна проверка взаимодействия частей, то есть

интеграционные тесты. Лучше автоматические.– Нужна проверка конечной функциональности – ручное

и/или автоматизированное функциональное тестирование– Интегральные тесты значительно стабильнее и легче, когда

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

проблем на разных этапах тестирования, так и обеспечение регрессионной стабильности

Page 25: tdd-overview-java.ppt

Что дает TDD• Разработчикам– Возможность писать правильный и хороший код– Уверенность в коде– Возможность рефакторинга в любой момент– Проектную документацию

• Менеджерам и заказчикам– Ориентация на результат– Контроль над процессом– Ускорение разработки в целом– Регрессионная стабильность– Готовность к изменениям

Page 26: tdd-overview-java.ppt

Как это делается?

Page 27: tdd-overview-java.ppt

Процесс TDD

Page 28: tdd-overview-java.ppt

Величина шагов• Любое изменение системы – это ступенька на

пути вверх• Есть оптимальная высота ступеньки, она

зависит от наших возможностей• Гораздо легче подняться на 10 ступенек по

15см, чем на одну в 150см, хотя это и возможно.

• Забраться на ступеньку в 300см мы уже не сможем

• Слишком мелкие ступеньки также сильно утомляют

Page 29: tdd-overview-java.ppt

Что делать в случае взаимодействующих объектов?

• Мы сделаем юнит –тесты на A, а как насчет B, C, D, E, …?

Page 30: tdd-overview-java.ppt

Как тестировать код, тесно взаимодействующий с окружением?• Улучшение структуры системы – Dependency Injection– Паттерны OOP: MVC, Observer, …

• TDD-техники отработки взаимодействия с окружением– Stub-объекты– Fake-объекты– Mock-объекты

• State-based and Interaction-based unit testing

Page 31: tdd-overview-java.ppt

Уменьшение связности

• Когда мы стараемся написать тест, мы просто вынуждены уменьшать связность, иначе тест часто невозможно или очень сложно написать.

• Синглтоны, глобальные переменные, слишком большая ответственность классов, сложное создание объектов – все это усложняет тестирование и побуждает нас отказываться от таких решений

Page 32: tdd-overview-java.ppt

State-based unit testing

• State-based unit testing – это тестирование прозрачного ящика

• Общий алгоритм– Задаем начальное состояние объекта– Производим действия– Проверяем новое состояние объекта

Page 33: tdd-overview-java.ppt

Interaction-based testing

• Interaсtion-based unit testing – это тестирование черного ящика

• Общий алгоритм– Инстанцируем объект– Производим действия– Проверяем как объект взаимодействовал с

окружением

Page 34: tdd-overview-java.ppt

State-based vs Interaction-based

• Считается, что state-based несколько проще• Считается, что state-based несколько

стабильнее, так как взаимодействие с окружением может меняться без изменения базовой ответственности класса

• Некоторые считают, что Interaction-based тесты более соответствуют духу ООП, так как внутреннее состояние объекта остается скрытым.

Page 35: tdd-overview-java.ppt

Взаимодействие с окружением

• Stub object– Заглушка реализации

• Fake object– Поддельный объект

• Mock object– Поддельный интерфейс

Page 36: tdd-overview-java.ppt

Stub object

• Заглушка реализации• Реализует тривиальную функциональность• Нужен в основном для временных

«затычек», чтобы можно было собрать работающую систему

• Реализует функционал на время разработки

Page 37: tdd-overview-java.ppt

Пример: реализация интерфейса

public interface File{

byte[] Read(int size);int Write(byte[] data);

}

Page 38: tdd-overview-java.ppt

Пример stub-объекта

public class StubFile implements File{

@Overridepublic byte[] Read(int size){ return new byte[0];}

@Overridepublic int Write(byte[] data){ return 0;}

}

Page 39: tdd-overview-java.ppt

Пример stub-объекта

Page 40: tdd-overview-java.ppt

Fake object

• Реализует требуемую функциональность более простым и изолированным от окружения способом, чем в реальном объекте

• Нужен для изоляции теста от сложного окружения

Page 41: tdd-overview-java.ppt

Пример fake-объектаpublic class FakeFile implements File {

@Overridepublic byte[] Read(int size){

if (Data == null){return new byte[0];

}

int readSize = Math.min(size, Data.length);byte[] result = new byte[readSize];System.arraycopy(Data, 0, result, 0, readSize);return result;

}

@Overridepublic int Write(byte[] data){

Data = data;return Data.length;

}

private byte[] Data;}

Page 42: tdd-overview-java.ppt

Пример fake-объекта: тестimport static org.junit.Assert.*;import org.junit.Test;

import java.util.Arrays;

public class FileTest {@Testpublic void Fake() {

FakeFile fake = new FakeFile();byte[] data = new byte[100];Arrays.fill(data, (byte)42);assertEquals(100, fake.Write(data));byte[] readData = fake.Read(10);assertEquals(10, readData.length);

}}

Page 43: tdd-overview-java.ppt

Mock object

• С помощью специального фреймворка позволяет легко создавать временные реализации интерфейсов.

• Нужен для проверки взаимодействия с окружением

• Эмуляция интерфейсов описывается декларативно, например, «при вызове такого-то метода должны быть такие-то аргументы и возвращаемое значение будет таким-то»

Page 44: tdd-overview-java.ppt

Пример mock-объекта: клиентimport java.util.Arrays;

public class Client {public Client(File file) {

TheFile = file;Data = new byte[100];Arrays.fill(Data, (byte)12);

}

public int Write() {return TheFile.Write(Data);}

private File TheFile;private byte[] Data;

}

Page 45: tdd-overview-java.ppt

// Пример mock-объекта: тест

Page 46: tdd-overview-java.ppt

Когда нужны fake- и mock- и stub- объекты?

• Когда мы отрабатываем взаимодействие класса с окружением – удобно использовать mock-объекты

• Когда мы отрабатываем детали внутренней реализации – удобнее (проще) использовать fake-объекты

• Когда мы используем наш объект как вспомогательный при работе с другими – удобно использовать stub-объекты

Page 47: tdd-overview-java.ppt

В любой момент времени мы сосредотачиваемся на одной задаче

Дао TDD

• Сначала делаем правильно• Потом делаем хорошо• Повторяем

Page 48: tdd-overview-java.ppt

Обучение TDD – ГЛАВНОЕ ПРАВИЛО

Не пишите код в голове пока вы не написали тест

Page 49: tdd-overview-java.ppt

Обучение TDD – ГЛАВНОЕ ПРАВИЛО• Когда мы впервые используем TDD на реальной задаче,

мы уже перед написанием теста просчитываем, какой должен быть дизайн. Мы уже «знаем» какой код собираемся написать. Поэтому мы пишем тест, который подходит под код, который мы уже держим в голове.

• Когда мы так делаем, мы фактически не используем TDD, так как мы сначала пишем код (хотя этот код всего лишь у нас в голове)

• Требуется время, чтобы принять что сначала необходимо сконцентрироваться на тесте. Пишем тест для требуемого поведения, затем пишем минимальный код, который позволяет тесту проходить, затем позволяем проявиться дизайну через рефакторинг. Повторяем до готовности.

Page 50: tdd-overview-java.ppt

Принцип YAGNI

• You Ain't Gonna Need It• Это нам никогда не понадобится• Всегда когда хочется сказать "ну это же нам

все равно потом понадобится" лучше пересилить себя и заменить на фразу "это нам НИКОГДА не понадобится".

Page 51: tdd-overview-java.ppt

Непрерывный рефакторинг

• Процесс Test-driven development предполагает непрерывный рефакторинг

• В каждый отдельный момент времени мы сосредоточены на чем-то одном

• Когда мы пишем правильный код мы думаем только о корректности

• Когда мы делаем рефакторинг мы думаем только о совершенстве кода

• Рефакторинг о котором идет речь это не тяжелый долгий процесс, а такой микро-рефакторинг, который выполняется очень быстро

Page 52: tdd-overview-java.ppt

Когда остановиться?• Наша конечная цель – функционал для

пользователя• Принцип good enouth• С другой стороны, разработчики имеют право

делать хороший код и не накапливать технический долг

• Если мы не можем написать тест на новую функцию, она скорее всего не нужна

• Если система после рефакторинга не упрощается, это ненужный шаг

Page 53: tdd-overview-java.ppt

А может, лучше сначала написать несколько тестов (или все), а потом весь код?

Если мы так делаем, потом нам приходится одновременно реализовывать несколько требований. Это либо сложно (и не всегда получится), либо мы все равно будем реализовывать требования по-одному, просто у нас всегда будет куча нерабочих тестов. Кроме того, будет неясен порядок реализации. Если мы хотим зафиксировать какое-то требование чтобы не забыть, можно пометить этот тест как Disabled

Page 54: tdd-overview-java.ppt

Что делать если при реализации теста мы видим, что сначала надо сделать другой тест или функционал?

Тест можно пометить как Disabled.Это не позволит нам забыть о нем, но он не будет попадать в «красноту» и мешать текущему процессу.Так же можно поступить если мы неожиданно встретились с каким-то препятствием и не хотим терять темп. Помечаем тест как Disabled, переходим к следущим задачам. Потом возвращаемся к тому что отложили.

Page 55: tdd-overview-java.ppt

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

driven-development?

Page 56: tdd-overview-java.ppt

Почему разработчики не хотят писать тесты - 1

• Надо писать в два раза больше кода, это займет в два раза больше времени

• Тесты надо поддерживать, это непроизводительные расходы

• TDD это как написание тестов к коду, только наоборот, но так же бесполезно

• У нас слишком часто меняются требования• Этот код правильный и никогда не будет изменяться• TDD не уменьшает количество багов• На нас хотят навесить еще и тестирование!• Это все придумали менеджеры чтобы снять с себя

ответственность

Page 57: tdd-overview-java.ppt

Почему разработчики не хотят писать тесты - 2

• Тесты мешают думать• Тестирование не нужно• Тестировать слишком сложно• Тестирование это для тестировщиков• Юнит-тесты - это для идиотов• Какие тесты! у нас и так полный ахтунг!• Сейчас у нас нет на это времени• Написание тестов отнимает слишком много

времени

Page 58: tdd-overview-java.ppt

Почему разработчики не хотят писать тесты - 3

• У нас уже есть миллиарды строк старого кода• Раньше мы прекрасно обходились без юнит-

тестирования!• Этот код нельзя протестировать• Mой код слишком сложно тестировать• Мой код связан со средой исполнения, это невозможно

автоматически протестировать• Я не могу протестировать код, потому что я точно не

знаю, как он должен работать • Невозможно формализовать требования к коду пока он

не написан• Юнит-тесты не нужны, потому что мои задачи для них

слишком сложны

Page 59: tdd-overview-java.ppt

Почему разработчики не хотят писать тесты - 4

• Юнит-тесты не проверяют конечный функционал, за который платит пользователь

• Юнит тесты не эстетичны, их код всегда грязен• Если я напишу тесты, кто напишет тесты на тесты• Я еще не уверовал в их магическую силу экономить

мне деньги, время и нервы• Мне не дают писать тесты• Я пробовал писать юнит-тесты, но они мне ни разу

не помогли• C TDD как с зарядкой по утрам. Пробовал,

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

Page 60: tdd-overview-java.ppt

Is TDD a Waste of Time (Microsoft Research)

Page 61: tdd-overview-java.ppt

Сверхэффекты TDD

• Большую часть времени разработки код работает правильно!

• Debugging sucks, testing rocks• Превращение страха в скуку• Правильно заданный вопрос это уже половина

ответа• Взгляд на код с точки зрения его клиента• Оказывается, код не хрустальный! • Ощущение свободы

Page 62: tdd-overview-java.ppt

Сверхэффекты TDD

Page 63: tdd-overview-java.ppt

Что дает TDD• Разработчикам– Возможность писать хороший и правильный код– Уверенность в коде– Возможность рефакторинга в любой момент– Проектную документацию

• Менеджерам и заказчикам– Ориентация на результат– Контроль над процессом– Ускорение разработки в целом– Регрессионная стабильность– Готовность к изменениям

Page 64: tdd-overview-java.ppt

Спасибо за внимание

Павел Лазарев[email protected]