单元测试编写的最佳实践

3
我有一个“最佳实践”问题。我正在为某个方法编写测试,但有多个入口值。我应该为每个入口值编写一个测试,还是应该更改entryValues变量的值,并调用.assert()方法(对所有可能值的范围进行测试)?
谢谢你的帮助。 最好的问候,
Pedro Magueija
编辑:我正在使用.NET。Visual Studio 2010与VB。
6个回答

5

如果你需要编写许多测试,这些测试仅在初始输入和最终输出方面有所不同,那么你应该使用数据驱动测试。这样可以一次定义测试以及输入和输出之间的映射关系。单元测试框架将把其解释为每个用例一个测试。如何实际操作取决于您使用的框架。


数据驱动(参数化)测试在理论上听起来不错,但在我看来,它们使测试用例更难理解和维护。为每个用例编写一个具有描述性名称的单独测试有助于我更好地理解用例。公平地说,这意味着一些重复的代码,但通过重构可以将其最小化到可承受的水平。 - Péter Török
@Peter - 参数化测试还可以在NUnit中使用Description和Name属性来提高可读性。当单元测试中的所有步骤都是恒定的,只有输入输出发生变化时,我会使用数据驱动测试。 - Gishu

2

最好为您要测试的方法编写单独的单元测试,覆盖每个输入/输出集的所有可能值(或者至少是您想要进行单元测试的那些输入/输出集)。


1
  1. 小型测试更容易阅读。
  2. 名称是测试文档的一部分。
  3. 分离的方法可以更精确地指示失败的原因。

因此,如果您有一个像以下这样的单个方法:

void testAll() {
  // setup1
  assert()
  // setup2
  assert()
  // setup3
  assert()
}

在我的经验中,这个很快就会变得非常庞大,因此变得难以阅读和理解,所以我会这样做:
void testDivideByZero() {
  // setup
  assert()
}

void testUnderflow() {
  // setup
  assert()
}


void testOverflow() {
  // setup
  assert()
}

1
我应该为每个输入值编写一个测试,还是应该更改entryValues变量的值,并调用.assert()方法(对所有可能值的范围进行操作)?
如果您只有一条代码路径,通常不会测试所有可能的输入。您通常想要测试的是“有趣”的输入,这些输入是数据的良好示例。
例如,如果我有一个函数:
define add_one(num) {
  return num+1;
}

我无法针对所有可能的值编写测试,因此我可能会使用MAX_NEGATIVE_INT、-1、0、1、MAX_POSITIVE_INT作为我的测试集,因为它们是我可能得到的有趣值的良好代表。

您应该至少有一个输入与每个代码路径相对应。如果您有一个函数,其中每个值都对应于唯一的代码路径,那么我会考虑为可能值的完整范围编写测试。这样做的一个例子是命令解析器。

define execute(directive) {
  if (directive == 'quit') { exit; }
  elsif (directive == 'help') { print help; }
  elsif (directive == 'connect') { intialize_connection(); }
  else { warn("unknown directive"); }
}

为了清晰起见,我使用elif而不是分派表。我认为这使得每个唯一的值都有不同的行为变得清晰,因此您需要测试每个可能的值。

0
你是在谈论这个差异吗?
- (void) testSomething
{
    [foo callBarWithValue:x];
    assert…
}

- (void) testSomething2
{
    [foo callBarWithValue:y];
    assert…
}

对比。

- (void) testSomething
{
    [foo callBarWithValue:x];
    assert…
    [foo callBarWithValue:y];
    assert…
}

第一种版本更好,因为当测试失败时,您将更清楚地知道哪些部分不起作用。第二个版本显然更方便。有时我甚至会将测试值放入集合中以节省工作量。当我可能想要单独调试某个特定案例时,我通常选择第一种方法。当测试值确实属于同一组并形成一个连贯的单元时,我当然只选择后者。


0

你有两个选择,但你没有提到你使用的测试框架或语言,所以其中一个可能不适用。

1)如果你的测试框架支持它,请使用RowTest,MBUnit和Nunit支持这一点。如果你使用.NET,这将允许你在方法上放置多个属性,并且每行都将作为单独的测试执行。

2)如果不支持,则为每个条件编写一个测试,并确保给它一个有意义的名称,以便在测试失败时(当测试失败时)可以轻松找到问题并且对你有意义。

编辑 在Nunit中称为TestCase Nunit TestCase Explination


嗨Kev,我要去搜索RowTest。看起来是一个非常好的方法。谢谢你和所有抽出时间回答的人。 - Pedro Magueija

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