我应该在哪个级别测试我的逻辑
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 或加入 ChainNavigator
和 MarkovChain
,您的测试将不需要知道也不会中断。
我有一个 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 或加入 ChainNavigator
和 MarkovChain
,您的测试将不需要知道也不会中断。