Быстрое введение в tdd от А до Я

134
Бибичев Андрей март 2012 Быстрое введение в TDD от А до Я

Upload: andrey-bibichev

Post on 29-Nov-2014

3.023 views

Category:

Technology


8 download

DESCRIPTION

Слайды с мастер-класса "Быстрое введение в TDD от А до Я" с конференции AgileDays'12 (март 2012, Москва)

TRANSCRIPT

Page 1: Быстрое введение в TDD от А до Я

Бибичев Андрей март 2012

Быстрое

введение

в TDD

от А до Я

Page 2: Быстрое введение в TDD от А до Я

@bibigine

Андрей Бибичев

• E-mail: [email protected]

• Twitter: @bibigine

• Profile: http://tinyurl.com/bibigine

• Slideshare: http://www.slideshare.net/bibigine

Page 3: Быстрое введение в TDD от А до Я

Для кого и зачем?

• Beginner o хорошая точка входа

• Intermediate o поможет лучше всё структурировать в голове и

объяснять коллегам

• Advanced o можно использовать для обучения и проверки

других

Page 4: Быстрое введение в TDD от А до Я

Введение: Data-Driven тесты

А

Page 5: Быстрое введение в TDD от А до Я

• Java o JUnit

oMockito

o [FEST]

• .Net (C#) oMS Unit

oMoq

o [FluentAssertions]

• C++ oGoogle Test

oGoogle Mock

• PHP o phpUnit

Page 6: Быстрое введение в TDD от А до Я

Пример

Округление цены

$19.99

$99.99

$159

$299

$999

$1095

$9995

Page 7: Быстрое введение в TDD от А до Я

TDD: классический пример

Б

Page 8: Быстрое введение в TDD от А до Я
Page 10: Быстрое введение в TDD от А до Я
Page 11: Быстрое введение в TDD от А до Я

BowlingGame

roll(pins: int) getScore(): int getFrameScore(indx: int): int getCurrentFrame(): int

Page 12: Быстрое введение в TDD от А до Я

Тесты

1. На 0 (ничего не сбито)

2. Простой фрейм

3. Очки по фреймам

4. Номер текущего фрейма

5. Spare

6. Strike

7. Spare в 10-ом фрейме

8. Strike в 10-ом фрейме

Page 13: Быстрое введение в TDD от А до Я

Цикл разработки в TDD

RED

GREEN REFACTOR

Написание неработающего теста для новой функциональности

Пишем ровно столько кода, чтобы тест прошел

Рефакторим код, чтобы тесты продолжили проходить,

а код стал чистым

Page 14: Быстрое введение в TDD от А до Я

Традиционный цикл разработки

CODING

COMPILING DEBUGGING

Пишем сразу заметный кусок функциональности

Добиваемся компилируемости

Отлаживаем код, ловим и исправляем поверхностные баги

Page 15: Быстрое введение в TDD от А до Я

TDD: состояние vs поведение

В-Э

Page 16: Быстрое введение в TDD от А до Я

N

Cycle Time

День Кол-

во

Min

Cycle

Time

Max

Cycle

Time

1 0 - -

2 0 - -

3 2 3 3

4 3 3 4

5 1 4 4

Page 17: Быстрое введение в TDD от А до Я

Контрольный пример:

5

День Кол-

во

Min

Cycle

Time

Max

Cycle

Time

1 0 - -

2 0 - -

3 0 - -

4 0 - -

5 1 5 5

Page 18: Быстрое введение в TDD от А до Я

Диаграмма классов

Page 19: Быстрое введение в TDD от А до Я

Conveyor

tick(Item[*]):Item[*]

Worker

enqueue(Item[*]) tick():Item[*]

* workers

Item

lifeTime():int tick()

* queue

Page 20: Быстрое введение в TDD от А до Я

Item

Worker

Conveyor

Page 21: Быстрое введение в TDD от А до Я

Conveyor

tick(Item[*]):Item[*]

Worker

enqueue(Item[*]) tick():Item[*]

* workers

Item

lifeTime():int tick()

* queue

Page 22: Быстрое введение в TDD от А до Я

У вновь созданной детали время жизни должно равняться нулю

дано: новая деталь, когда: запрашиваем у нее время жизни,

тогда: получаем в результате 0

Page 23: Быстрое введение в TDD от А до Я

import org.junit.Test; import static org.fest.assertions.Assertions.assertThat; public class ItemTest { @Test public void shouldHaveZeroLifeTimeAterCreation() { // given final Item item = new Item(); // when int lifeTime = item.lifeTime(); // then assertThat(lifeTime).isZero(); }

FEST

Page 24: Быстрое введение в TDD от А до Я

using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using FluentAssertions; [TestClass] public class ItemTest { [TestMethod] public void ShouldHaveZeroLifeTimeAterCreation() { // given var item = new Item(); // when var lifeTime = item.LifeTime; // then lifeTime.Should().Be(0); }

FluentAssertions

Page 25: Быстрое введение в TDD от А до Я

Это «вырожденный» тест на состояние

Page 26: Быстрое введение в TDD от А до Я

public class Item { public int lifeTime() { return 0; } }

public class Item { public int LifeTime { get; } }

Page 27: Быстрое введение в TDD от А до Я
Page 28: Быстрое введение в TDD от А до Я

Дано: арбуз + гиря 1 кг = гиря 6 кг арбуз = ? Решение: x + 1 = 6 x = 6 – 1 = 5 Ответ: 5 кг

Page 29: Быстрое введение в TDD от А до Я

vs.

Test…

{

// Arrange

// Action

// Assertion

}

Should…

{

// GIVEN

// WHEN

// THEN

}

Page 30: Быстрое введение в TDD от А до Я

Март 2006

Журнал «Better Software»

Статья «Introducing BDD»

Dan North

Page 31: Быстрое введение в TDD от А до Я

Альтернатива

ROY OSHEROVE

Page 32: Быстрое введение в TDD от А до Я

ИмяМетода_Условия_Результат

[TestMethod] public void IsValidLogFileName_ValidFile_ReturnsTrue() { // arrange var analyzer = new LogAnalyzer(); // act var result = analyzer.IsValidLogFileName("whatever.slf"); // assert Assert.IsTrue(result, "filename should be valid!"); }

LogAnalyzer

+ IsValidLogFileName(name : string) : bool

Page 33: Быстрое введение в TDD от А до Я

Критерий

хорошо оформленного теста:

• Содержательное название

• Короткое тело (max = 20-30 строк)

• По шаблону AAA или GIVEN-WHEN-THEN

• Без циклов

• Без ветвлений (if-ов и case-ов)

• Должен легко читаться

(literate programming)

Page 34: Быстрое введение в TDD от А до Я
Page 35: Быстрое введение в TDD от А до Я

Оповещение о том, что прошел такт конвейера

должно увеличивать значение времени жизни на один

дано: новая деталь, когда: оповещаем ее о такте конвейера,

тогда: время жизни становится 1

Page 36: Быстрое введение в TDD от А до Я

@Test public void shouldIncrementLifeTimeDuringTick() { // given final Item item = new Item(); // when item.tick(); // then assertThat(item.lifeTime()).isEqualTo(1); }

Page 37: Быстрое введение в TDD от А до Я

[TestMethod] public void ShouldIncrementLifeTimeDuringTick() { // given var item = new Item(); // when item.Tick(); // then item.LifeTime.Should().Be(1); }

Page 38: Быстрое введение в TDD от А до Я

Это примитивный пример теста на состояние

Page 39: Быстрое введение в TDD от А до Я

public class Item { public int LifeTime { get; private set; } public void Tick() { LifeTime++; } }

public class Item { public int lifeTime() { return lifeTime; } public void tick() { lifeTime++; } private int lifeTime; }

Page 40: Быстрое введение в TDD от А до Я

Conveyor

tick(Item[*]):Item[*]

Worker

enqueue(Item[*]) tick():Item[*]

* workers

Item

lifeTime():int tick()

* queue

Page 41: Быстрое введение в TDD от А до Я

Worker

queue

Page 42: Быстрое введение в TDD от А до Я

Рабочий ничего не обрабатывает, если нет деталей

x

У вновь созданного рабочего входная очередь деталей пуста

Page 43: Быстрое введение в TDD от А до Я

public class WorkerTest { @Test public void shouldReturnNothingIfNothingToDo() { // given final Worker worker = new Worker(); // when final List<Item> output = worker.tick(); // then assertThat(output).isEmpty(); }

Page 44: Быстрое введение в TDD от А до Я

[TestClass] public class WorkerTest { [TestMethod] public void ShouldReturnNothingIfNothingToDo() { // given var worker = new Worker(); // when var output = worker.Tick(); // then output.Should().BeEmpty(); }

Page 45: Быстрое введение в TDD от А до Я

Если во время обработки на кубике выпало значение

большее количества деталей в очереди,

то рабочий обрабатывает все детали в очереди

(и больше ничего)

Page 46: Быстрое введение в TDD от А до Я

Conveyor

tick(Item[*]):Item[*]

Worker

enqueue(Item[*]) tick():Item[*]

* workers

Item

lifeTime():int tick()

* queue

Dice

roll():int

dice 1

Page 47: Быстрое введение в TDD от А до Я

Worker

queue

Dice

Page 48: Быстрое введение в TDD от А до Я

Worker

Worker(Dice) enqueue(Item[*]) tick():Item[*]

Dependency Injection (DI) через конструктор

Page 49: Быстрое введение в TDD от А до Я

@Test public void shouldProcessNotGreaterThanItemsInQueue() { // given final Dice dice = createDiceStub(4); final Worker worker = new Worker(dice); final List<Item> items = Arrays.asList( new Item(), new Item()); worker.enqueue(items); // when final List<Item> output = worker.tick(); // then assertThat(output).isEqualTo(items); }

Page 50: Быстрое введение в TDD от А до Я

[TestMethod] public void ShouldProcessNotGreaterThanItemsInQueue() { // given var dice = CreateDiceStub(4); var worker = new Worker(dice); var items = new[] { new Item(), new Item() }; worker.Enqueue(items); // when var output = worker.Tick(); // then output.Should().Equal(items); }

Page 51: Быстрое введение в TDD от А до Я

4

enqueue tick Dice

roll():int

DiceStub

roll():int

Page 52: Быстрое введение в TDD от А до Я

import static org.mockito.Mockito.*; … private Dice createDiceStub(int rollValue) { final Dice dice = mock(Dice.class); when(dice.roll()).thenReturn(rollValue); return dice; }

using Moq; … private Dice CreateDiceStub(int rollValue) { var diceMock = new Mock<Dice>(); diceMock.Setup(d => d.Roll()).Returns(rollValue); return diceMock.Object; }

Page 54: Быстрое введение в TDD от А до Я

Хотя мы и воспользовались mock-объектом,

это всё равно, по большому счету, тест на состояние

Page 55: Быстрое введение в TDD от А до Я

Если во время обработки на кубике выпало значение N,

меньше количества деталей в очереди,

то обрабатывается только первые N деталей из очереди

Page 56: Быстрое введение в TDD от А до Я

@Test public void shouldProcessNotGreaterThanRolledValue() { // given final int rollValue = 3; final Dice dice = createDiceStub(rollValue); final Worker worker = new Worker(dice); final List<Item> items = Arrays.asList( new Item(), new Item(), new Item(), new Item()); worker.enqueue(items); // when final List<Item> output = worker.tick(); // then assertThat(output).isEqualTo( items.subList(0, rollValue)); }

Page 57: Быстрое введение в TDD от А до Я

Еще аналогичные тесты:

• Проверяем, что enqueue() добавляет в

очередь

• Проверяем, что tick() удаляет из очереди

обработанные детали

Page 58: Быстрое введение в TDD от А до Я

Тупой, но важный тест:

Во время Worker.tick() кубик бросается ровно один раз!

Page 59: Быстрое введение в TDD от А до Я

public List<Item> tick() { final List<Item> output = new LinkedList<Item>(); for (int i = 0; i < dice.roll(); i++) { if (queue.isEmpty()) break; output.add(queue.poll()); } return output; }

Во время тренингов

доводилось встречать такой код:

Page 60: Быстрое введение в TDD от А до Я

Всё работает,

но распределение…

P(1) = 1/6 = 0.1(6)

P(2) = 5/18 = 0.2(7)

P(3) = 5/18 = 0.2(7)

P(4) = 5/27 = 0.(185)

P(5) = 25/324 0.077

P(6) = 5/324 0.0154

Page 61: Быстрое введение в TDD от А до Я

@Test public void shouldRollDiceOnlyOnceDuringTick() { // given final Dice dice = createDiceStub(3); final Worker worker = new Worker(dice); final List<Item> items = Arrays.asList( new Item(), new Item(), new Item(), new Item()); worker.enqueue(items); // when worker.tick(); // then verify(dice, times(1)).roll(); }

Page 62: Быстрое введение в TDD от А до Я

[TestMethod] public void ShouldRollDiceOnlyOnceDuringTick() { // given var diceMock = new Mock<Dice>(); diceMock.Setup(d => d.Roll()).Returns(3); var dice = diceMock.Object; var worker = new Worker(dice); var items = new[] { new Item(), new Item(), new Item(), new Item() }; worker.Enqueue(items); // when worker.Tick(); // then diceMock.Verify(d => d.Roll(), Times.Once()); }

Page 63: Быстрое введение в TDD от А до Я

Это примитивный пример теста на поведение:

мы проверили как взаимодействует наш объект с другим объектом

Page 64: Быстрое введение в TDD от А до Я

Объект Объект

mock

На состояние На поведение

Page 65: Быстрое введение в TDD от А до Я

Закрепим материал:

• Проверим, что во время Worker.tick() вызывается Item.tick() для всех деталей,

находящихся в очереди на начало tick()-а

• Это можно проверить, не прибегая к mock-ам – через значение lifeTime(), но

тогда мы тестируем два класса сразу, а

не один в изоляции

Page 66: Быстрое введение в TDD от А до Я

@Test public void shouldCallTickForAllItemsInQueue() { // given final Dice dice = createDiceStub(1); final Worker worker = new Worker(dice); final Item firstItem = mock(Item.class); final Item secondItem = mock(Item.class); final List<Item> items = Arrays.asList( firstItem, secondItem); worker.enqueue(items); // when worker.tick(); // then verify(firstItem, times(1)).tick(); verify(secondItem, times(1)).tick(); }

Page 67: Быстрое введение в TDD от А до Я

[TestMethod] public void ShouldCallTickForAllItemsInQueue() { // given var dice = CreateDiceStub(1); var worker = new Worker(dice); var firstItemMock = new Mock<Item>(); var secondItemMock = new Mock<Item>(); worker.Enqueue(new[] { firstItemMock.Object, secondItemMock.Object }); // when worker.Tick(); // then firstItemMock.Verify(i => i.Tick(), Times.Once()); secondItemMock.Verify(i => i.Tick(), Times.Once()); }

Page 68: Быстрое введение в TDD от А до Я
Page 69: Быстрое введение в TDD от А до Я

Совет «по случаю»

• Избегайте имен переменных item1, item2 и т.п.

• Точно запутаетесь и опечатаетесь

• Лучше говорящие имена

• Или на худой конец: firstItem, secondItem и т.п.

Page 70: Быстрое введение в TDD от А до Я
Page 71: Быстрое введение в TDD от А до Я

Переходим к самому интересному

Page 72: Быстрое введение в TDD от А до Я

Conveyor

tick(Item[*]):Item[*]

Worker

enqueue(Item[*]) tick():Item[*]

* workers

Item

lifeTime():int tick()

* queue

Dice

roll():int

dice 1

Page 73: Быстрое введение в TDD от А до Я

Как тестировать?

Page 74: Быстрое введение в TDD от А до Я

Тесты на состояние:

• Можно придумать несколько тестовых

сценариев (разрисовать на бумажке) o в стиле «Контрольный пример» из начала презентации

• Проблемы:

o Но как они помогут написать реализацию?

o Какие тесты написать первыми, а какие потом?

o Как быть уверенным, что протестированы все случаи и

нюансы? (полнота покрытия)

o Как эти тесты будут соотноситься со спецификацией?

(test == executable specification)

Page 75: Быстрое введение в TDD от А до Я

Напомним спецификацию:

1. То, что подается на вход конвейера, сражу же

оказывается в очереди первого рабочего, т.е. до

начала обработки им деталей

2. То, что обработал последний рабочий, является

выходом конвейера за соответствующий цикл

3. Для всех остальных рабочих их результат работы

попадает в очередь к следующему рабочему,

но уже после того, как тот произвел обработку

Page 76: Быстрое введение в TDD от А до Я

N

Cycle Time

День Кол-

во

Min

Cycle

Time

Max

Cycle

Time

1 0 - -

2 0 - -

3 2 3 3

4 3 3 4

5 1 4 4

Page 77: Быстрое введение в TDD от А до Я

Тесты на поведение позволяют

протестировать эту спецификацию один в один

Page 78: Быстрое введение в TDD от А до Я

1. То, что подается на вход конвейера, сражу же

оказывается в очереди первого рабочего, т.е. до

начала обработки им деталей

2. То, что обработал последний рабочий, является

выходом конвейера за соответствующий цикл

3. Для всех остальных рабочих их результат работы

попадает в очередь к следующему рабочему,

но уже после того, как тот произвел обработку

Page 79: Быстрое введение в TDD от А до Я

conveyor

worker1 worker2

1. enqueue( ) 2. tick

tick( ) Conveyor

Conveyor(Worker[*]) tick(Item[*]):Item[*]

Page 80: Быстрое введение в TDD от А до Я

@Test public void shouldEnqueueInputToFirstWorkerBeforeProcessing() { // given final List<Item> someInput = Arrays.asList( new Item(), new Item(), new Item()); final Worker firstWorker = mock(Worker.class); final Worker secondWorker = mock(Worker.class); final List<Worker> workers = Arrays.asList( firstWorker, secondWorker); final Conveyor conveyor = new Conveyor(workers); // when conveyor.tick(someInput); // then final InOrder order = inOrder(firstWorker); order.verify(firstWorker, times(1)).enqueue(someInput); order.verify(firstWorker, times(1)).tick(); }

Page 81: Быстрое введение в TDD от А до Я

[TestMethod] public void ShouldEnqueueInputToFirstWorkerBeforeProcessing() { // given var someInput = new[] { new Item(), new Item };

var callSequence = MoqSequence.Create(); var firstWorkerMock = new Mock<Worker>(); firstWorkerMock.Setup(w => w.Enqueue(someInput)) .InSequence(callSequence); firstWorkerMock.Setup(w => w.Tick()) .InSequence(callSequence); var firstWorker = firstWorkerMock.Object;

var secondWorker = new Mock<Worker>().Object;

var conveyor = new Conveyor(new[]{firstWorker, secondWorker }); // when conveyor.Tick(someInput); // then callSequence.Verify(); }

Page 82: Быстрое введение в TDD от А до Я

Недостаток Moq: нет «встроенной» поддержки последовательностей вызовов

Moq.Sequences.dll

Page 83: Быстрое введение в TDD от А до Я

public static class MoqSequence { public interface ISequence { void Verify(); } public static ISequence Create() { return new Sequence(); } public static void InSequence(this ICallback mock, ISequence sequence) { var seq = (Sequence)sequence; var id = seq.GetNextCallId(); mock.Callback(() => seq.LogCall(id)); }

private class Sequence : ISequence { public int GetNextCallId() { return nextCallId++; } public void LogCall(int id) { calls.Add(id); } public void Verify() { calls.Count.Should().Be(nextCallId, "it's expected count of calls in sequence"); for (var i = 0; i < calls.Count; i++) { calls[i].Should().Be(i, "wrong call sequence in position {0} ", i); } } private int nextCallId; private readonly IList<int> calls = new List<int>(); } }

Page 84: Быстрое введение в TDD от А до Я

1. То, что подается на вход конвейера, сражу же

оказывается в очереди первого рабочего, т.е. до

начала обработки им деталей

2. То, что обработал последний рабочий, является

выходом конвейера за соответствующий цикл

3. Для всех остальных рабочих их результат работы

попадает в очередь к следующему рабочему,

но уже после того, как тот произвел обработку

Page 85: Быстрое введение в TDD от А до Я

@Test public void shouldReturnOutputOfLastWorker() { // given final List<Item> someOutput = Arrays.asList( new Item(), new Item()); final Worker firstWorker = mock(Worker.class); final Worker secondWorker = mock(Worker.class); when(secondWorker.tick()).thenReturn(someOutput); final List<Worker> workers = Arrays.asList( firstWorker, secondWorker); final Conveyor conveyor = new Conveyor(workers); final List<Item> someInput = Arrays.asList(new Item()); // when final List<Item> output = conveyor.tick(someInput); // then assertThat(output).isEqualTo(someOutput); }

Page 86: Быстрое введение в TDD от А до Я

Это можно было проверить, создав реальных Worker-ов,

«накормив» заранее второго нужными Item-ами,

но такие тесты уже больше похожи на интеграционные

Page 87: Быстрое введение в TDD от А до Я

Mock-и очень удобны, чтобы имитировать любое

необходимое состояние стороннего объекта,

не связываясь с длинной цепочкой вызовов, необходимой

для приведения реального объекта в это состояние

Page 88: Быстрое введение в TDD от А до Я
Page 89: Быстрое введение в TDD от А до Я

Fake Mock Stub, Dummy

Page 90: Быстрое введение в TDD от А до Я
Page 91: Быстрое введение в TDD от А до Я

1. То, что подается на вход конвейера, сражу же

оказывается в очереди первого рабочего, т.е. до

начала обработки им деталей

2. То, что обработал последний рабочий, является

выходом конвейера за соответствующий цикл

3. Для всех остальных рабочих их результат работы

попадает в очередь к следующему рабочему,

но уже после того, как тот произвел обработку

Page 92: Быстрое введение в TDD от А до Я

conveyor firstWorker secondWorker thirdWorker

tick() enqueue()

tick()

enqueue()

tick()

tick()

enqueue()

Page 93: Быстрое введение в TDD от А до Я

@Test public void shouldEnqueueOutputOfPreviousWorkerToTheNextAfterProcessing() { // given final List<Item> outputOfFirstWorker = Arrays.asList( new Item(), new Item()); final List<Item> outputOfSecondWorker = Arrays.asList( new Item(), new Item(), new Item()); final Worker firstWorker = mock(Worker.class); when(firstWorker.tick()).thenReturn(outputOfFirstWorker); final Worker secondWorker = mock(Worker.class); when(secondWorker.tick()).thenReturn(outputOfSecondWorker); final Worker thirdWorker = mock(Worker.class); final List<Worker> workers = Arrays.asList( firstWorker, secondWorker, thirdWorker); final Conveyor conveyor = new Conveyor(workers); final List<Item> someInput = Arrays.asList(new Item());

Page 94: Быстрое введение в TDD от А до Я

// when conveyor.tick(someInput); // then InOrder secondWorkerOrder = inOrder(secondWorker); secondWorkerOrder.verify(secondWorker).tick(); secondWorkerOrder.verify(secondWorker) .enqueue(outputOfFirstWorker); InOrder thirdWorkerOrder = inOrder(thirdWorker); thirdWorkerOrder.verify(thirdWorker).tick(); thirdWorkerOrder.verify(thirdWorker) .enqueue(outputOfSecondWorker); }

Page 95: Быстрое введение в TDD от А до Я

Тест на поведение – это проверка, что код соответствует задуманной

диаграмме последовательности

Page 96: Быстрое введение в TDD от А до Я

Реализация

Page 97: Быстрое введение в TDD от А до Я

public List<Item> tick(final List<Item> input) { if (workers.isEmpty()) { return input; } final Worker firstWorker = workers.get(0); firstWorker.enqueue(input); List<Item> output = firstWorker.tick(); for (int i = 1; i < workers.size(); i++) { final Worker worker = workers.get(i); final List<Item> tmp = worker.tick(); worker.enqueue(output); output = tmp; } return output; }

Page 98: Быстрое введение в TDD от А до Я

public virtual IList<Item> Tick(IList<Item> input) { if (workers.Count == 0) return input; var firstWorker = workers.First(); firstWorker.Enqueue(input); var lastOutput = firstWorker.Tick(); foreach (var worker in workers.Skip(1)) { var tmp = worker.Tick(); worker.Enqueue(lastOutput); lastOutput = tmp; } return lastOutput; }

Page 100: Быстрое введение в TDD от А до Я

• Java o junit + mockito + fest

o проект NetBeans 7.0

• .Net (C#) o ms unit + moq + fluentAssertions

o проект VS 2010

• C++ o google test + google mock

o проект VS 2010

• PHP o phpUnit

o проект PhpStorm 3.0

Page 101: Быстрое введение в TDD от А до Я

WARNING:

В коде не выделены интерфейсы для Item, Dice и Worker

только в целях «упрощения» примера.

Выделение интерфейсов предпочтительнее перекрытия самих

классов с функциональностью.

«interface»

Dice

roll():int

RandomDice

Page 102: Быстрое введение в TDD от А до Я

Итого

Page 103: Быстрое введение в TDD от А до Я

Плюсы тестов на поведение

• Просто писать (когда привыкнешь) o не нужно долго и мучительно приводить окружение в нужное

состояние

• Являются истинными unit-тестами o проверяют функционал класса в изоляции от всех остальных

• Хорошо отражают спецификации и дают уверенность в хорошем покрытии кода o executable specification

• Принуждают к модульному дизайну o SRP, LSP, DIP, ISP

• Позволяют разрабатывать функционал сверху-вниз от сценариев использования o а не снизу вверх от данных

Page 104: Быстрое введение в TDD от А до Я

Conveyor

tick(Item[*]):Item[*]

Worker

enqueue(Item[*]) tick():Item[*]

* workers

Item

lifeTime():int tick()

* queue

Dice

roll():int

dice 1

1

2 3

4

Page 105: Быстрое введение в TDD от А до Я

Минусы тестов на поведение

• Чтобы ими овладеть, требуется ментальный сдвиг

• Проверяют, что код работает так, как вы ожидаете, но это не значит, что он работает правильно o этот недостаток легко снимается небольшим количеством

интеграционных тестов

• Не весь функционал можно так протестировать

• Тесты хрупкие o изменение в реализации ломает тесты

• Требуют выделения интерфейсов или виртуальности методов o Обычно не проблема. А если проблема, то используйте «быстрые

mock-и» на C++ templates

Page 106: Быстрое введение в TDD от А до Я

Mock Hell

• Чтобы его избежать,

очень важно соблюдать ISP o Широко используемыми могут быть только

стабильные интерфейсы

Page 107: Быстрое введение в TDD от А до Я

Mockist vs. Classicist

Mockist + Classicist

Page 108: Быстрое введение в TDD от А до Я

Legacy-код

Ю

Page 109: Быстрое введение в TDD от А до Я
Page 110: Быстрое введение в TDD от А до Я
Page 111: Быстрое введение в TDD от А до Я

Пример

by Miško Hevery

http://bit.ly/92Ozrz

http://goo.gl/V0aWx

Page 112: Быстрое введение в TDD от А до Я

public class InboxSyncer { private static final InboxSyncer instance = new InboxSyncer(Config.get()); public static getInstance() { return instance; } private final Certificate cert; private final String username; private final String password; private long lastSync = -1; private InboxSyncer(Config config) { this.cert = DESReader.read(config.get(“cert.path”)); User user = config.getUser(); this.username = user.getUsername(); this.password = user.getPassword(); }

Page 113: Быстрое введение в TDD от А до Я

public sync() { long syncFrom = lastSync; if (syncFrom == -1) { syncFrom = new Date().getTime() - Defaults.INBOX_SYNC_AMOUNT; } Inbox inbox = Inbox.get(username); POPConnector pop = new POPConnector(cert, username, password); pop.connect(); try { Iterator<Messages> messages = pop.messagesIterator(); Message message; while ( messages.hasNext() && (message = messages.next()).getTime() > syncFrom ) { if (Defaults.FILTER_SPAM ? MessageFilter.spam(message) : MessageFilter.addressedToMe(message, username)) { this.lastSync = Math.max(this.lastSync, message.getTime()); if (!inbox.contains(message.getId()) { inbox.add(message); } } } } finally { pop.disconnect(); } } }

Page 114: Быстрое введение в TDD от А до Я

Если писать на «это» тест в лоб

Page 115: Быстрое введение в TDD от А до Я

public class InboxSyncerTest extends TestCase { PopServer server; Certificate cert; static InboxSyncer syncer; public setUp() { if (syncer == null) { Config config = Config.get(); User user = new User(); user.setUsername(“[email protected]”); user.setPassword(“myTestPassword”); config.setUser(user); cert = DESCert.generateCert(); File tempCert = File.creteTempFile(“temp”, “.cert”); FileOutputStream out = new FileOutputStream(tempCert); out.write(cert); out.close(); config.set(“cert.path”, tempCert.toString()); syncer = InboxSyncer.get(); } // Reset the state of Global Objects Inbox inbox = Inbox.get(“[email protected]”); Iterator<Message> messages = inbox.getMessages(); while(messages.hasNext()) { messages.remove(); } Reflection.setPrivateField(syncer, “lastSync”, -1); POPServer server = new POPServer(cert); server.start(); MessageQueue queue = server.getMessageQueueForUser(“[email protected]”); queue.clear(); }

Page 116: Быстрое введение в TDD от А до Я

public tearDown() { server.stop(); } public testSync() { Defaults.FILTER_SPAM = true; MessageQueue queue = server.getMessageQueueForUser(“[email protected]”); Date time = new Date(); Message message = new Message(“from”, “to”, “subject”, “message”, time); queue.addMessage(message); syncer.sync(); Inbox inbox = Inbox.get(“[email protected]”); assertTrue(inbox.contains(message.getId()); assertEquals(1, inbox.getMessages().size()); } }

Page 117: Быстрое введение в TDD от А до Я

Что не так с кодом?

Page 118: Быстрое введение в TDD от А до Я

• Глобальный контекст (Singleton)

• Нарушение закона Деметры (Law of Demeter)

• Использование текущего системного времени

• Самостоятельное создание объектов

• Отсутствие Dependency Injection

Page 119: Быстрое введение в TDD от А до Я

public class InboxSyncer { private final POPConnector pop; private final Inbox inbox; private final Filter filter; private final Date lastSync; private final long defaultPastSync; private InboxSyncer(POPConnector pop, Inbox inbox, Filter filter, Date lastSync, long defaultPastSync) { this.pop = pop; this.inbox = inbox; this.filter = filter; this.lastSync = lastSync; this.defaultPastSync = defaultPastSync; }

Page 120: Быстрое введение в TDD от А до Я

public sync(Date now) { Date syncFrom = lastSync; if (syncFrom == null) { syncFrom = new Date(now.getTime() - defaultPastSync); } pop.connect(); try { Iterator<Messages> messages = pop.messagesIterator(); Message message; while ( messages.hasNext() && (message = messages.next()).getTime() > syncFrom.getTime() ) { if (filter.apply(message)) { this.lastSync = new Date(Math.max( this.lastSync.getTime(), message.getTime())); if (!inbox.contains(message.getId()) { inbox.add(message); } } } } finally { pop.disconnect(); } } }

Page 121: Быстрое введение в TDD от А до Я

Теперь тесты:

Page 122: Быстрое введение в TDD от А до Я

сlass InboxSyncerSpec extends TestCase { Date longEgo = new Date(1); Date past = new Date(2); Date now = new Date(3); Date future = new Date(4); POPConnector pop = new MockPOPConnector(); Inbox inbox = new Inbox(); Filter filter = new NoopFilter(); Message longEgoMsg = new Message(“from”, “to”, “subject”, “msg”, longEgo); Message pastMsg = new Message(“me”, “you”, “hello”, “world”, past); long noDefaultSync = -1; public @Test itShouldSyncMessagesFromPopSinceLastSync() { pop.addMessage(longEgoMsg); pop.addMessage(pastMsg); new InboxSyncer(pop, inbox, filter, longEgo, noDefaultSync).sync(now); assertThat(inbox.getMessages(), is(equalTo(pastMsg))); assertThat(pop.isClosed(), is(true)); }

Page 123: Быстрое введение в TDD от А до Я

public @Test itShouldCloseConnectionEvenWhenExceptionThrown() { Exception exception = new Exception(); Filter filter = new ExceptionThrowingFilter(exception); InboxSyncer syncer = new InboxSyncer(pop, null, filter, null, noDefaultSync); try { syncer.sync(now); fail(“Exception expected!”); } catch (Exception e) { assertThat(e, is(exception)); assertThat(pop.isClosed(), is(true)); } } public @Test itShouldSyncMessagesOnlyWhenNotAlreadyInInbox() { pop.addMessage(pastMsg); inbox.add(pastMsg); new InboxSyncer(pop, inbox, filter, longEgo, noDefaultSync).sync(now); assertThat(inbox.getMessages(), is(equalTo(pastMsg))); } public @Test itShouldIgnoreMessagesOlderThenLastSync() {} public @Test itShouldIgnoreMessagesFailingFilter() {} public @Test itShouldDefaultToDefaultTimeWhenNeverSynced() {} }

Page 124: Быстрое введение в TDD от А до Я

Тестопригодная архитектура

Я

Page 125: Быстрое введение в TDD от А до Я

http://www.youtube.com/watch?v=WpkDN78P884

Page 126: Быстрое введение в TDD от А до Я
Page 127: Быстрое введение в TDD от А до Я
Page 128: Быстрое введение в TDD от А до Я
Page 130: Быстрое введение в TDD от А до Я

Смежные выступления

bonus

Page 131: Быстрое введение в TDD от А до Я

Роль декомпозиции функционала на отдельные классы при следовании TDD

Page 132: Быстрое введение в TDD от А до Я

Архитектура в Agile: слабая связность кода

Page 133: Быстрое введение в TDD от А до Я

Тесты на поведение против тестов на состояние

Page 134: Быстрое введение в TDD от А до Я

@bibigine

[email protected]

http://tinyurl.com/bibigine

http://www.slideshare.net/bibigine

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

Вопросы?