2009-02-13 3 views
4

Я большой поклонник TDD и использую его для подавляющего большинства моего развития в эти дни. Одна ситуация, с которой я сталкиваюсь несколько чаще, и никогда не находила того, что, по моему мнению, было «хорошим» ответом, похоже на следующий (надуманный) пример.Разработка интерфейса с TDD

Предположим, у меня есть интерфейс, как это (написание в Java, но на самом деле, это относится к любому языку OO):

public interface PathFinder { 
    GraphNode[] getShortestPath(GraphNode start, GraphNode goal); 

    int getShortestPathLength(GraphNode start, GraphNode goal); 
} 

Теперь предположим, что я хочу, чтобы создать три реализации этого интерфейса. Назовем их DijkstraPathFinder, DepthFirstPathFinder и AStarPathFinder.

Вопрос в том, как мне разработать эти три реализации с использованием TDD? Их публичный интерфейс будет таким же, и, по-видимому, я бы написал те же тесты для каждого, так как результаты getShortestPath() и getShortestPathLength() должны быть согласованными между всеми тремя реализациями.

Мой выбор кажутся:

  1. Написать один набор тестов против PathFinder, как я код первой реализации. Затем напишите две другие версии «blind» и убедитесь, что они проходят тесты PathFinder. Это не кажется правильным, потому что я не использую TDD для разработки вторых двух классов реализации.

  2. Разработка каждого класса реализации в тестовом режиме. Это не кажется правильным, потому что я буду писать те же тесты для каждого класса.

  3. Объедините две техники выше; теперь у меня есть набор тестов против интерфейса и набор тестов для каждого класса реализации, что приятно, но тесты все те же, что не очень приятно.

Это похоже на довольно распространенную ситуацию, особенно при реализации шаблона стратегии, и, разумеется, различия между реализациями могут быть не просто сложной по времени. Как другие справляются с этой ситуацией? Есть ли образец для тестовой разработки против интерфейса, о котором я не знаю?

ответ

3

Вы пишете интерфейсные тесты для осуществления интерфейса, и вы пишете более подробные тесты для фактических реализаций. Interface-based design немного говорит о том, что ваши юнит-тесты должны быть своего рода «контрактной» спецификацией для этого интерфейса. Возможно, когда появится SpeC#, будет поддерживаться langugage.

В этом конкретном случае, являющемся реализацией строгой стратегии, достаточно проверить интерфейс. В других случаях, когда интерфейс является подмножеством функциональности реализации, у вас будут тесты как для интерфейса, так и для реализации. Подумайте о классе, который реализует, например, 3 интерфейса.

EDIT: Это полезно, так что, когда вы добавляете другую реализацию интерфейса по дороге, у вас уже есть тесты для проверки того, что класс правильно реализует контракт интерфейса. Это может работать для чего-то определенного как ISortingStrategy для чего-то такого же широкого, как IDisposable.

+0

У меня все еще есть проблемы. Я принял подход к тестированию двух одинаковых классов, а затем рефакторинг к общему интерфейсу. При добавлении третьего класса я вырезал-n-вставил тесты и сделал каждый компиляцию и по очереди стал зеленым, добавив интерфейс к классу. Это подвержено ошибкам, если мой cut-n-вставка идет не так, но это не моя проблема. Теперь я нахожусь в ситуации, когда я могу использовать тесты для добавления функциональности интерфейса из одного класса, но эта функциональность не будет иметь никаких тестов для поддержки в других классах. Я должен помнить, чтобы скопировать тесты. Это не кажется правильным? – tenpn

+0

вынул мой комментарий на новый вопрос: http://stackoverflow.com/questions/1340712/how-to-tdd-functionality-in-a-base-mixin-class-from-one-of-many-leaf- классы – tenpn

2

нет ничего плохого в написании тестов против интерфейса, и повторно использовать их для каждой реализации, например -

public class TestPathFinder : TestClass 
{ 
    public IPathFinder _pathFinder; 
    public IGraphNode _startNode; 
    public IGraphNode _goalNode; 

    public TestPathFinder() : this(null,null,null) { } 
    public TestPathFinder(IPathFinder ipf, 
     IGraphNode start, IGraphNode goal) : base() 
    { 
     _pathFinder = ipf; 
     _startNode = start; 
     _goalNode = goal; 
    } 
} 

TestPathFinder tpfDijkstra = new TestPathFinder(
    new DijkstraPathFinder(), n1, nN); 
tpfDijkstra.RunTests(); 

//etc. - factory optional 

Я считаю, что это решение минимум усилий, что очень много с принципами Agile/TDD.

1

Я не против повторного использования тестового кода в качестве шаблона для новых тестов, имеющих аналогичную функциональность. В зависимости от конкретного тестируемого класса вам может потребоваться переработать их с помощью разных макетных объектов и ожиданий. По крайней мере вам придется реорганизовать их для использования новой реализации. Однако я следовал бы методу TDD, взяв один тест, переработав его для нового класса, а затем написал только код для прохождения этого теста. Это может потребовать еще большей дисциплины, поскольку у вас уже есть одна реализация под вашим поясом и, несомненно, будет зависеть от кода, который вы уже написали.

2

У меня не возникло бы проблемы с вариантом 1, и имейте в виду, что рефакторинг является частью TDD, и обычно на этапе рефакторинга вы переходите к шаблону проектирования, такому как стратегия, поэтому я не буду плохо себя чувствовать делая это без написания новых тестов.

Если вы хотите протестировать специфические для реализации детали каждого объекта PathFinder, вы можете рассмотреть возможность передачи макетов GraphNodes, которые каким-то образом способствуют утверждению Dijkstra-ness или DepthFirst-ness и т. Д. Реализации. (Возможно, эти макеты GraphNodes могут записывать, как они пройдены, или каким-то образом оценивать производительность.) Возможно, это тестирование overkill, но опять же, если вы знаете, что по какой-то причине ваша система нуждается в этих трех разных стратегиях, было бы неплохо иметь тесты чтобы показать, почему - иначе почему бы просто не выбрать одну реализацию и выбросить других?

1

Это не кажется правильным, потому что я не использую TDD разработать второй два классов реализации.

Уверенный, что вы есть.

Начните с комментариев всех тестов, кроме одного. Когда вы делаете тестовый проход, рефакторинг или раскомментируйте другой тест.

Jtf