4

我是 TDD 的忠实拥护者,并且这些天在我的绝大多数开发中都使用它。但是,我经常遇到的一种情况,并且从未找到我认为是“好的”答案的情况,类似于以下(人为的)示例。

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

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

    int getShortestPathLength(GraphNode start, GraphNode goal);
}

现在,假设我想创建这个接口的三个实现。我们称它们DijkstraPathFinder为 、DepthFirstPathFinderAStarPathFinder

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

我的选择似乎是:

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

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

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

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

4

5 回答 5

3

您编写接口测试来运行接口,并为实际实现编写更详细的测试。基于接口的设计谈到了这样一个事实,即您的单元测试应该为该接口形成一种“合同”规范。也许当 Spec# 出来时,会有一种语言支持的方式来做到这一点。

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

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

于 2009-02-13T02:17:02.980 回答
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 原则。

于 2009-02-13T03:07:12.703 回答
2

使用选项 1 没有问题,请记住,重构是 TDD 的一部分,通常在重构阶段你会转向策略等设计模式,所以我不会因为这样做而感到难过/ o 编写新的测试。

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

于 2009-02-13T05:20:58.543 回答
1

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

于 2009-02-13T03:10:22.773 回答
1

这似乎不对,因为我没有使用 TDD 来开发后两个实现类。

你当然是。

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

联通

于 2009-02-13T07:14:09.753 回答