单元测试实战(二):初体验

简介:

上篇文章中提到过,单元测试是程序员编写的代码,用于验证某段代码的行为是否与开发者所期望的一致,并且说明了它的重要性,现在是时候来看看在实际开发中如何去做了。

为了验证代码行为是否与我们所期望的一致,就需要使用断言(assertion)。断言就是对待测代码的结果进行检查,判断是否与期望的一致。比如下面的IsTrue方法,它检查给定的条件是否为真:如果非真,则断言失败,程序中止。

public void IsTrue(bool condition)
{
    if (!condition)
    {
        Abort();
    }
}

比如,有如下代码:

int a = 2;

IsTrue(a == 2);

如果a不等于2,那么程序会退出。不难理解,所有类似的断言都可以使用此方法。但情况却不都像a == 2这样简单,我们要断言的值可能是浮点数、字符串、集合等等很多情况,如果都使用IsTrue,那么在调用前我们都要考虑如何进行比较,而且我们的处理也不止程序中止这种方式,好像很复杂啊。

如果有一个专门处理这些断言的工具多好啊。很幸运,已经有了,我们可以选择NUnit或者MbUnit。我们这里先看看NUnit,它是.NET这边最正统的单元测试工具。NUnit提供了自己的GUI工具,我们可以通过该工具测试,但是有更好的选择——TestDriven.NET,有了它,就不需要在开发环境和测试工具间来回切换了,单元测试在VS内就可以完成。

在NUnit中,有个Assert类,它提供了多种断言方式,可以满足大部分需要了。比如针对两个整数是否相等的断言,可以使用Assert.AreEqual,我们没必要写自己的IsTrue方法了,只要调用:Assert.AreEqual(2, a)即可。

好,现在从一个简单的例子开始,看一下如何使用NUnit进行单元测试。

计划你的测试

我们考虑如下一个方法,它查找一个int数组中的元素的最大值:

static int Largest(int[] array);

给定一个数组[7, 8, 9],该方法应该返回9(这就是我们的期望值)。等一下,你有没有想到其它的测试呢?思考一分钟再往下看。

首先,元素的位置应与返回值无关,所以可想到如下测试:

l [7, 8, 9] -> 9

l [8, 9, 7] -> 9

l [9, 7, 8] -> 9

接下来,如果数组中有两个相等的最大值,该是如何?

l [7, 9, 8, 9] -> 9

还有,如果数组只有一个元素,会是怎样?

l [1] -> 1

别忘了整数包含负数呢:

l [-9, -8, -7] -> -7

还不写代码啊,我早就想到它的实现方法了:

public static int Largest(int[] array)
{
    int index, max = Int32.MaxValue;
    for (int index = 0; index < array.Length - 1; index++)
    {
        if (array[index] > max)
        {
            max = array[index];
        }
 
        return max;
    }
}

测试一个简单的方法

我们来给上面的方法编写一个测试用例(Test Case):

[TestFixture]
public class MyUtilTest
{
    [Test]
    public void LargestOf3()
    {
        Assert.AreEqual(9, MyUtil.Largest(new int[]{8, 9, 7}));
    }
}

好,激动人心的时刻到了,运行下看看。

不好,测试没通过,出现了下面的信息

TestCase 'Ch02.MyUtilTest.LargestOf3' failed: 
  Expected: 9
  But was:  2147483647
    D:\myWorks\VS2008\Consoles\UnitTestingInAction\Ch02\MyUtilTest.cs(22,0): at Ch02.MyUtilTest.LargestOf3()

结果与期望不一致,输入的是7、8、9,怎么会返回那么大的数字呢?看看代码,max变量的初始值为Int32.MaxValue,这就不对了,如果改成0的话就对了。此时测试确实是通过的。

上面我们想到了很多测试还没做呢,先考虑最大值的位置,这个跟结果应当是无关的。

再添加两个断言,代码还是写在刚才的测试用例中。

Assert.AreEqual(9, MyUtil.Largest(new int[] { 8, 9, 7 }));
Assert.AreEqual(9, MyUtil.Largest(new int[] { 9, 8, 7 }));
Assert.AreEqual(9, MyUtil.Largest(new int[] { 7, 8, 9 }));

现在该通过了,运行测试。

晕,又没通过:

TestCase 'Ch02.MyUtilTest.LargestOf3' failed: 
  Expected: 9
  But was:  8
    D:\myWorks\VS2008\Consoles\UnitTestingInAction\Ch02\MyUtilTest.cs(24,0): at Ch02.MyUtilTest.LargestOf3()

第三个断言失败,最大值是8,你的直觉是什么?是不是好像9根本没执行到呢?检查下for循环:

for (index = 0; index < array.Length - 1; index++)

通常我们会写index < array.Length,这里显然少循环了一次,这是个事故多发地带,这个错误被称为“off-by-one”错误,如果使用foreach语句就不存在这个问题了。

现在测试终于通过了,再看看重复最大值和单一元素的断言:

[Test]
public void TestDups()
{
    Assert.AreEqual(9, MyUtil.Largest(new int[] { 8, 9, 7, 9 }));
}
 
[Test]
public void TestOne()
{
    Assert.AreEqual(1, MyUtil.Largest(new int[] { 1 }));
}

至此一切正常,Yeah!等等,如果元素是负数呢?

Assert.AreEqual(-7, MyUtil.Largest(new int[] { -9, -8, -7 }));
TestCase 'Ch02.MyUtilTest.TestNegative' failed: 
  Expected: -7
  But was:  0
    D:\myWorks\VS2008\Consoles\UnitTestingInAction\Ch02\MyUtilTest.cs(42,0): at Ch02.MyUtilTest.TestNegative()

0是哪里来的?看来又是初始值的问题,0要大于负数,看来初始值必须是一个最小的整数,它就是Int32.MinValue,这样就可以了。

最后,如果array为空(长度为0)怎么办?这个时候最大值是无意义的,返回任何值都不合适,应当抛出一个异常,代码修改如下:

int index, max = Int32.MinValue;
if (array.Length == 0)
{
    throw new ArgumentException("largest: Empty array.");
}
 
for (index = 0; index < array.Length; index++)

注意,代码在设计上发生了改动,改善设计正是单元测试的好处之一。

为之编写测试用例:

[Test, ExpectedException(typeof(ArgumentException))]
public void TestEmpty()
{
    MyUtil.Largest(new int[] { });
}

注意,这个方法的特性中,除了Test,还多了个ExpectedException,与前面的用例不同,我们期望的正是异常

小结

本文通过一个简单的例子描述了单元测试的过程,从此我们也可以编写测试用例了,对其有了初步的认识。其中的过程有些繁琐,也许你会问,这么一个简单的方法值得花费这么大的力气吗?答案是肯定的,单元测试保证了程序在当前的质量,而在维护时会体现出更大的价值。

文中用到了NUnit和TestDriven.NET两个工具,其详细用法可以在园子里搜一下,在后续文章中我也不想再写相关内容了。从下一篇开始将逐步深入地讨论单元测试的实施过程。


本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2008/04/14/your-first-unit-test.html,如需转载请自行联系原作者。

目录
相关文章
|
1月前
|
机器学习/深度学习 PyTorch 算法框架/工具
目标检测实战(一):CIFAR10结合神经网络加载、训练、测试完整步骤
这篇文章介绍了如何使用PyTorch框架,结合CIFAR-10数据集,通过定义神经网络、损失函数和优化器,进行模型的训练和测试。
107 2
目标检测实战(一):CIFAR10结合神经网络加载、训练、测试完整步骤
|
14天前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
49 3
|
19天前
|
缓存 测试技术 Apache
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
35 1
|
1月前
|
机器学习/深度学习 编解码 监控
目标检测实战(六): 使用YOLOv8完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
这篇文章详细介绍了如何使用YOLOv8进行目标检测任务,包括环境搭建、数据准备、模型训练、验证测试以及模型转换等完整流程。
1594 1
目标检测实战(六): 使用YOLOv8完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
|
1月前
|
PyTorch 算法框架/工具 计算机视觉
目标检测实战(二):YoloV4-Tiny训练、测试、评估完整步骤
本文介绍了使用YOLOv4-Tiny进行目标检测的完整流程,包括模型介绍、代码下载、数据集处理、网络训练、预测和评估。
121 2
目标检测实战(二):YoloV4-Tiny训练、测试、评估完整步骤
|
1月前
|
Java 程序员 应用服务中间件
「测试线排查的一些经验-中篇」&& 调试日志实战
「测试线排查的一些经验-中篇」&& 调试日志实战
23 1
「测试线排查的一些经验-中篇」&& 调试日志实战
|
23天前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
42 2
|
24天前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
31 2
|
1月前
|
机器学习/深度学习 监控 计算机视觉
目标检测实战(八): 使用YOLOv7完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
本文介绍了如何使用YOLOv7进行目标检测,包括环境搭建、数据集准备、模型训练、验证、测试以及常见错误的解决方法。YOLOv7以其高效性能和准确率在目标检测领域受到关注,适用于自动驾驶、安防监控等场景。文中提供了源码和论文链接,以及详细的步骤说明,适合深度学习实践者参考。
372 0
目标检测实战(八): 使用YOLOv7完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
|
1月前
|
机器学习/深度学习 XML 并行计算
目标检测实战(七): 使用YOLOX完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
这篇文章介绍了如何使用YOLOX完成图像目标检测任务的完整流程,包括数据准备、模型训练、验证和测试。
178 0
目标检测实战(七): 使用YOLOX完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)
下一篇
无影云桌面