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

Post on 29-Nov-2014

3.024 Views

Category:

Technology

8 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

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

Быстрое

введение

в TDD

от А до Я

@bibigine

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

• E-mail: bibigine@gmail.com

• Twitter: @bibigine

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

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

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

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

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

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

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

других

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

А

• Java o JUnit

oMockito

o [FEST]

• .Net (C#) oMS Unit

oMoq

o [FluentAssertions]

• C++ oGoogle Test

oGoogle Mock

• PHP o phpUnit

Пример

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

$19.99

$99.99

$159

$299

$999

$1095

$9995

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

Б

BowlingGame

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

Тесты

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

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

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

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

5. Spare

6. Strike

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

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

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

RED

GREEN REFACTOR

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

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

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

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

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

CODING

COMPILING DEBUGGING

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

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

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

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

В-Э

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

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

5

День Кол-

во

Min

Cycle

Time

Max

Cycle

Time

1 0 - -

2 0 - -

3 0 - -

4 0 - -

5 1 5 5

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

Conveyor

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

Worker

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

* workers

Item

lifeTime():int tick()

* queue

Item

Worker

Conveyor

Conveyor

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

Worker

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

* workers

Item

lifeTime():int tick()

* queue

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

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

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

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

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

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

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

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

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

vs.

Test…

{

// Arrange

// Action

// Assertion

}

Should…

{

// GIVEN

// WHEN

// THEN

}

Март 2006

Журнал «Better Software»

Статья «Introducing BDD»

Dan North

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

ROY OSHEROVE

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

[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

Критерий

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

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

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

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

• Без циклов

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

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

(literate programming)

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

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

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

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

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

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

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

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; }

Conveyor

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

Worker

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

* workers

Item

lifeTime():int tick()

* queue

Worker

queue

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

x

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

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

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

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

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

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

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

Conveyor

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

Worker

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

* workers

Item

lifeTime():int tick()

* queue

Dice

roll():int

dice 1

Worker

queue

Dice

Worker

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

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

@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); }

[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); }

4

enqueue tick Dice

roll():int

DiceStub

roll():int

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; }

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

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

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

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

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

@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)); }

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

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

очередь

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

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

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

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

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; }

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

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

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

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

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

@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(); }

[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()); }

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

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

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

mock

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

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

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

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

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

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

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

@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(); }

[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()); }

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

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

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

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

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

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

Conveyor

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

Worker

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

* workers

Item

lifeTime():int tick()

* queue

Dice

roll():int

dice 1

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

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

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

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

• Проблемы:

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

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

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

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

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

(test == executable specification)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

conveyor

worker1 worker2

1. enqueue( ) 2. tick

tick( ) Conveyor

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

@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(); }

[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(); }

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

Moq.Sequences.dll

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>(); } }

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

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

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

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

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

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

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

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

@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); }

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

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

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

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

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

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

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

Fake Mock Stub, Dummy

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

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

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

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

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

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

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

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

conveyor firstWorker secondWorker thirdWorker

tick() enqueue()

tick()

enqueue()

tick()

tick()

enqueue()

@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());

// 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); }

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

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

Реализация

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; }

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; }

• 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

WARNING:

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

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

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

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

«interface»

Dice

roll():int

RandomDice

Итого

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

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

состояние

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

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

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

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

Conveyor

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

Worker

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

* workers

Item

lifeTime():int tick()

* queue

Dice

roll():int

dice 1

1

2 3

4

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

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

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

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

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

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

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

mock-и» на C++ templates

Mock Hell

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

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

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

Mockist vs. Classicist

Mockist + Classicist

Legacy-код

Ю

Пример

by Miško Hevery

http://bit.ly/92Ozrz

http://goo.gl/V0aWx

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(); }

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(); } } }

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

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(“test@example.com”); 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(“test@example.com”); 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(“test@example.com”); queue.clear(); }

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

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

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

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

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

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

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

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; }

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(); } } }

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

с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)); }

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() {} }

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

Я

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

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

bonus

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

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

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

@bibigine

bibigine@gmail.com

http://tinyurl.com/bibigine

http://www.slideshare.net/bibigine

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

Вопросы?

top related