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

简介:

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

为了验证代码行为是否与我们所期望的一致,就需要使用断言(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,如需转载请自行联系原作者。

目录
相关文章
|
10月前
|
测试技术 持续交付 UED
软件测试的艺术:确保质量的实战策略
在软件开发的舞台上,测试是那把确保每个功能如交响乐般和谐奏响的指挥棒。本文将深入探讨软件测试的重要性、基本类型以及如何设计高效的测试策略。我们将通过一个实际的代码示例,展示如何运用这些策略来提升软件质量和用户体验。
|
11天前
|
存储 关系型数据库 测试技术
玩转n8n测试自动化:核心节点详解与测试实战指南
n8n中节点是自动化测试的核心,涵盖触发器、数据操作、逻辑控制和工具节点。通过组合节点,测试工程师可构建高效、智能的测试流程,提升测试自动化能力。
|
1月前
|
Web App开发 人工智能 JavaScript
主流自动化测试框架的技术解析与实战指南
本内容深入解析主流测试框架Playwright、Selenium与Cypress的核心架构与适用场景,对比其在SPA测试、CI/CD、跨浏览器兼容性等方面的表现。同时探讨Playwright在AI增强测试、录制回放、企业部署等领域的实战优势,以及Selenium在老旧系统和IE兼容性中的坚守场景。结合六大典型场景,提供技术选型决策指南,并展望AI赋能下的未来测试体系。
|
1月前
|
存储 人工智能 算法
AI测试平台实战:深入解析自动化评分和多模型对比评测
在AI技术迅猛发展的今天,测试工程师面临着如何高效评估大模型性能的全新挑战。本文将深入探讨AI测试平台中自动化评分与多模型对比评测的关键技术与实践方法,为测试工程师提供可落地的解决方案。
|
1月前
|
人工智能 缓存 测试技术
Playwright进阶指南 (6) | 自动化测试实战
2025企业级测试解决方案全面解析:从单元测试到千级并发,构建高可用测试体系。结合Playwright智能工具,解决传统测试维护成本高、环境依赖强、执行效率低等痛点,提升测试成功率,内容从测试架构设计、电商系统实战框架、高级测试策略、Docker化部署、CI/CD集成及AI测试应用,助力测试工程师掌握前沿技术,打造高效稳定的测试流程。
Playwright进阶指南 (6) | 自动化测试实战
|
17天前
|
人工智能 数据可视化 测试技术
AI 时代 API 自动化测试实战:Postman 断言的核心技巧与实战应用
AI 时代 API 自动化测试实战:Postman 断言的核心技巧与实战应用
240 11
|
1月前
|
资源调度 前端开发 JavaScript
Jest 测试实战指南
本文系统讲解如何使用 Jest 进行高效的 JavaScript 函数测试,涵盖环境搭建、测试用例编写、模拟函数与快照测试等内容,帮助开发者提升代码质量与测试效率。
|
5月前
|
监控 测试技术 数据库连接
RunnerGo API 性能测试实战:从问题到解决的全链路剖析
API性能测试是保障软件系统稳定性与用户体验的关键环节。本文详细探讨了使用RunnerGo全栈测试平台进行API性能测试的全流程,涵盖测试计划创建、场景设计、执行分析及优化改进。通过电商平台促销活动的实际案例,展示了如何设置测试目标、选择压测模式并分析结果。针对发现的性能瓶颈,提出了代码优化、数据库调优、服务器资源配置和缓存策略等解决方案。最终,系统性能显著提升,满足高并发需求。持续关注与优化API性能,对系统稳定运行至关重要。
|
1月前
|
人工智能 缓存 监控
大模型性能测试实战指南:从原理到落地的全链路解析
本文系统解析大模型性能测试的核心方法,涵盖流式响应原理、五大关键指标(首Token延迟、吐字率等)及测试策略,提供基于Locust的压测实战方案,并深入性能瓶颈分析与优化技巧。针对多模态新挑战,探讨混合输入测试与资源优化
|
3月前
|
Java 测试技术 容器
Jmeter工具使用:HTTP接口性能测试实战
希望这篇文章能够帮助你初步理解如何使用JMeter进行HTTP接口性能测试,有兴趣的话,你可以研究更多关于JMeter的内容。记住,只有理解并掌握了这些工具,你才能充分利用它们发挥其应有的价值。+
742 23

热门文章

最新文章