在这种简单的情况下,我如何保持我的单元测试彼此独立



我正在使用测试驱动开发方法用C#编写一个小游戏。我面临一个问题:

我有一个课程CheckersBoard:

public class CheckersBoard
{
    private const string InitialBoardPattern = "B-B-B-B-" +
                                               "-B-B-B-B" +
                                               "B-B-B-B-" +
                                               "--------" +
                                               "--------" +
                                               "-W-W-W-W" +
                                               "W-W-W-W-" +
                                               "-W-W-W-W";
    public SquareStatus[,] Squares { get; private set; }
    public CheckersBoard(IBoardGenerator boardGenerator)
    {
        Squares = boardGenerator.GenerateFromPattern(InitialBoardPattern, 8, 8);
    }
    //....
}

和一个类BoardGenerator:

public class BoardGenerator : IBoardGenerator
{
    public SquareStatus[,] GenerateFromPattern(string pattern, int width, int height)
    {
        //....
    }
}

BoardGenerator允许我以一种非常可读的方式初始化CheckersBoard。所以,我真的很想在单元测试中使用BoardGenerator来避免难看的数组初始化。

但这违反了我必须保持所有单元测试相互独立的规则。如果GenerateFromPattern的测试失败,将产生"级联"效应,所有使用GenerateFromPattern的测试也将失败。

这是我第一次使用单元测试,所以我有点困惑。我该如何避免这个问题?我的设计有什么问题吗?

显然这是一个没有明显答案的常见问题,我碰巧遇到了两个类似的问题:

  • 在单元测试中重用业务逻辑-建议不要,因为OP谈论两个不同的组件(类似于您的案例)
  • 使用测试过的函数来准备另一个单元测试-建议使用,给定使用范围为单个类(这样新的bug就不会破坏一些完全无关的组件,而是所有的bug都将保持在原始"崩溃范围"内)

在某种程度上,重用业务逻辑(其他经过测试的组件)有点类似于在单元测试中使用第三方库——我们假设它们可以工作,并且不需要生成关于它们的额外断言。在单元测试中使用List<T>,你会有什么犹豫吗?很可能不会。但是,注意,List<T>每天都会被许多其他开发人员测试,并且背后有一个庞大的软件公司。你能对BoardGenerator说同样的话吗?

另一个需要考虑的问题是,CheckersBoard测试代码对从未使用过BoardGenerator的人来说会像对你一样显而易见吗?您声称它是可读的,但考虑到创作,这是常见的。也许使用数组初始化并不像您想象的那么糟糕(不可读)。

您可以模拟BoardGenerator类,也可以伪造实现,例如

internal class MockBoardGenerator : IBoardGenerator
{
    public SquareStatus[,] GenerateFromPattern(string pattern, int width, int height)
    {
        // return fixed test data
    }
}

最新更新