tdd-overview-java.ppt

Post on 11-Nov-2014

132 views 1 download

Tags:

Transcript of tdd-overview-java.ppt

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

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

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

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

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

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

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

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

Martin Fowler

Kent Beck

Alberto Savoia

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

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

проекте

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Хороший код

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

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

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

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

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

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

• Повторяем

Процесс TDD

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

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

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

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

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

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

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

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

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

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

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

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

The pain is here! This is too late…

Is TDD a Waste of Time (Microsoft Research)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Процесс TDD

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

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

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

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

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

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

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

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

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

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

• State-based and Interaction-based unit testing

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

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

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

State-based unit testing

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

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

Interaction-based testing

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

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

окружением

State-based vs Interaction-based

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

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

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

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

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

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

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

Stub object

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

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

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

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

public interface File{

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

}

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

public class StubFile implements File{

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

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

}

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

Fake object

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

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

Пример 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;}

Пример 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);

}}

Mock object

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

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

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

Пример 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;

}

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

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

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

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

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

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

Дао TDD

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

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

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

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

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

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

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

Принцип YAGNI

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

driven-development?

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

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

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

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

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

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

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

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

времени

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

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

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

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

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

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

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

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

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

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

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

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

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

Is TDD a Waste of Time (Microsoft Research)

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

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

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

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

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

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

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

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

Павел ЛазаревPavel_Lasarev@epam.com