我有一个TextGenerator
类,该类使用MarkovChain
类生成随机文本。从MarkovChain
创建下一个单词的逻辑位于ChainNavigator
类中:
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
是从源文本生成的。源文本的最后一个单词将没有任何“下一个状态”,因为它后面没有任何单词。生成长文本时,ChainNavigator
将到达最后一个单词,并且不知道返回什么。
我想测试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
类,在这里进行测试很有意义:
[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);
}
在两个地方都这样做似乎是重复的。在哪里做得更好?
答案 0 :(得分:0)
你的困惑其实很常见。其来源是“单元测试”中的“单元”一词。很多人会告诉你,“单元”就像一个单一的类,甚至只是一个单一的方法。几十年来,人们一直在说这个,但大多是错误的。这种误解源于这样一个事实,即您很少在书籍、文章和博客中看到真正的应用程序经过测试。由于很难用完整的应用程序展示单元测试的一般原则,因此示例通常仅限于非常少的类。甚至 Kent Beck 也在他的书中使用了著名的 Money 类示例,该示例主要限于单个类。
在不受外部细节约束的最高级别进行测试。在您有限的示例中,TestGenerator
可能只是完美级别。它可以让您完全测试您的业务逻辑,而不会在您更改内部结构时中断测试。如果您决定将 ChainNavigator
拆分为多个类或使用 ChainNavigator
加入 MarkovChain
,您的测试不需要知道并且不会中断。