简而言之
- 将您的任务重新定义为测试API实现。
- 由于该算法是用概念(假设,翻译选项)表达的,这些概念本身相当复杂,因此通过首先开发(使用TDD)封装这些概念的类来自下而上地编程。
- 由于该算法不是特定于语言的,而是抽象了特定于语言的操作(所有翻译选项,如果适用),因此将特定于语言的部分封装在单独的对象中,使用依赖注入为该对象,并使用该对象的简单虚拟实现进行测试。
- 利用您对算法的了解来建议良好的测试用例。
测试 API
在专注于实现(算法)之前,你会犯一个错误。首先想象一下,如果有一个魔法类可以完成算法的工作,那它的API是什么?它的输入是什么,输出是什么?输入和输出之间必须有哪些必需的连接。您想要将算法封装在此类中,并重新组织问题以生成此类。
在这种情况下,似乎输入是已经进行了分词的句子,输出是已经被翻译成不同语言的分词句子。因此,我猜API大概是这样的:
interface Translator {
public List<String> translate(List<String> original);
public Locale getTranslatesFrom();
public Locale getTranslatesTo();
}
那就是策略设计模式的一个例子。因此,你的任务不再是“如何使用TDD来实现这个算法”,而是“如何使用TDD来实现策略设计模式的一个特定案例”。
接下来,你需要想出一系列测试用例,从最简单到最困难,来使用这个API。也就是说,给
translate
方法传递一组原始句子值。对于每个输入,你必须给出输出的一组约束条件。翻译必须满足这些约束条件。注意,你已经对输出有一些约束条件:
非空;不包含null或空元素。
你会想要一些例句,你
确定算法应该输出什么。我怀疑你会发现这样的句子很少。将这些测试按照从容易通过到难以通过的顺序排列。这将成为你在实现
Translator
类时的待办事项清单。
自底向上实现
你会发现让你的代码通过超过几个这样的测试用例非常困难。那么你如何彻底测试你的代码呢?
再次查看算法。它足够复杂,以至于translate
方法不能直接完成所有工作。它将委托其他类来完成大部分工作。
将空假设放入堆栈0中
您需要一个Hypothesis
类吗?一个HypothesisStack
类?
对于所有的翻译选项
您需要一个TranslationOption
类吗?
如果适用,则
是否有一个TranslationOption.isApplicable(...)
方法?
如果可能,与现有假设重新组合
是否有一个Hypothesis.combine(Hypothesis)
方法?一个Hypothesis.canCombineWith(Hypothesis)
方法?
如果堆栈太大,则修剪堆栈
是否有一个HypothesisStack.prune()
方法?
你的实现可能需要额外的类。你可以使用TDD单独实现每个类。你对Translator
类的少量测试最终将成为集成测试。其他类将比Translator
更容易测试,因为它们将具有明确定义、狭窄定义的任务。
因此,在实现委托类之前,推迟实现Translator
。也就是说,我建议你采用自下而上的方式编写代码,而不是自上而下。编写实现给定算法的代码成为最后一步。在那个阶段,你有了可以使用Java代码编写实现的类,这些代码看起来非常像你的算法伪代码。也就是说,你的translate
方法的主体只有大约13行。
将真实生活中的复杂性推入相关对象
你的翻译算法是通用的,可以用于任何语言对之间的翻译。我猜想使其适用于特定语言对的原因是“对于所有的翻译选项”和“如果适用,则”部分。我猜想后者可以通过一个“TranslationOption.isApplicable(Hypothesis)”方法来实现。所以使算法特定于某种语言的是生成翻译选项。将其抽象为一个工厂对象,由类委托处理。就像这样:
interface TranslationOptionGenerator {
Collection<TranslationOption> getOptionsFor(Hypothesis h, List<String> original);
}
到目前为止,您可能一直在考虑在真实语言之间进行翻译,其中包含所有令人讨厌的复杂性。但是,您不需要那种复杂性来测试您的算法。您可以使用一对比真实语言简单得多的虚假语言来测试它。或者(等效地)使用一个不像实际应用那么丰富的TranslationOptionGenerator
。使用依赖注入将Translator
与TranslationOptionGenerator
关联起来。
使用简单的虚拟实现代替逼真的关联
现在考虑一些最极端简单的TranslationOptionGenerator
案例,算法必须处理:
- 例如当空假设没有选项时
- 只有一个选项
- 只有两个选项。
- 一种英语到 哈拉帕文的翻译器。没有人知道如何翻译成哈拉帕文,因此这个翻译器每次都指示无法翻译。
- 身份翻译器:将英语翻译成英语的翻译器。
- 有限的英语到新生儿翻译器:它只能翻译句子“我饿了”、“我需要换尿布”和“我累了”,它将它们翻译为“Waaaa!”。
- 简单的英式英语到美式英语翻译器:大多数单词相同,但有少数拼写不同。
您可以使用此功能生成不需要某些循环或不需要进行isApplicable
测试的测试用例。
将这些测试用例添加到您的TODO列表中。您将不得不编写虚假的简单TeranslatorOptionGenerator
对象,以供这些测试用例使用。