您如何编写单元测试用例?


14

有时我最终会为其他开发人员编写的代码编写单元测试用例。有时候我真的不知道开发人员要做什么(业务部分),而我只是操纵测试用例来获得绿线。这些事情在业界正常吗?

正常趋势是什么?开发人员是否应该为自己编写的代码编写单元测试用例?


2
“有点”?“ dint”是什么意思?
S.Lott

Answers:


12

尝试阅读此博客文章:编写出色的单元测试:最佳实践和最佳实践

但是网络上还有无数其他人。

直接回答您的问题...

  1. “正常趋势”-我猜这在各地之间可能会有所不同,对我来说正常的情况对其他人来说可能很奇怪。
  2. 我会说(以我的选择)编写代码的开发人员应该编写测试,最好使用TDD之类的方法,在测试之前,您应该在代码之前编写测试。但是其他人可能在这里有不同的方法和想法!

而且您描述的编写测试方式(在您的问题中)是完全错误的!


9

这种方法使单元测试毫无价值。

当某些实际操作无法按预期进行时,您需要使单元测试失败。如果您不这样做,甚至在测试代码前编写测试,就好像烟雾报警器无法正常工作一样。


8
这不是真的。或者更确切地说,在理想世界中确实如此,但可惜的是,我们常常离这一点还很遥远。考虑使用没有测试,没有规范的遗留代码,并且没有任何人可以可靠地告诉您最新细节,确切地说,特定代码应该做什么(这在大部分现有项目中都是现实)。即使在这种情况下,仍然可能值得编写单元测试来锁定代码的当前状态,并确保您在将来的重构,错误修复或扩展中不会破坏任何内容。
彼得Török

2
另外,我猜您的意思是“在要测试的代码之后编写测试”,对吗?
彼得Török

@Péter,措辞有误-你说对了。但是,如果您决定编写测试,则应该做一些有用的事情。在我看来,只是盲目地调用代码说这是一个测试,而不是测试。

ørn,如果您的意思是我们必须在单元测试中包含有意义的断言,以验证被测试的代码确实按照我们的想法做,那么我完全同意。
彼得Török

3

如果您不知道某个功能是什么,那么就无法为其编写单元测试。就您所知,它甚至都没有达到预期的效果。您需要先了解它应该做什么。然后编写测试。


3

在现实世界中,为其他人的代码编写单元测试是完全正常的。当然,原始开发人员应该已经做到了,但是通常您会收到遗留代码,而这只是没有完成。顺便说一句,遗留代码是否源于数十年前的遥远星系,或者您的同事是否在上周检查过它,还是您今天编写的,遗留代码是未经测试的代码

问自己:为什么我们要编写单元测试?走向绿色显然只是达到目的的一种手段,最终目的是证明或反驳有关被测代码的主张。

假设您有一种方法可以计算浮点数的平方根。在Java中,接口会将其定义为:

public double squareRoot(double number);

无论是编写实现还是其他人编写,都想声明squareRoot的一些属性:

  1. 它可以返回简单的根,如sqrt(4.0)
  2. 它可以找到像sqrt(2.0)这样的真实根,并且具有合理的精度
  3. 它发现sqrt(0.0)是0.0
  4. 当输入负数时,即在sqrt(-1.0)上它抛出IllegalArgumentException

因此,您开始将这些作为单独的测试编写:

@Test
public void canFindSimpleRoot() {
  assertEquals(2, squareRoot(4), epsilon);
}

糟糕,此测试已失败:

java.lang.AssertionError: Use assertEquals(expected, actual, delta) to compare floating-point numbers

您忘记了浮点运算。好,介绍double epsilon=0.01并开始:

@Test
public void canFindSimpleRootToEpsilonPrecision() {
  assertEquals(2, squareRoot(4), epsilon);
}

并添加其他测试:最后

@Test
@ExpectedException(IllegalArgumentException.class)
public void throwsExceptionOnNegativeInput() {
  assertEquals(-1, squareRoot(-1), epsilon);
}

哎呀,再次:

java.lang.AssertionError: expected:<-1.0> but was:<NaN>

您应该已经测试:

@Test
public void returnsNaNOnNegativeInput() {
  assertEquals(Double.NaN, squareRoot(-1), epsilon);
}

我们在这里做了什么?我们从关于该方法应如何工作的一些假设开始,发现并非全部都是正确的。然后,我们将测试套件设为绿色,以记下该方法根据我们正确的假设进行工作的证明。现在,此代码的客户可以依靠此行为。如果有人要用其他东西交换squareRoot的实际实现,例如确实引发了异常而不返回NaN的东西,我们的测试将立即捕获到这一点。

这个例子很简单,但是经常会在不清楚实际作用的地方继承大量代码。在这种情况下,通常在代码周围放置测试工具。从有关代码应如何表现的一些基本假设开始,为它们编写单元测试,然后进行测试。如果为绿色,则编写更多测试。如果是Red,那么现在您有一个失败的断言,您可以坚持某个规范。遗留代码中可能存在错误。也许规格对此不清楚。也许您没有规格。在这种情况下,重写测试,使其记录意外的行为:

@Test
public void throwsNoExceptionOnNegativeInput() {
  assertNotNull(squareRoot(-1)); // Shouldn't this fail?
}

随着时间的流逝,您最终获得了一个测试工具,该工具记录了代码的实际行为,并成为某种编码规范。如果您想更改旧代码或将其替换为其他代码,则可以使用测试工具来验证新代码的行为是否相同,或者新代码在预期和受控方式下的行为是否不同(例如,实际上修复了您期望它修复的错误)。实际上,这种挽具不必在第一天就完成,事实上,拥有不完备的挽具几乎总比根本没有挽具要好。拥有安全带意味着您可以更轻松地编写客户代码,知道更改后期望中断的地方,以及最终更改时可以中断的地方。

您应该尝试摆脱必须编写单元测试的想法,就像您必须在表单上填写必填字段一样。而且,您不应该只是为了使红线变为绿色而编写单元测试。单元测试不是您的敌人,单元测试是您的朋友。


1

在为打印机编写测试用例时,我尝试考虑每个小组件……以及如何做才能打破它。举例来说,让扫描器说一下,它使用什么命令(在pjl printer-job-language中),我可以写些什么来测试每一个功能……现在,我可以做些什么来尝试破坏它。

我尝试对每个主要组件执行此操作,但是涉及软件而不是太多硬件时,您要查看每种方法/功能并检查边界等。


1

听起来您正在与其他未进行单元测试的开发人员(或维护其他开发人员编写的代码)一起工作。在那种情况下,我认为您肯定想知道您要测试的对象或方法应该做什么,然后为其创建测试。

不会是TDD,因为您没有先编写测试,但是可以改善情况。您可能还想使用存根创建测试对象的副本,以便可以确定代码失败时测试可以正常运行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.