代码之家  ›  专栏  ›  技术社区  ›  MattK

开发为与TDD的接口

  •  4
  • MattK  · 技术社区  · 15 年前

    我是TDD的忠实粉丝,这些天我把它用于我的大部分开发工作。不过,有一种情况我经常遇到,而且从来没有找到我认为是“好”答案的答案,那就是下面的(人为的)例子。

    假设我有一个接口,像这样(写在爪哇,但实际上,这适用于任何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”,并确保它们通过 探路者 测验。这似乎不正确,因为我没有使用TDD开发第二个实现类。

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

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

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

    5 回复  |  直到 15 年前
        1
  •  3
  •   DavidN    15 年前

    编写接口测试来运行接口,并为实际实现编写更详细的测试。 Interface-based design 稍微讨论一下单元测试应该为该接口形成一种“契约”规范的事实。也许当规范出现时,会有一种语言支持的方法来实现这一点。

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

    编辑:这很有用,这样当您在路上添加接口的另一个实现时,您已经有了测试来验证类是否正确地实现了接口的约定。这可以适用于一些特定的东西,如IsortingStrategy,以及范围广泛的IDisposable。

        2
  •  2
  •   Steven A. Lowe    15 年前

    例如,在接口上编写测试并在每个实现中重用它们没有任何错误。-

    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原则。

        3
  •  2
  •   Scott Bale    15 年前

    我可以使用选项1,并记住重构是TDD的一部分,通常是在重构阶段,您将转向设计模式(如策略),因此我不会对编写新测试感到遗憾。

    如果您想要测试每个路径查找器impl的具体实现细节,您可以考虑传递模拟图节点,这些模拟图节点在某种程度上能够帮助断言实现的dijkstra或depthFirst等。(也许这些模拟图形节点可以记录它们是如何被遍历的,或者以某种方式度量性能。)也许这是测试过度杀戮,但是如果您知道您的系统出于某种原因需要这三种不同的策略,那么最好进行测试来演示为什么——否则,为什么不选择一个实现并抛出另一个实现呢?呃?

        4
  •  1
  •   tvanfosson    15 年前

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

        5
  •  1
  •   Jeffrey Fredrick    15 年前

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

    当然可以。

    首先对所有测试进行评论,只有一个测试。当您使一个测试通过时,请重构或取消对另一个测试的注释。

    JTF