4

众所周知,贝叶斯分类器是过滤垃圾邮件的有效方法。这些可以相当简洁(我们的只有几百个 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 应该如何处理这种从最简单的代码演变为通过测试的代码不是正确的方法的情况呢?(特别是如果事先知道最好的实现不能简单地进化)。

4

9 回答 9

4

Begin by writing tests for lower level parts of the spam filter algorithm.

First you need to have in your mind a rough design of how the algorithm should be. Then you isolate a core part of the algorithm and write tests for it. In the case of a spam filter that would maybe be calculating some simple probability using Bayes' theorem (I don't know about Bayesian classifiers, so I could be wrong). You build it bottom-up, step by step, until finally you have all the parts of the algorithm implemented and putting them together is simple.

It requires lots of practice to know which tests to write in which order, so that you can do TDD in small enough steps. If you need to write much more than 10 lines of code to pass one new test, you probably are doing something wrong. Start from something smaller or mock some of the dependencies. It's safer err on the smaller side, so that the steps are too small and your progress is slow, than trying to make too big steps and failing badly.

The "Cheap generic viagra" example that you have might be better suited for an acceptance test. It will probably even run very slowly, because you first need to initialize the spam filter with example data, so it won't be useful as a TDD test. TDD tests need to be FIRST (F = Fast, as in many hundreds or thousands tests per second).

于 2009-04-20T09:23:41.313 回答
2

这是我的看法:测试驱动开发意味着在编码之前编写测试。这并不意味着您为其编写测试的每个代码单元都必须是微不足道的。

此外,您仍然需要计划您的软件以合理有效的方式完成其任务。简单地添加越来越多的字符串似乎并不是解决这个问题的最佳设计。

因此,简而言之,您从可能的最小功能块编写代码(并对其进行测试),但您不会以这种方式设计算法(在伪代码中或您喜欢这样做)。

看看你和其他人是否同意会很有趣。

于 2009-04-20T08:51:24.277 回答
0

我不认为检查一个特定的字符串是否是垃圾邮件真的是一个单元测试,它更多的是一个客户测试。有一个重要的区别,因为它不是真正的红色/贪婪类型的东西。实际上,您可能应该有几百个测试文档。最初,有些会被归类为垃圾邮件,随着您对产品的改进,这些分类将更直接地符合您的要求。因此,您应该制作一个自定义应用程序来加载一堆测试文档,对它们进行分类,然后评估整体得分。当您完成该客户测试时,由于您尚未实施算法,因此得分将非常糟糕。但是您现在有了衡量前进进度的方法,考虑到您可以期待前进的学习/更改/实验的数量,这非常有价值。

当你实现你的算法时,(甚至是第一手的客户测试)你仍然可以用真正的单元测试来做 TDD。贝叶斯过滤器组件的第一个测试不会测量特定字符串是否被评估为垃圾邮件,而是衡量该字符串是否适当地通过了贝叶斯过滤器组件。然后,您的下一个测试将关注如何实现贝叶斯过滤器(正确构造节点、应用训练数据等)。

您确实需要对产品的发展方向有一个愿景,并且您的测试和实施应该针对该愿景。您也不能只是盲目地添加客户测试,您需要在考虑整体产品愿景的情况下添加测试。任何软件开发目标都会有你可以编写的好测试和坏测试。

于 2010-03-02T19:14:50.673 回答
0

在我看来,使用贝叶斯垃圾邮件过滤器,您应该使用现有方法。特别是您将使用贝叶斯定理,可能还有其他一些概率论。

在这种情况下,似乎最好的方法是根据这些方法来决定你的算法,这些方法应该经过试验和测试,或者可能是实验性的。然后,您的单元测试应该设计为测试 ispam 是否正确实现了您决定的算法,以及结果在 0 和 1 之间的基本测试。

关键是,您的单元测试并非旨在测试您的算法是否合理。您应该已经知道,或者您的程序可能被设计为实验,以查看它是否明智。

这并不是说 isspam 功能的性能不重要。但它不一定是单元测试的一部分。数据可能来自 alpha 测试的反馈、新的理论结果或您自己的实验。在这种情况下,可能需要一种新算法,并且需要新的单元测试。

另请参阅有关测试随机数生成器的问题。

于 2009-04-20T11:20:48.520 回答
0

这里的问题不在于测试驱动开发,而在于您的测试。如果您开始针对单个测试开发代码,那么您所做的所有测试就是指定一个字符串检查函数。

TDD 的主要思想是在编写代码之前考虑您的测试。您无法详尽地测试垃圾邮件过滤器,但您可以通过数万或数十万个测试文档得出一个合理的近似值。在存在这么多测试的情况下,朴素贝叶斯算法比十万行 switch 语句更简单。

实际上,您可能无法通过 100% 的单元测试,因此您只需要尝试尽可能多地通过即可。您还必须确保您的测试足够真实。如果你这样想,测试驱动开发和机器学习有很多共同点。

于 2009-04-23T10:08:02.600 回答
0

For me, what you call minimum amount of code to pass a test is the whole IsSpam() function. This is consistent with its size (you say only a few hundred LoC).

Alternatively, incremental approach does not claim to code first and think afterwards. You can design a solution, code it and then refine the design with special cases or better algorithm.

Anyway, refactoring does not consist simply in adding new stuff over old one. For me this is a more destructive approach, where you throw away old code for a simple feature and replace it with new code for a refined and more elaborate feature.

于 2009-04-20T09:32:09.407 回答
0

你有你的单元测试,对吧?

这意味着您现在可以重构代码甚至重写它并使用单元测试来查看您是否破坏了某些东西。

先让它工作,然后让它干净——是时候进行第二步了:)

于 2009-04-20T09:34:47.153 回答
0

(1) 你不能说一个字符串“是垃圾邮件”或“不是垃圾邮件”,就像你说一个数字是否是素数一样。这不是黑色或白色。

(2) 仅使用用于测试的示例编写字符串处理函数是不正确的,当然也不是 TDD 的目标。例子应该代表一种价值观。TDD 不能防止愚蠢的实现,所以你不应该假装你根本不知道,所以你不应该写return text == "Cheap generic viagra".

于 2009-04-20T09:39:42.570 回答
0

您所描述的问题是理论上的,通过添加对测试的响应,您将制作一个大而混乱的泥球。你缺少的东西非常重要。

循环是:红色 --> 绿色 --> 重构

你不只是在红色和绿色之间跳跃。一旦测试通过(绿色),您就可以重构生产代码和测试。然后你编写下一个失败的测试(红色)。

如果你正在重构,那么你正在消除重复和混乱,并且随着它的增长而变得混乱。您将很快达到提取方法、建立评分和评级以及可能引入外部工具的地步。只要它是最简单的事情,你就会这样做。

不要只是在红色和绿色之间跳来跳去,否则你的所有代码都会变得一团糟。该重构步骤不是可选的或任意的。这是必不可少的。

于 2010-03-02T18:56:24.193 回答