众所周知,贝叶斯分类器是过滤垃圾邮件的有效方法。这些可以相当简洁(我们只有几百个LoC)但是在获得任何结果之前,所有核心代码都需要预先编写。
但是,TDD方法要求只能编写通过测试的最小代码量,因此给出以下方法签名:
bool IsSpam(string text)
以下文字字符串,显然是垃圾邮件:
"Cheap generic viagra"
我可以编写的最小代码量是:
bool IsSpam(string text)
{
return text == "Cheap generic viagra"
}
现在我可能会添加另一条测试消息,例如
"Online viagra pharmacy"
我可以将代码更改为:
bool IsSpam(string text)
{
return text.Contains("viagra");
}
......依此类推,等等。直到某些时候,代码变得混乱了字符串检查,正则表达式等,因为我们已经进化而不是考虑它并以一种不同的方式从头开始编写它。
那么TDD应该如何处理这种类型的情况,即从最简单的代码中提取代码以通过测试不是正确的方法? (特别是如果事先知道最佳实现不能简单地进化)。
答案 0 :(得分:4)
首先为垃圾邮件过滤器算法的较低级别部分编写测试。
首先,您需要在脑海中对算法应该如何进行粗略设计。然后,您隔离算法的核心部分并为其编写测试。在垃圾邮件过滤器的情况下,可能使用贝叶斯定理计算一些简单的概率(我不知道贝叶斯分类器,所以我可能是错的)。你自下而上地逐步构建它,直到最后你已经实现了算法的所有部分并将它们放在一起很简单。
需要大量练习才能知道要以哪种顺序编写哪些测试,以便您可以用足够小的步骤进行TDD。如果您需要编写超过10行代码来传递一个新测试,那么您可能做错了。从较小的东西开始或模拟一些依赖项。它在较小的一侧更安全,因此步骤太小而且进度缓慢,而不是试图做太大的步骤而且失败严重。
您拥有的“廉价通用伟哥”示例可能更适合acceptance test。它甚至可能运行得非常慢,因为您首先需要使用示例数据初始化垃圾邮件过滤器,因此它不会用作TDD测试。 TDD测试需要FIRST(F =快速,如每秒数百或数千次测试)。
答案 1 :(得分:2)
这是我的看法:测试驱动开发意味着在编码之前编写测试。这并不意味着您编写测试的每个代码单元都必须是微不足道的。
此外,您仍需要规划软件,以合理有效的方式完成任务。简单地添加越来越多的字符串似乎不是解决此问题的最佳设计。
简而言之,您可以从尽可能最小的功能中编写代码(并对其进行测试),但是您不会以这种方式设计算法(使用伪代码或者您喜欢这样做)。
看看你和其他人是否同意会很有趣。
答案 2 :(得分:0)
对我来说,你称之为传递测试的最小代码量是整个IsSpam()
函数。这与它的大小一致(你只说几百LoC)。
或者,增量方法不会声称先编码并在之后进行思考。您可以设计解决方案,对其进行编码,然后使用特殊情况或更好的算法来优化设计。
无论如何,重构不仅仅是在添加新内容之外。对我来说,这是一种更具破坏性的方法,在这种方法中,您可以丢弃旧代码以获取简单的功能,并将其替换为新代码,以获得精致且更精细的功能。
答案 3 :(得分:0)
你有单元测试,对吧?
这意味着您现在可以重构代码甚至重写代码并使用单元测试来查看是否有损坏。
首先让它工作,然后让它干净 - 现在是时候进行第二步了:)
答案 4 :(得分:0)
(1)你不能说一个字符串“是垃圾邮件”或“不是垃圾邮件”,就像你说一个数字是否为素数一样。这不是黑色或白色。
(2)使用仅用于测试的示例来编写字符串处理函数是不正确的,当然也不是TDD的目标。示例应代表一种价值观。 TDD不能防止愚蠢的实现,所以你不应该假装你完全不知道,所以你不应该写return text == "Cheap generic viagra"
。
答案 5 :(得分:0)
在我看来,使用贝叶斯垃圾邮件过滤器,您应该使用现有方法。特别是你将使用贝叶斯定理,可能还有其他一些概率论。
在这种情况下,似乎最好的方法是根据这些方法确定您的算法,这些方法应该尝试和测试,或者可能是实验性的。然后,您的单元测试应该被设计为测试ispam是否正确实现您决定的算法,以及结果在0和1之间的基本测试。
关键是,您的单元测试不是为了测试您的算法是否合理。您应该已经知道,或者您的程序可能被设计为实验,以确定它是否合理。
这并不是说isspam功能的表现并不重要。但它不必是单元测试的一部分。数据可以来自alpha测试的反馈,新的理论结果或您自己的实验。在这种情况下,可能需要一种新算法,并且需要进行新的单元测试。
另见this question关于测试随机数生成器的信息。
答案 6 :(得分:0)
这里的问题不是测试驱动开发,而是测试。如果您开始针对单个测试开发代码,那么您的所有测试都在指定字符串检查功能。
TDD的主要思想是在编写代码之前考虑一下你的测试。您无法详尽地测试垃圾邮件过滤器,但您可以通过数十或数十万个测试文档得出合理的近似值。在存在大量测试的情况下,朴素贝叶斯算法比十万行切换语句更简单。
实际上,您可能无法通过100%的单元测试,因此您只需尝试尽可能多地通过。您还必须确保您的测试足够逼真。如果以这种方式考虑它,测试驱动开发和机器学习有很多共同之处。
答案 7 :(得分:0)
你所描述的问题是理论上的问题,即通过在测试中加入残骸,你将会制造一个大而混乱的泥球。你缺少的东西非常重要。
周期为:红色 - >绿色 - >重构
你不只是在红色和绿色之间反弹。一旦你通过测试(绿色),你就会重构生产代码和测试。然后你写下一个失败的测试(红色)。
如果你正在重构,那么你就会消除重复,混乱以及随着它的增长而出现的问题。您将很快达到提取方法,建立评分和评级以及可能引入外部工具的程度。一旦最简单的事情发挥作用,你就会尽快完成。
不要只是在红色和绿色之间反弹,否则你的所有代码都会被污染。重构步骤不是可选的或任意的。这很重要。
答案 8 :(得分:0)
我不认为检查特定字符串是否是垃圾邮件实际上是一个单元测试,更多的是客户测试。有一个重要的区别,因为它不是真正的红/贪婪类型的东西。实际上你应该有几百个测试文件。最初有些将被归类为垃圾邮件,当您改进产品时,分类将更直接地匹配您想要的。因此,您应该创建一个自定义应用程序来加载一堆测试文档,对它们进行分类,然后评估整体评分。当您完成该客户测试后,由于您尚未实施算法,因此得分将非常糟糕。但是你现在有了一种衡量未来进展的方法,考虑到你可以期待的学习/变化/实验的数量,这是非常有价值的。
当您实施算法时(甚至是第一手的客户测试),您仍然可以使用真实的单元测试进行TDD。贝叶斯过滤器组件的第一个测试不会测量特定字符串是否评估为垃圾邮件,而是该字符串是否适当地通过贝叶斯过滤器组件。接下来的测试将集中在如何实现贝叶斯过滤器(正确构造节点,应用训练数据等)。
您确实需要了解产品的去向,并且您的测试和实施应该针对该愿景。您也不能盲目地添加客户测试,您需要在考虑整体产品愿景的情况下添加测试。任何软件开发目标都会有很好的测试和可以编写的错误测试。