Your SlideShare is downloading. ×
0
Тесты на поведение     супротивтестов на состояние     Бибичев Андрей      декабрь 2011
@bibigine                     Андрей Бибичев• E-mail:       bibigine@gmail.com• Twitter:      @bibigine• Profile:      htt...
Для кого и зачем?• Beginner o хорошая точка входа• Intermediate o поможет лучше всё структурировать в голове и   объяснять...
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....
Полный код
ZIP-архивhttp://tinyurl.com/tdd-demo-for-xpdays                10 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
Спасибо за внимание!        Вопросы?              @bibigine        bibigine@gmail.com      http://tinyurl.com/bibigine  ht...
Mockist vs Classicist
Mockist vs Classicist
Mockist vs Classicist
Mockist vs Classicist
Mockist vs Classicist
Mockist vs Classicist
Mockist vs Classicist
Upcoming SlideShare
Loading in...5
×

Mockist vs Classicist

2,132

Published on

Presentation for the 1st XPDays Ukrain conference.

Published in: Technology

Transcript of "Mockist vs Classicist"

  1. 1. Тесты на поведение супротивтестов на состояние Бибичев Андрей декабрь 2011
  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. Min Max Кол- День Cycle Cycle во Time Time 1 0 - - N 2 0 - - 3 2 3 3 4 3 3 4 5 1 4 4Cycle Time
  5. 5. Контрольный пример: Min Max Кол- День Cycle Cycle во Time Time 1 0 - - 2 0 - - 5 3 0 - - 4 0 - - 5 1 5 5
  6. 6. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick()
  7. 7. Conveyor ItemWorker
  8. 8. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick()
  9. 9. У вновь созданной деталивремя жизни должно равняться нулю дано: новая деталь, когда: запрашиваем у нее время жизни, тогда: получаем в результате 0
  10. 10. 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
  11. 11. 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
  12. 12. Это «вырожденный» тест на состояние
  13. 13. public class Item { public int lifeTime() { return 0; }}public class Item{ public int LifeTime { get; }}
  14. 14. Дано: арбуз + гиря 1 кг = гиря 6 кг арбуз = ?Решение: x+1=6 x=6–1=5Ответ: 5 кг
  15. 15. vs.Test… Should…{ { // Arrange // GIVEN … … // Action // WHEN … … // Assertion // THEN … …} }
  16. 16. Март 2006Журнал «Better Software»Статья «Introducing BDD» Dan North
  17. 17. Альтернатива ROY OSHEROVE
  18. 18. ИмяМетода_Условия_Результат[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
  19. 19. Критерий хорошо оформленного теста:• Содержательное название• Короткое тело (max = 20-30 строк)• По шаблону AAA или GIVEN-WHEN-THEN• Без циклов• Без ветвлений (if-ов и case-ов)• Должен легко читаться (literate programming)
  20. 20. Оповещение о том, что прошел такт конвейера должно увеличиватьзначение времени жизни на один дано: новая деталь,когда: оповещаем ее о такте конвейера, тогда: время жизни становится 1
  21. 21. @Testpublic void shouldIncrementLifeTimeDuringTick() { // given final Item item = new Item(); // when item.tick(); // then assertThat(item.lifeTime()).isEqualTo(1);}
  22. 22. [TestMethod]public void ShouldIncrementLifeTimeDuringTick(){ // given var item = new Item(); // when item.Tick(); // then item.LifeTime.Should().Be(1);}
  23. 23. Это примитивный пример теста на состояние
  24. 24. 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++; }}
  25. 25. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick()
  26. 26. Workerqueue
  27. 27. Рабочий ничего не обрабатывает, если нет деталей x У вновь созданного рабочего входная очередь деталей пуста
  28. 28. public class WorkerTest { @Test public void shouldReturnNothingIfNothingToDo() { // given final Worker worker = new Worker(); // when final List<Item> output = worker.tick(); // then assertThat(output).isEmpty();}
  29. 29. [TestClass]public class WorkerTest{ [TestMethod] public void ShouldReturnNothingIfNothingToDo() { // given var worker = new Worker(); // when var output = worker.Tick(); // then output.Should().BeEmpty(); }
  30. 30. Если во время обработки на кубике выпало значениебольшее количества деталей в очереди, то рабочий обрабатывает все детали в очереди (и больше ничего)
  31. 31. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick() dice 1 Dice roll():int
  32. 32. Dice Workerqueue
  33. 33. Dependency Injection (DI) через конструктор Worker Worker(Dice) enqueue(Item[*]) tick():Item[*]
  34. 34. @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);}
  35. 35. [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);}
  36. 36. 4enqueue tick Dice roll():int DiceStub roll():int
  37. 37. 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;}
  38. 38. Хотя мы и воспользовались mock-объектом,это всё равно, по большому счету, тест на состояние
  39. 39. Если во время обработкина кубике выпало значение N, меньше количества деталей в очереди, то обрабатывается толькопервые N деталей из очереди
  40. 40. @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));}
  41. 41. Еще аналогичные тесты:• Проверяем, что enqueue() добавляет в очередь• Проверяем, что tick() удаляет из очереди обработанные детали
  42. 42. Тупой, но важный тест: Во время Worker.tick() кубик бросается ровно один раз!
  43. 43. Во время тренингов доводилось встречать такой код: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;}
  44. 44. Всё работает, но распределение…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
  45. 45. @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();}
  46. 46. [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());}
  47. 47. Это примитивный пример теста на поведение:мы проверили как взаимодействует наш объект с другим объектом
  48. 48. На состояние На поведение mock Объект Объект
  49. 49. Закрепим материал:• Проверим, что во время Worker.tick() вызывается Item.tick() для всех деталей, находящихся в очереди на начало tick()-а• Это можно проверить, не прибегая к mock-ам – через значение lifeTime(), но тогда мы тестируем два класса сразу, а не один в изоляции
  50. 50. @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();}
  51. 51. [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());}
  52. 52. Совет «по случаю»• Избегайте имен переменных item1, item2 и т.п.• Точно запутаетесь и опечатаетесь• Лучше говорящие имена• Или на худой конец: firstItem, secondItem и т.п.
  53. 53. Переходим к самому интересному
  54. 54. Conveyortick(Item[*]):Item[*] workers * Worker Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick() dice 1 Dice roll():int
  55. 55. Как тестировать?
  56. 56. Тесты на состояние:• Можно придумать несколько тестовых сценариев (разрисовать на бумажке) o в стиле «Контрольный пример» из начала презентации• Проблемы: o Но как они помогут написать реализацию? o Какие тесты написать первыми, а какие потом? o Как быть уверенным, что протестированы все случаи и нюансы? (полнота покрытия) o Как эти тесты будут соотноситься со спецификацией? (test == executable specification)
  57. 57. Напомним спецификацию:1. То, что подается на вход конвейера, сражу же оказывается в очереди первого рабочего, т.е. до начала обработки им деталей2. То, что обработал последний рабочий, является выходом конвейера за соответствующий цикл3. Для всех остальных рабочих их результат работы попадает в очередь к следующему рабочему, но уже после того, как тот произвел обработку
  58. 58. Min Max Кол- День Cycle Cycle во Time Time 1 0 - - N 2 0 - - 3 2 3 3 4 3 3 4 5 1 4 4Cycle Time
  59. 59. Тесты на поведение позволяютпротестировать эту спецификацию один в один
  60. 60. 1. То, что подается на вход конвейера, сражу же оказывается в очереди первого рабочего, т.е. до начала обработки им деталей2. То, что обработал последний рабочий, является выходом конвейера за соответствующий цикл3. Для всех остальных рабочих их результат работы попадает в очередь к следующему рабочему, но уже после того, как тот произвел обработку
  61. 61. worker1 worker21. enqueue( )2. tick conveyor Conveyor tick( ) Conveyor(Worker[*]) tick(Item[*]):Item[*]
  62. 62. @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();}
  63. 63. [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();}
  64. 64. Недостаток Moq:нет «встроенной» поддержкипоследовательностей вызовов Moq.Sequences.dll
  65. 65. 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>(); }}
  66. 66. 1. То, что подается на вход конвейера, сражу же оказывается в очереди первого рабочего, т.е. до начала обработки им деталей2. То, что обработал последний рабочий, является выходом конвейера за соответствующий цикл3. Для всех остальных рабочих их результат работы попадает в очередь к следующему рабочему, но уже после того, как тот произвел обработку
  67. 67. @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);}
  68. 68. Это можно было проверить, создав реальных Worker-ов,«накормив» заранее второго нужными Item-ами, но такие тесты уже больше похожи на интеграционные
  69. 69. Mock-и очень удобны, чтобы имитировать любое необходимое состояние стороннего объекта, не связываясь с длиннойцепочкой вызовов, необходимой для приведения реального объекта в это состояние
  70. 70. Fake Mock Stub, Dummy
  71. 71. 1. То, что подается на вход конвейера, сражу же оказывается в очереди первого рабочего, т.е. до начала обработки им деталей2. То, что обработал последний рабочий, является выходом конвейера за соответствующий цикл3. Для всех остальных рабочих их результат работы попадает в очередь к следующему рабочему, но уже после того, как тот произвел обработку
  72. 72. conveyor firstWorker secondWorker thirdWorkertick() enqueue() tick()  tick()  enqueue() tick()  enqueue() 
  73. 73. @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());
  74. 74. // 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);}
  75. 75. Тест на поведение – это проверка,что код соответствует задуманной диаграмме последовательности
  76. 76. Реализация
  77. 77. 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;}
  78. 78. 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;}
  79. 79. Полный код
  80. 80. ZIP-архивhttp://tinyurl.com/tdd-demo-for-xpdays 10 Mb
  81. 81. • 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
  82. 82. WARNING: В коде не выделены интерфейсы для Item, Dice и Workerтолько в целях «упрощения» примера. Выделение интерфейсовпредпочтительнее перекрытия самих классов с функциональностью. «interface» Dice RandomDice roll():int
  83. 83. Итого
  84. 84. Плюсы тестов на поведение• Просто писать (когда привыкнешь) o не нужно долго и мучительно приводить окружение в нужное состояние• Являются истинными unit-тестами o проверяют функционал класса в изоляции от всех остальных• Хорошо отражают спецификации и дают уверенность в хорошем покрытии кода o executable specification• Принуждают к модульному дизайну o SRP, LSP, DIP, ISP• Позволяют разрабатывать функционал сверху-вниз от сценариев использования o а не снизу вверх от данных
  85. 85. 1 Conveyortick(Item[*]):Item[*] workers * 2 Worker 3 Item queue enqueue(Item[*]) * lifeTime():int tick():Item[*] tick() dice 1 4 Dice roll():int
  86. 86. Минусы тестов на поведение• Чтобы ими овладеть, требуется ментальный сдвиг• Проверяют, что код работает так, как вы ожидаете, но это не значит, что он работает правильно o этот недостаток легко снимается небольшим количеством интеграционных тестов• Не весь функционал можно так протестировать• Тесты хрупкие o изменение в реализации ломает тесты• Требуют выделения интерфейсов или виртуальности методов o Обычно не проблема. А если проблема, то используйте «быстрые mock-и» на C++ templates
  87. 87. Mock Hell• Чтобы его избежать, очень важно соблюдать ISP o Широко используемыми могут быть только стабильные интерфейсы
  88. 88. Mockist vs. ClassicistMockist + Classicist
  89. 89. Спасибо за внимание! Вопросы? @bibigine bibigine@gmail.com http://tinyurl.com/bibigine http://www.slideshare.net/bibigine
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×