Бибичев Андрей март 2012
Быстрое
введение
в TDD
от А до Я
@bibigine
Андрей Бибичев
• E-mail: [email protected]
• 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: классический пример
Б
http://www.objectmentor.com/resources/articles/xpepisode.htm
http://wiki.agiledev.ru/doku.php?id=tdd:bowling
http://www.slideshare.net/stewshack/bowling-game-kata-c
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; }
Полный код
http://tinyurl.com/tdd-demo-for-agiledays
17 Mb
• 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(“[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(); }
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()); } }
Что не так с кодом?
• Глобальный контекст (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
DCI
Data, context and interaction
http://architects.dzone.com/videos/dci-architecture-trygve
http://www.artima.com/articles/dci_vision.html
Смежные выступления
bonus
Роль декомпозиции функционала на отдельные классы при следовании TDD
Архитектура в Agile: слабая связность кода
Тесты на поведение против тестов на состояние
@bibigine
http://tinyurl.com/bibigine
http://www.slideshare.net/bibigine
Спасибо за внимание!
Вопросы?
Top Related