我应该在哪个级别测试我的逻辑

On which level should I test my logic

我有一个 TextGenerator class,它使用 MarkovChain class 生成随机文本。从 MarkovChain 创建下一个单词的逻辑存在于 ChainNavigator class:

public class TextGenerator
{
    public List<string> MakeText(int requiredTextLength, string sourceText)
    {
        var chain = new MarkovChain(sourceText);
        var chainNavigator = new ChainNavigator(chain);            
        var nextWord = chainNavigator.GetNextWord(/*params here*/);
    }
}

internal class ChainNavigator
{
    internal string GetNextWord(/*params here*/) { }
}

MarkovChain 是从源文本生成的。源文本的最后一个单词不会有任何 'next state',因为它后面没有任何单词。生成长文本时,ChainNavigator 会写到最后一个字,不知道要写什么 return。

我想测试 TextGenerator 开始新句子,当它到达最后一个单词时,这个测试可以写在 2 个地方。

一方面,在 TextGenerator 中测试它是有意义的,因为它是我的外部接口:

[TestClass]
public class TextGeneratorTest
{
    [TestMethod]
    public void ShouldAppendADot_WhenEndOfChainReached()
    {
        var generator = new TextGenerator();
        var sourceText = "free men can remain free or sell their freedom";
        var firstWord = "their";
        var requiredTextLength = 2;

        var text = generator.MakeText(requiredTextLength, sourceText, firstWord);

        Assert.AreEqual("freedom.", text[1]);
    }
}

另一方面,实际测试的逻辑属于ChainNavigator class,在这里测试它是有意义的:

[TestMethod]
public void AppendADot_WhenEndOfChainReached()
{
    var sourceText = "free men can remain free or sell their freedom";
    var chain = new Chain(sourceText);
    var navigator = new ChainNavigator(chain);

    var nextWord = navigator.GetNextWord("their", 1);

    Assert.AreEqual("freedom.", nextWord);
}

在两个地方都这样做看起来像是重复。哪里做比较好?

你的困惑其实很常见。其来源是“单元测试”中的“单元”一词。很多人会告诉您,“单元”类似于单个 class 甚至只是一个方法。几十年来人们一直这样说,但大部分都是错误的。这种误解源于这样一个事实,即您很少在书籍、文章和博客中看到正在测试的真实应用程序。由于很难用完整的应用程序来展示单元测试的一般原则,因此示例通常仅限于极少数 classes。甚至 Kent Beck 也在他的书中使用著名的 Money class 示例,该示例主要限于单个 class。

在不受外部细节约束的最高级别进行测试。在您的有限示例中,TestGenerator 可能只是完美的水平。它可以让您全面测试您的业务逻辑,而不会在您更改内部结构时中断测试。如果您决定将 ChainNavigator 拆分为多个 class 或加入 ChainNavigatorMarkovChain,您的测试将不需要知道也不会中断。