你如何进行单元测试?

8

我已经了解了一些有关单元测试的内容,想知道您是如何进行单元测试的。显然,单元测试应该将程序分解为非常小的“单元”,然后从那里测试功能。

但我想知道,仅对一个类进行单元测试是否足够?还是您进一步对算法、公式等进行单元测试?或者扩大范围进行asp页面/功能的单元测试?或者您根本不进行单元测试?

9个回答

6
我想知道,仅对一个类进行单元测试是否足够?或者你会进一步进行算法、公式等单元测试吗?或者你会扩展到对ASP页面/功能进行单元测试吗?或者你根本不进行单元测试?
算法应该在一个类中,自动进行单元测试。公式作为函数在类中,也进行了单元测试。单元测试测试行为、状态和所有可以测试的最小开发单元。因此,是的,算法的所有细节都得到了测试。稍后,当您拥有使用其他类的类时,您将进行集成测试(通常使用单元测试程序进行测试)。这将是相同的,但在更高的级别上。

5

我将单元测试作为一种工具来衡量代码变更(例如重构,修复错误,添加功能)后是否仍然能够正常工作。 由于我使用Java,因此主要使用JUnit编写自动化单元测试。只需调用一条命令行脚本,便可运行数百个测试用例,以验证代码是否存在问题。


2

我单元测试的是功能,而不是个别方法或类。编写和维护单元测试的开销并不小,一般来说我不建议为每一点代码编写单元测试。然而,对于功能进行单元测试是值得的,因为客户为此付费。


2

以下是我认为在单元测试中有用的通用指导原则:

1)识别边界对象(Win / WebForms,CustomControls等)。

2)识别控制对象(业务层对象)。

3)确保至少编写单元测试以测试由边界对象调用的控制对象公共方法。

这样,您就可以确保覆盖应用程序的主要功能方面,并且不会冒微观测试的风险(除非您想这样做)。


2
我是一个相当懒散的单元测试者,但我是一名学者,所以我的大多数程序都是为自己使用的脚本或更重型的研究程序。我的许多工作都是关于编译器的,所以经典的单元测试是困难的——例如,除非你有一个编译器来包装它,否则很难对寄存器分配器进行单元测试,在那个时候你可能会直接进入回归测试。
但有一些重要的例外。我在Lua中做了大量的脚本编写,它被分成模块(源文件可以是模块)。如果我正在开发新的东西,比如与shell交互的实用程序,我会把一些单元测试放在模块内部,每次加载模块时都会运行它们。Lua速度很快,通常这并不重要。以下是一些示例:
assert(os.quote [[three]] == [[three]]) assert(os.quote [[three"]] == [['three"']]) assert(os.quote [[your mama]] == [['your mama']]) assert(os.quote [[$i]] == [['$i']])
如果我是一个好狗,我会在编写函数之前编写一些简单的测试。
我用单元测试的另一件事是,如果有什么难题,我会使用QuickCheck测试代数定律,这是一个随机测试工具,必须亲自使用才能相信。这是我使用过的唯一一个让单元测试变得有趣的工具。那里有一个链接失效了,但你可以在他的博客上找到Tom Moertel关于ICFP编程竞赛的故事
希望这对你有所帮助。QuickCheck已经多次拯救了我的生命。最近,我使用精确的有理算术测试离散余弦变换的代码,然后将其移植到C中!

1

我们通常使用Java库为机器编写程序。一个程序通常由20多个库组成,因此我们的做法是对每个库进行单元测试。这并不是一项容易的任务,因为很多时候库之间的耦合非常紧密,这种情况下就不太可能进行单元测试。

我们的代码并不像我们希望的那样模块化,但出于兼容性问题,我们必须接受它,并且在许多情况下,打破耦合意味着打破兼容性。


1

我尽可能地测试公共接口(我使用的是C++,但语言并不重要)。最重要的方面是在编写代码时编写测试(紧接着或之后)。从经验上来看,以这种方式开发将导致更可靠的代码,并使其更易于维护(因为破坏测试的更改将立即显而易见)。

对于所有项目,我建议您从一开始就考虑测试-如果您编写了一个依赖于另一个复杂类的类,则使用接口,以便在测试时“模拟”更复杂的对象(数据库访问,网络访问等)。

编写大量测试似乎会减慢您的速度,但实际上,在项目的整个生命周期内,您将花费更少的时间修复错误。

经常进行测试-如果它可以破坏,那么它就会破坏-最好在测试时破坏它,而不是在客户尝试使用它时破坏它。


1

仅对一个类进行单元测试是不够的。类相互配合,这也必须进行测试。

除了类之外,还有更多的单元:

  • 模块,
  • 层,
  • 框架。

当然,还有不同形式的测试,例如集成和验收。

我测试那些我认为困难的事情,那些我认为可能会改变的事情,接口以及我必须修复的事情。而且我大多数时候都从测试开始,试图确保我理解我要解决的问题。


1

仅仅因为编译通过并不意味着它能运行!这就是单元测试的本质。试试代码,确保它正在做你认为它在做的事情。

让我们面对现实吧,如果你从Matlab中带来一个矩阵变换,很容易在某个地方搞错加号或减号。那种事情很难发现。如果不试一下,你就不知道它是否能正确工作。调试100行代码比调试100,000行代码要容易得多。


有些人会走向极端,他们试图测试每一个可以想象的东西。测试变成了目的本身。

这在后期维护阶段可能很有用。您可以快速检查以确保更新没有破坏任何内容。

但是,所涉及的开销可能会瘫痪产品开发!而未来更改可能涉及大量的测试更新开销,以更改功能。

(在多线程和任意执行顺序方面也可能变得混乱。)


通常情况下,除非另有指示,我的测试会尝试达到中间地带。

我会尝试进行更大粒度的测试,以验证基本的一般功能。我不会过于担心每种可能的边界情况。(这就是ASSERT宏的作用所在。)

例如:当我编写代码以通过UDP发送/接收消息时,我会快速地编写一个测试,使用该类通过环回接口发送/接收数据。没有什么花哨的东西。快速、简单、脏的代码。我只是想试试它。确保在构建其上之前它实际上是有效的。

另一个例子:从Firewire相机读取相机图像。我编写了一个快速而简单的GTK应用程序来读取图像、处理它们并实时显示它们。其他人称之为集成测试。但我可以用它来验证我的Firewire接口、我的Image类、我的Bayer RGGB->RGB变换、我的图像方向和对齐,甚至是否再次倒置相机。只有在这被证明不足时才需要进行更详细的测试。

另一方面,即使对于像这样简单的事情:

template<class TYPE> inline TYPE MIN(const TYPE & x, const TYPE & y) { return x > y ? y : x; }
template<class TYPE> inline TYPE MAX(const TYPE & x, const TYPE & y) { return x < y ? y : x; }

我写了一个一行的SHOW宏来确保我没有搞错符号:

  SHOW(MIN(3,4));  SHOW(MAX(3,4));

我想做的只是验证它在一般情况下是否按照预期运行。我更担心的不是它如何处理NaN / +-Infinity / (double,int),而是我的同事之一是否决定更改参数顺序并搞砸了。

就工具而言,有很多单元测试的东西。如果它对你有帮助,那就更好了。如果没有,那么你不需要太过花哨。

我经常编写一个测试程序,将数据转储到类中,然后使用 SHOW 宏将其全部打印出来:

#define SHOW(X)  std::cout << # X " = " << (X) << std::endl

(或者,我的许多类可以使用内置的operator<<(ostream&)方法进行自我打印。这是一种非常有用的调试技术和测试技术!)

Makefile 可以轻松扩展,以自动从测试程序生成输出文件,并自动将这些输出文件与先前已知的(经过审核的)结果进行比较(diff)。

也许不太花哨,可能不太优雅,但作为技术而言,这非常有效,实现速度快,开销非常低。(当你的经理反对在测试上浪费时间时,这有其优势。)


最后我想留给你一个思考。 这会让我被扣分,所以不要这样做!

有一段时间我需要一个测试程序。这是必须交付的内容。该程序本身必须验证另一个类是否正常工作。但它不能访问外部数据文件。(我们不能依赖程序相对于其他任何东西的位置。也没有绝对路径。)项目的单元测试框架与我必须使用的编译器不兼容。它还必须在一个文件中。项目的makefile系统不支持将多个文件链接在一起以进行低级别的测试程序。(应用程序可以使用库。但每个测试程序只能使用一个文件。)

所以,上帝原谅我,我“打破了规则”...

<尴尬>
我使用了宏定义。当设置一个 #define 宏时,数据被写入第二个 .c 文件作为结构体数组的初始化器。随后,在软件重新编译和那个带有结构体数组的第二个 .c 文件 #included 时,若 #define 宏没有设置,则将新结果与先前存储的数据进行比较。是的,我 #included 了一个 .c 文件。真是太尴尬了。
</尴尬>

但这是可行的……


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接