开发到TDD的接口

时间:2009-02-13 02:07:27

标签: language-agnostic tdd polymorphism

我是TDD的忠实粉丝,现在用于我的绝大部分开发。然而,我经常碰到的一种情况,并且从未发现我认为是“好”的答案,就像下面的(人为的)例子。

假设我有一个接口,就像这样(用Java编写,但实际上,这适用于任何OO语言):

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

    int getShortestPathLength(GraphNode start, GraphNode goal);
}

现在,假设我想创建此接口的三个实现。我们称他们为DijkstraPathFinderDepthFirstPathFinderAStarPathFinder

问题是,如何使用TDD开发这三种实现?它们的公共接口将是相同的,并且,我可能会为每个接口编写相同的测试,因为getShortestPath()和getShortestPathLength()的结果应该在所有三个实现中保持一致。

我的选择似乎是:

  1. 在编写第一个实现时编写一组针对PathFinder的测试。然后将其他两个实现写成“盲”并确保它们通过PathFinder测试。这似乎不对,因为我没有使用TDD来开发第二个两个实现类。

  2. 以测试优先的方式开发每个实现类。这似乎不对,因为我会为每个班级编写相同的测试。

  3. 结合上述两种技术;现在我有一组针对接口的测试和针对每个实现类的一组测试,这很好,但测试都是一样的,这不太好。

  4. 这似乎是一种相当常见的情况,特别是在实施策略模式时,当然实现之间的差异可能不仅仅是时间复杂性。其他人如何处理这种情况?是否存在针对我不知道的接口进行测试优先开发的模式?

5 个答案:

答案 0 :(得分:3)

您编写接口测试来练习接口,并为实际实现编写更详细的测试。 Interface-based design谈到了一个事实,即你的单元测试应该为该接口形成一种“契约”规范。也许当Spec#出现时,将会有一个支持这种方式的语言。

在这个特殊情况下,这是一个严格的策略实现,接口测试就足够了。在其他情况下,如果接口是实现功能的子集,那么您将对接口和实现进行测试。例如,可以考虑实现3个接口的类。

编辑:这非常有用,因此当你在路上添加另一个接口实现时,你已经有了测试来验证该类是否正确实现了接口的契约。这可以像ISortingStrategy那样特定于IDisposable这样广泛的东西。

答案 1 :(得分: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

我认为这是尽力而为解决方案,这与敏捷/ TDD原则非常一致。

答案 2 :(得分:2)

我对选项1没有任何问题,请记住,重构是TDD的一部分,通常在重构阶段你会转向设计模式,比如策略,所以我不会感到难过没有编写新测试。

如果您想测试每个PathFinder impl的特定于实现的详细信息,您可以考虑传递模拟GraphNode,它们能够以某种方式帮助断言实现的Dijkstra-ness或DepthFirst-ness等。 (也许这些模拟GraphNode可以记录它们是如何遍历的,或者以某种方式衡量性能。)也许这是测试过度杀伤,但如果你知道你的系统由于某种原因需要这三种不同的策略,那么测试可能会很好为了说明原因 - 否则为什么不选择一个实现并抛弃其他实现?

答案 3 :(得分:1)

我不介意将测试代码重新用作具有类似功能的新测试的模板。根据所测试的特定类,您可能必须使用不同的模拟对象和期望对它们进行返工。至少你必须重构它们以使用新的实现。不过,我会遵循TDD方法,进行一次测试,为新类重新编写,然后编写代码以通过该测试。但是,这可能需要更多的纪律,因为你已经掌握了一个实现,并且无疑会受到你已经编写的代码的影响。

答案 4 :(得分:1)

  

这似乎不对,因为我   不使用TDD开发第二个   两个实现类。

当然可以。

首先评论所有测试但只有一个。当您进行测试时,要么重构要么取消注释另一个测试。

JTF