Language agnostic 开发与TDD的接口

Language agnostic 开发与TDD的接口,language-agnostic,tdd,polymorphism,Language Agnostic,Tdd,Polymorphism,我是TDD的忠实粉丝,这些天我的大部分开发都使用它。不过,我经常遇到的一种情况是,我从未找到我认为“好”的答案,类似于下面的(人为的)例子 假设我有这样一个接口(用Java编写,但实际上,这适用于任何OO语言): 现在,假设我想创建这个接口的三个实现。让我们称它们为DijkstraPathFinder,DepthFirstPathFinder,和AStarPathFinder 问题是,我如何使用TDD开发这三个实现?它们的公共接口将是相同的,并且,据推测,我将为每一个编写相同的测试,因为getS

我是TDD的忠实粉丝,这些天我的大部分开发都使用它。不过,我经常遇到的一种情况是,我从未找到我认为“好”的答案,类似于下面的(人为的)例子

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

现在,假设我想创建这个接口的三个实现。让我们称它们为
DijkstraPathFinder
DepthFirstPathFinder
,和
AStarPathFinder

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

我的选择似乎是:

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

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

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


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

    您编写接口测试来练习接口,并为实际实现编写更详细的测试。讨论了单元测试应该为该接口形成一种“契约”规范这一事实。也许当Spec出来时,会有一种语言支持的方法来实现这一点

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


    编辑:这很有用,因此当您添加接口的另一个实现时,您已经有了验证类是否正确实现接口约定的测试。这可以适用于像ISortingStrategy这样特定的东西,也可以适用于像IDisposable这样范围广泛的东西。

    例如,针对接口编写测试并在每个实现中重用它们并没有什么错-

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

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

    我对选项1没有问题,请记住,重构是TDD的一部分,通常在重构阶段,您会转向一种设计模式,比如策略,所以我不会因为编写新的测试而感到不安

    如果您想测试每个探索者IMPL的具体实现细节,您可以考虑通过模拟的图形节点,它能够帮助确定实现的Dijkstra或DethTraseNess等。(也许这些模拟图形节点可以记录它们是如何被遍历的,或者以某种方式测量性能。)也许这是测试过度,但如果您知道您的系统出于某种原因需要这三种不同的策略,有测试来证明为什么可能是好的,否则为什么不选择一个实现并扔掉其他的呢

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

    当然可以

    首先,注释掉除一个测试之外的所有测试。当您通过测试时,重构或取消注释另一个测试


    联合特遣部队

    我仍然有问题。我采用的方法是测试驱动两个相同的类,然后重构到一个公共接口。当添加第三个类时,我剪切粘贴测试,并通过向类添加接口使每个测试依次编译并变为绿色。如果剪切粘贴出错,这很容易出错,但这不是我的问题。现在,我可以使用测试从一个类向接口添加功能,但该功能在其他类中没有支持它的测试。我得记得把测验抄过来。这似乎不对?将我的评论拉到一个新问题中:
    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