Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Быстроевведениев 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,395 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

×