Быстроевведениев TDDот А до ЯБибичев Андреймарт 2012
@bibigine                     Андрей Бибичев• E-mail:       bibigine@gmail.com• Twitter:      @bibigine• Profile:      htt...
Для кого и зачем?• Beginner o хорошая точка входа• Intermediate o поможет лучше всё структурировать в голове и   объяснять...
Введение:Data-Driven тесты                А
• Java                  • C++ o JUnit                 o Google Test o Mockito               o Google Mock o [FEST]        ...
ПримерОкругление цены $19.99 $99.99 $159 $299 $999 $1095 $9995
TDD:классический пример                 Б
http://www.objectmentor.com/resources/articles/xpepisode.htmhttp://wiki.agiledev.ru/doku.php?id=tdd:bowlinghttp://www.slid...
BowlingGameroll(pins: int)getScore(): intgetFrameScore(indx: int): intgetCurrentFrame(): int
Тесты1.   На 0 (ничего не сбито)2.   Простой фрейм3.   Очки по фреймам4.   Номер текущего фрейма5.   Spare6.   Strike7.   ...
Цикл разработки в TDD                   Написание неработающего теста                    для новой функциональности       ...
Традиционный цикл разработки                        Пишем сразу заметный                       кусок функциональности     ...
TDD:состояние vs поведение                  В-Э
Min  Max                    Кол-             День          Cycle Cycle                     во                           Ti...
Контрольный пример:                            Min  Max                    Кол-             День          Cycle Cycle     ...
Диаграмма классов
Conveyortick(Item[*]):Item[*]             workers         *       Worker                         Item                     ...
Conveyor ItemWorker
Conveyortick(Item[*]):Item[*]             workers         *       Worker                         Item                     ...
У вновь созданной деталивремя жизни должно равняться нулю           дано: новая деталь, когда: запрашиваем у нее время жиз...
import org.junit.Test;import static org.fest.assertions.Assertions.assertThat;public class ItemTest {    @Test    public v...
using System;using Microsoft.VisualStudio.TestTools.UnitTesting;using FluentAssertions;[TestClass]public class ItemTest{  ...
Это «вырожденный» тест на состояние
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…                    Should…{                        {    // Arrange               // GIVEN    …                   ...
Март 2006Журнал «Better Software»Статья «Introducing BDD»       Dan North
Альтернатива  ROY OSHEROVE
ИмяМетода_Условия_Результат[TestMethod]public void IsValidLogFileName_ValidFile_ReturnsTrue(){    // arrange    var analyz...
Критерий    хорошо оформленного теста:•   Содержательное название•   Короткое тело (max = 20-30 строк)•   По шаблону AAA и...
Оповещение о том, что     прошел такт конвейера      должно увеличиватьзначение времени жизни на один          дано: новая...
@Testpublic void shouldIncrementLifeTimeDuringTick() {    // given    final Item item = new Item();    // when    item.tic...
[TestMethod]public void ShouldIncrementLifeTimeDuringTick(){    // given    var item = new Item();    // when    item.Tick...
Это примитивный пример   теста на состояние
public class Item {    public int lifeTime() { return lifeTime; }    public void tick() { lifeTime++; }    private int lif...
Conveyortick(Item[*]):Item[*]             workers         *       Worker                         Item                     ...
Workerqueue
Рабочий ничего не обрабатывает,       если нет деталей              x  У вновь созданного рабочего входная очередь деталей...
public class WorkerTest {    @Test    public void shouldReturnNothingIfNothingToDo() {        // given       final Worker ...
[TestClass]public class WorkerTest{    [TestMethod]    public void ShouldReturnNothingIfNothingToDo()    {        // given...
Если во время обработки на кубике выпало значениебольшее количества деталей          в очереди,  то рабочий обрабатывает  ...
Conveyortick(Item[*]):Item[*]             workers         *       Worker                         Item                     ...
Dice   Workerqueue
Dependency Injection (DI)  через конструктор         Worker    Worker(Dice)    enqueue(Item[*])    tick():Item[*]
@Testpublic void shouldProcessNotGreaterThanItemsInQueue() {    // given    final Dice dice = createDiceStub(4);    final ...
[TestMethod]public void ShouldProcessNotGreaterThanItemsInQueue(){    // given    var dice = CreateDiceStub(4);    var wor...
4enqueue   tick      Dice                 roll():int                  DiceStub                 roll():int
import static org.mockito.Mockito.*;…private Dice createDiceStub(int rollValue) {    final Dice dice = mock(Dice.class);  ...
Хотя мы и воспользовались          mock-объектом,это всё равно, по большому счету,         тест на состояние
Если во время обработкина кубике выпало значение N, меньше количества деталей         в очереди,  то обрабатывается только...
@Testpublic void shouldProcessNotGreaterThanRolledValue() {    // given    final int rollValue = 3;    final Dice dice = c...
Еще аналогичные тесты:• Проверяем, что enqueue() добавляет в  очередь• Проверяем, что tick() удаляет из очереди  обработан...
Тупой, но важный тест:     Во время Worker.tick()        кубик бросается        ровно один раз!
Во время тренингов    доводилось встречать такой код:public List<Item> tick() {    final List<Item> output = new LinkedLis...
Всё работает,       но распределение…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) ...
@Testpublic void shouldRollDiceOnlyOnceDuringTick() {    // given    final Dice dice = createDiceStub(3);    final Worker ...
[TestMethod]public void ShouldRollDiceOnlyOnceDuringTick(){    // given    var diceMock = new Mock<Dice>();    diceMock.Se...
Это примитивный пример      теста на поведение:мы проверили как взаимодействует  наш объект с другим объектом
На состояние          На поведение               mock    Объект              Объект
Закрепим материал:• Проверим, что во время Worker.tick()  вызывается Item.tick() для всех деталей,  находящихся в очереди ...
@Testpublic void shouldCallTickForAllItemsInQueue() {    // given    final Dice dice = createDiceStub(1);    final Worker ...
[TestMethod]public void ShouldCallTickForAllItemsInQueue(){    // given    var dice = CreateDiceStub(1);    var worker = n...
Совет «по случаю»•   Избегайте имен переменных item1, item2 и т.п.•   Точно запутаетесь и опечатаетесь•   Лучше говорящие ...
Переходим к самому   интересному
Conveyortick(Item[*]):Item[*]             workers         *       Worker                         Item                     ...
Как тестировать?
Тесты на состояние:• Можно придумать несколько тестовых  сценариев (разрисовать на бумажке)  o в стиле «Контрольный пример...
Напомним спецификацию:1. То, что подается на вход конвейера, сражу же   оказывается в очереди первого рабочего, т.е. до   ...
Min  Max                    Кол-             День          Cycle Cycle                     во                           Ti...
Тесты на поведение           позволяютпротестировать эту спецификацию           один в один
1. То, что подается на вход конвейера, сражу же   оказывается в очереди первого рабочего, т.е. до   начала обработки им де...
worker1        worker21. enqueue( )2. tick                       conveyor             Conveyor   tick( )                  ...
@Testpublic void shouldEnqueueInputToFirstWorkerBeforeProcessing() {    // given    final List<Item> someInput = Arrays.as...
[TestMethod]public void ShouldEnqueueInputToFirstWorkerBeforeProcessing() {    // given    var someInput = new[] { new Ite...
Недостаток Moq:нет «встроенной» поддержкипоследовательностей вызовов                  Moq.Sequences.dll
public static class MoqSequence {    public interface ISequence { void Verify(); }    public static ISequence Create() { r...
1. То, что подается на вход конвейера, сражу же   оказывается в очереди первого рабочего, т.е. до   начала обработки им де...
@Testpublic void shouldReturnOutputOfLastWorker() {    // given    final List<Item> someOutput = Arrays.asList(           ...
Это можно было проверить,     создав реальных Worker-ов,«накормив» заранее второго нужными              Item-ами, но такие...
Mock-и очень удобны,  чтобы имитировать любое    необходимое состояние      стороннего объекта,   не связываясь с длиннойц...
Fake   Mock   Stub, Dummy
1. То, что подается на вход конвейера, сражу же   оказывается в очереди первого рабочего, т.е. до   начала обработки им де...
conveyor                firstWorker   secondWorker   thirdWorkertick()                 enqueue()                  tick()...
@Test public voidshouldEnqueueOutputOfPreviousWorkerToTheNextAfterProcessing() {    // given    final List<Item> outputOfF...
// when    conveyor.tick(someInput);    // then    InOrder secondWorkerOrder = inOrder(secondWorker);    secondWorkerOrder...
Тест на поведение – это проверка,что код соответствует задуманной диаграмме последовательности
Реализация
public List<Item> tick(final List<Item> input) {    if (workers.isEmpty()) { return input; }    final Worker firstWorker =...
public virtual IList<Item> Tick(IList<Item> input){    if (workers.Count == 0) return input;    var firstWorker = workers....
Полный код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• ...
WARNING:          В коде не выделены интерфейсы для Item, Dice и Workerтолько в целях «упрощения» примера.      Выделение ...
Итого
Плюсы тестов на поведение• Просто писать (когда привыкнешь)  o не нужно долго и мучительно приводить окружение в нужное   ...
1        Conveyortick(Item[*]):Item[*]                 workers             *    2       Worker                            ...
Минусы тестов на поведение• Чтобы ими овладеть, требуется ментальный  сдвиг• Проверяют, что код работает так, как вы  ожид...
Mock Hell• Чтобы его избежать,  очень важно соблюдать ISP  o Широко используемыми могут быть только    стабильные интерфейсы
Mockist vs. ClassicistMockist + Classicist
Legacy-код             Ю
Примерby Miško Hevery  http://bit.ly/92Ozrz http://goo.gl/V0aWx
public class InboxSyncer {   private static final InboxSyncer instance                              = new InboxSyncer(Conf...
public sync() {        long syncFrom = lastSync;        if (syncFrom == -1) {            syncFrom = new Date().getTime() -...
Если писать на «это»     тест в лоб
public class InboxSyncerTest extends TestCase {   PopServer server;   Certificate cert;   static InboxSyncer syncer;  publ...
public tearDown() {        server.stop();    }    public testSync() {        Defaults.FILTER_SPAM = true;        MessageQu...
Что не так с кодом?
•   Глобальный контекст (Singleton)•   Нарушение закона Деметры (Law of Demeter)•   Использование текущего системного врем...
public class InboxSyncer {   private final POPConnector pop;   private final Inbox inbox;   private final Filter filter;  ...
public sync(Date now) {        Date syncFrom = lastSync;        if (syncFrom == null) {            syncFrom = new Date(now...
Теперь тесты:
сlass InboxSyncerSpec extends TestCase {   Date longEgo = new Date(1);   Date past = new Date(2);   Date now = new Date(3)...
public @Test itShouldCloseConnectionEvenWhenExceptionThrown() {   Exception exception = new Exception();   Filter filter =...
Тестопригодная  архитектура                 Я
http://www.youtube.com/watch?v=WpkDN78P884
DCIData, context and interaction    http://architects.dzone.com/videos/dci-architecture-trygve        http://www.artima.co...
Смежныевыступления          bonus
Роль декомпозиции функционала наотдельные классы при следовании TDD
Архитектура в Agile:слабая связность кода
Тесты на поведение против    тестов на состояние
Спасибо за внимание!        Вопросы?              @bibigine        bibigine@gmail.com      http://tinyurl.com/bibigine  ht...
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Быстрое введение в TDD от А до Я
Upcoming SlideShare
Loading in …5
×

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

3,338 views

Published on

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

Published in: Technology

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

  1. 1. Быстроевведениев TDDот А до ЯБибичев Андреймарт 2012
  2. 2. @bibigine Андрей Бибичев• E-mail: bibigine@gmail.com• Twitter: @bibigine• Profile: http://tinyurl.com/bibigine• Slideshare: http://www.slideshare.net/bibigine
  3. 3. Для кого и зачем?• Beginner o хорошая точка входа• Intermediate o поможет лучше всё структурировать в голове и объяснять коллегам• Advanced o можно использовать для обучения и проверки других
  4. 4. Введение:Data-Driven тесты А
  5. 5. • Java • C++ o JUnit o Google Test o Mockito o Google Mock o [FEST] • PHP• .Net (C#) o phpUnit o MS Unit o Moq o [FluentAssertions]
  6. 6. ПримерОкругление цены $19.99 $99.99 $159 $299 $999 $1095 $9995
  7. 7. TDD:классический пример Б
  8. 8. http://www.objectmentor.com/resources/articles/xpepisode.htmhttp://wiki.agiledev.ru/doku.php?id=tdd:bowlinghttp://www.slideshare.net/stewshack/bowling-game-kata-c
  9. 9. BowlingGameroll(pins: int)getScore(): intgetFrameScore(indx: int): intgetCurrentFrame(): int
  10. 10. Тесты1. На 0 (ничего не сбито)2. Простой фрейм3. Очки по фреймам4. Номер текущего фрейма5. Spare6. Strike7. Spare в 10-ом фрейме8. Strike в 10-ом фрейме
  11. 11. Цикл разработки в TDD Написание неработающего теста для новой функциональности RED REFACTOR GREEN Рефакторим код, чтобы Пишем ровно столько кода,тесты продолжили проходить, чтобы тест прошел а код стал чистым
  12. 12. Традиционный цикл разработки Пишем сразу заметный кусок функциональности CODING DEBUGGING COMPILING Отлаживаем код, Добиваемся ловим и исправляем компилируемости поверхностные баги
  13. 13. TDD:состояние vs поведение В-Э
  14. 14. Min Max Кол- День Cycle Cycle во Time Time 1 0 - - N 2 0 - - 3 2 3 3 4 3 3 4 5 1 4 4Cycle Time
  15. 15. Контрольный пример: Min Max Кол- День Cycle Cycle во Time Time 1 0 - - 2 0 - - 5 3 0 - - 4 0 - - 5 1 5 5
  16. 16. Диаграмма классов
  17. 17. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick()
  18. 18. Conveyor ItemWorker
  19. 19. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick()
  20. 20. У вновь созданной деталивремя жизни должно равняться нулю дано: новая деталь, когда: запрашиваем у нее время жизни, тогда: получаем в результате 0
  21. 21. 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
  22. 22. 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
  23. 23. Это «вырожденный» тест на состояние
  24. 24. public class Item { public int lifeTime() { return 0; }}public class Item{ public int LifeTime { get; }}
  25. 25. Дано: арбуз + гиря 1 кг = гиря 6 кг арбуз = ?Решение: x+1=6 x=6–1=5Ответ: 5 кг
  26. 26. vs.Test… Should…{ { // Arrange // GIVEN … … // Action // WHEN … … // Assertion // THEN … …} }
  27. 27. Март 2006Журнал «Better Software»Статья «Introducing BDD» Dan North
  28. 28. Альтернатива ROY OSHEROVE
  29. 29. ИмяМетода_Условия_Результат[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
  30. 30. Критерий хорошо оформленного теста:• Содержательное название• Короткое тело (max = 20-30 строк)• По шаблону AAA или GIVEN-WHEN-THEN• Без циклов• Без ветвлений (if-ов и case-ов)• Должен легко читаться (literate programming)
  31. 31. Оповещение о том, что прошел такт конвейера должно увеличиватьзначение времени жизни на один дано: новая деталь,когда: оповещаем ее о такте конвейера, тогда: время жизни становится 1
  32. 32. @Testpublic void shouldIncrementLifeTimeDuringTick() { // given final Item item = new Item(); // when item.tick(); // then assertThat(item.lifeTime()).isEqualTo(1);}
  33. 33. [TestMethod]public void ShouldIncrementLifeTimeDuringTick(){ // given var item = new Item(); // when item.Tick(); // then item.LifeTime.Should().Be(1);}
  34. 34. Это примитивный пример теста на состояние
  35. 35. public class Item { public int lifeTime() { return lifeTime; } public void tick() { lifeTime++; } private int lifeTime;}public class Item{ public int LifeTime { get; private set; } public void Tick() { LifeTime++; }}
  36. 36. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick()
  37. 37. Workerqueue
  38. 38. Рабочий ничего не обрабатывает, если нет деталей x У вновь созданного рабочего входная очередь деталей пуста
  39. 39. public class WorkerTest { @Test public void shouldReturnNothingIfNothingToDo() { // given final Worker worker = new Worker(); // when final List<Item> output = worker.tick(); // then assertThat(output).isEmpty();}
  40. 40. [TestClass]public class WorkerTest{ [TestMethod] public void ShouldReturnNothingIfNothingToDo() { // given var worker = new Worker(); // when var output = worker.Tick(); // then output.Should().BeEmpty(); }
  41. 41. Если во время обработки на кубике выпало значениебольшее количества деталей в очереди, то рабочий обрабатывает все детали в очереди (и больше ничего)
  42. 42. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick() dice 1 Dice roll():int
  43. 43. Dice Workerqueue
  44. 44. Dependency Injection (DI) через конструктор Worker Worker(Dice) enqueue(Item[*]) tick():Item[*]
  45. 45. @Testpublic 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);}
  46. 46. [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);}
  47. 47. 4enqueue tick Dice roll():int DiceStub roll():int
  48. 48. 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;}
  49. 49. Хотя мы и воспользовались mock-объектом,это всё равно, по большому счету, тест на состояние
  50. 50. Если во время обработкина кубике выпало значение N, меньше количества деталей в очереди, то обрабатывается толькопервые N деталей из очереди
  51. 51. @Testpublic 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));}
  52. 52. Еще аналогичные тесты:• Проверяем, что enqueue() добавляет в очередь• Проверяем, что tick() удаляет из очереди обработанные детали
  53. 53. Тупой, но важный тест: Во время Worker.tick() кубик бросается ровно один раз!
  54. 54. Во время тренингов доводилось встречать такой код: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;}
  55. 55. Всё работает, но распределение…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.077P(6) = 5/324  0.0154
  56. 56. @Testpublic 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();}
  57. 57. [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());}
  58. 58. Это примитивный пример теста на поведение:мы проверили как взаимодействует наш объект с другим объектом
  59. 59. На состояние На поведение mock Объект Объект
  60. 60. Закрепим материал:• Проверим, что во время Worker.tick() вызывается Item.tick() для всех деталей, находящихся в очереди на начало tick()-а• Это можно проверить, не прибегая к mock-ам – через значение lifeTime(), но тогда мы тестируем два класса сразу, а не один в изоляции
  61. 61. @Testpublic 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();}
  62. 62. [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());}
  63. 63. Совет «по случаю»• Избегайте имен переменных item1, item2 и т.п.• Точно запутаетесь и опечатаетесь• Лучше говорящие имена• Или на худой конец: firstItem, secondItem и т.п.
  64. 64. Переходим к самому интересному
  65. 65. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick() dice 1 Dice roll():int
  66. 66. Как тестировать?
  67. 67. Тесты на состояние:• Можно придумать несколько тестовых сценариев (разрисовать на бумажке) o в стиле «Контрольный пример» из начала презентации• Проблемы: o Но как они помогут написать реализацию? o Какие тесты написать первыми, а какие потом? o Как быть уверенным, что протестированы все случаи и нюансы? (полнота покрытия) o Как эти тесты будут соотноситься со спецификацией? (test == executable specification)
  68. 68. Напомним спецификацию:1. То, что подается на вход конвейера, сражу же оказывается в очереди первого рабочего, т.е. до начала обработки им деталей2. То, что обработал последний рабочий, является выходом конвейера за соответствующий цикл3. Для всех остальных рабочих их результат работы попадает в очередь к следующему рабочему, но уже после того, как тот произвел обработку
  69. 69. Min Max Кол- День Cycle Cycle во Time Time 1 0 - - N 2 0 - - 3 2 3 3 4 3 3 4 5 1 4 4Cycle Time
  70. 70. Тесты на поведение позволяютпротестировать эту спецификацию один в один
  71. 71. 1. То, что подается на вход конвейера, сражу же оказывается в очереди первого рабочего, т.е. до начала обработки им деталей2. То, что обработал последний рабочий, является выходом конвейера за соответствующий цикл3. Для всех остальных рабочих их результат работы попадает в очередь к следующему рабочему, но уже после того, как тот произвел обработку
  72. 72. worker1 worker21. enqueue( )2. tick conveyor Conveyor tick( ) Conveyor(Worker[*]) tick(Item[*]):Item[*]
  73. 73. @Testpublic 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();}
  74. 74. [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();}
  75. 75. Недостаток Moq:нет «встроенной» поддержкипоследовательностей вызовов Moq.Sequences.dll
  76. 76. 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, "its 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>(); }}
  77. 77. 1. То, что подается на вход конвейера, сражу же оказывается в очереди первого рабочего, т.е. до начала обработки им деталей2. То, что обработал последний рабочий, является выходом конвейера за соответствующий цикл3. Для всех остальных рабочих их результат работы попадает в очередь к следующему рабочему, но уже после того, как тот произвел обработку
  78. 78. @Testpublic 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);}
  79. 79. Это можно было проверить, создав реальных Worker-ов,«накормив» заранее второго нужными Item-ами, но такие тесты уже больше похожи на интеграционные
  80. 80. Mock-и очень удобны, чтобы имитировать любое необходимое состояние стороннего объекта, не связываясь с длиннойцепочкой вызовов, необходимой для приведения реального объекта в это состояние
  81. 81. Fake Mock Stub, Dummy
  82. 82. 1. То, что подается на вход конвейера, сражу же оказывается в очереди первого рабочего, т.е. до начала обработки им деталей2. То, что обработал последний рабочий, является выходом конвейера за соответствующий цикл3. Для всех остальных рабочих их результат работы попадает в очередь к следующему рабочему, но уже после того, как тот произвел обработку
  83. 83. conveyor firstWorker secondWorker thirdWorkertick() enqueue() tick()  tick()  enqueue() tick()  enqueue() 
  84. 84. @Test public voidshouldEnqueueOutputOfPreviousWorkerToTheNextAfterProcessing() { // 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());
  85. 85. // 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);}
  86. 86. Тест на поведение – это проверка,что код соответствует задуманной диаграмме последовательности
  87. 87. Реализация
  88. 88. 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;}
  89. 89. 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;}
  90. 90. Полный кодhttp://tinyurl.com/tdd-demo-for-agiledays 17 Mb
  91. 91. • 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
  92. 92. WARNING: В коде не выделены интерфейсы для Item, Dice и Workerтолько в целях «упрощения» примера. Выделение интерфейсовпредпочтительнее перекрытия самих классов с функциональностью. «interface» Dice RandomDice roll():int
  93. 93. Итого
  94. 94. Плюсы тестов на поведение• Просто писать (когда привыкнешь) o не нужно долго и мучительно приводить окружение в нужное состояние• Являются истинными unit-тестами o проверяют функционал класса в изоляции от всех остальных• Хорошо отражают спецификации и дают уверенность в хорошем покрытии кода o executable specification• Принуждают к модульному дизайну o SRP, LSP, DIP, ISP• Позволяют разрабатывать функционал сверху-вниз от сценариев использования o а не снизу вверх от данных
  95. 95. 1 Conveyortick(Item[*]):Item[*] workers * 2 Worker 3 Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick() dice 1 4 Dice roll():int
  96. 96. Минусы тестов на поведение• Чтобы ими овладеть, требуется ментальный сдвиг• Проверяют, что код работает так, как вы ожидаете, но это не значит, что он работает правильно o этот недостаток легко снимается небольшим количеством интеграционных тестов• Не весь функционал можно так протестировать• Тесты хрупкие o изменение в реализации ломает тесты• Требуют выделения интерфейсов или виртуальности методов o Обычно не проблема. А если проблема, то используйте «быстрые mock-и» на C++ templates
  97. 97. Mock Hell• Чтобы его избежать, очень важно соблюдать ISP o Широко используемыми могут быть только стабильные интерфейсы
  98. 98. Mockist vs. ClassicistMockist + Classicist
  99. 99. Legacy-код Ю
  100. 100. Примерby Miško Hevery http://bit.ly/92Ozrz http://goo.gl/V0aWx
  101. 101. 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(); }
  102. 102. 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(); } }}
  103. 103. Если писать на «это» тест в лоб
  104. 104. 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(); }
  105. 105. 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()); }}
  106. 106. Что не так с кодом?
  107. 107. • Глобальный контекст (Singleton)• Нарушение закона Деметры (Law of Demeter)• Использование текущего системного времени• Самостоятельное создание объектов• Отсутствие Dependency Injection
  108. 108. 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; }
  109. 109. 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(); } }}
  110. 110. Теперь тесты:
  111. 111. с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)); }
  112. 112. 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() {} }
  113. 113. Тестопригодная архитектура Я
  114. 114. http://www.youtube.com/watch?v=WpkDN78P884
  115. 115. DCIData, context and interaction http://architects.dzone.com/videos/dci-architecture-trygve http://www.artima.com/articles/dci_vision.html
  116. 116. Смежныевыступления bonus
  117. 117. Роль декомпозиции функционала наотдельные классы при следовании TDD
  118. 118. Архитектура в Agile:слабая связность кода
  119. 119. Тесты на поведение против тестов на состояние
  120. 120. Спасибо за внимание! Вопросы? @bibigine bibigine@gmail.com http://tinyurl.com/bibigine http://www.slideshare.net/bibigine

×