C# 单元测试(入门)

Wesley13
• 阅读 642

注:本文示例环境

VS2017
XUnit 2.2.0 单元测试框架
xunit.runner.visualstudio 2.2.0 测试运行工具
Moq 4.7.10 模拟框架

什么是单元测试?

确保软件应用程序按作者的期望执行操作,其中最好的一种方法是拥有自动化测试套件。 可以对软件应用程序进行各种不同的测试,包括集成测试、Web 测试、负载测试等。 测试各个软件组件或方法的单元测试是最低级测试。

所谓单元测试(unit testing),就是开发者编写的一小段代码,用于对软件中的最小单元进行检查和验证,其一般验证对象是一个函数或者一个类。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。

为什么要使用单元测试?

  • 大大节约了测试和修改的时间,有效且便于测试各种情况。
  • 能快速定位bug(每一个测试用例都是具有针对性)。
  • 能使开发人员重新审视需求和功能的设计(难以单元测试的代码,就需要重新设计)。
  • 强迫开发者以调用者而不是实现者的角度来设计代码,利于代码之间的解耦。
  • 自动化的单元测试能保证回归测试的有效执行。
  • 使代码可以放心修改和重构。
  • 测试用例,可作为开发文档使用(测试即文档)。
  • 测试用例永久保存,支持随时测试。

对于我个人来说,主要是防止自己犯低级错误的,同时也方便修改(BUG修复)而不引入新的问题。可以放心大胆的重构。简言之,这个简单有效的技术就是为了令代码变得更加完美。

既然单元测试有这些好处,为什么我们不去用呢?

可以归纳为以下几个理由。

  1. 对单元测试存在的误解,如:单元测试属于测试工作,应该由测试人员来完成,所以单元测试不属于开发人员的职责范围。

    答:虽然单元测试虽然叫做"测试",但实际属于开发范畴,应该由开发人员来做,而开发人员也能从中受益。

  2. 没有真正意识到单元测试的收益,认为写单元测试太费时,不值得。

    答:在开发时越早发现bug,就能节省更多的时间,降低更多的风险。单元测试先期要编写测试用例,是需要多耗费些时间,但是后面的调试、自测,都可以通过单元测试处理,不用手工一遍又一遍处理。实际上总时间被减少了。

  3. 项目经理或技术主管没有要求写单元测试,所以不用写。

    答:写单元测试应该成为开发人员的一种本能,开发本身就应该包含单元测试。

  4. 不知道有单元测试这回事,不知道如何用。经过这篇文档的说明,就基本知道如何处理单元测试。

框架选型

常用单元测试框架:MSTest (Visual Studio官方)、XUnit 和 NUnit。

  1. MS Test为微软产品,集成在Visual Studio 2008+工具中。
  2. NUnit为.Net开源测试框架(采用C#开发),广泛用于.Net平台的单元测试和回归测试中,官方网址(www.nunit.org)。
  3. XUnit.Net为NUnit的改进版。

(以下主要讲解MSTest 和NUnit的使用,XUnit操作和NUnit操作基本类似)

基础实践

开始创建你的第一个的单元测试项目吧

1)  我们先来用 VS2017 中自带的测试模块(MSTest)来写一个简单的单元测试吧。
  1. 新建一个Solution,并添加项目UnitTestDemo(用于编写被测试的项目)

  2. 在该工程中添加UnitTestClass类,并书写一个静态的GetTriangle(string[] sideArr) 函数用来返回一个三角形的类型。

    namespace UnitTest { public class UnitTestClass { ///

    /// 获取三角形类型. /// /// 三角形三边长度数组. /// 返回三角形类型名称. public static string GetTriangle(string[] sideArr) { string result = string.Empty; int a = int.Parse(sideArr[0]); int b = int.Parse(sideArr[1]); int c = int.Parse(sideArr[2]); if (a + b > c && a + c > b && b + c > a) { if (a == b && a == c) { result = "等边三角形"; } if (a == b || a == c || b == c) { result = "等腰三角形"; } else { result = "一般三角形"; } } else { result = "不构成三角形"; } return result; } } }

  3. 然后在solution中添加一个UnitTestDemoTests测试项目,如图所示,添加 => 新建项目之后选择 测试 => 单元测试项目。

C# 单元测试(入门)

新建好测试项目之后,你会得到一个UnitTest1测试类模板,即一个带有[TestClass] attribute标记的类和一个带有[TestMethod] attribute标记的空方法**public void TestMethod1()**。

  1. 单元测试项目无法自动访问它正在测试的类库。 可以通过添加对类库项目的引用来提供测试库访问权限。 为此,请右键单击UnitTestProject1项目,然后依次选择“添加” > “引用”。在“引用管理器”对话框中,然后选择 UnitTestDemo项目,如下图中所示。

C# 单元测试(入门)

在UnitTestDemoTests项目中添加UnitTestDemo项目的引用,现在我们的solution就具有了下图所示的目录结构。

C# 单元测试(入门)

  5. 在UnitTestDemoTests项目中的UnitTest1类中,将模板提供的样本单元测试代码替换为以下代码:

using UnitTest;
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestDemoTests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod()]
        public void GetTriangle_Test()
        {
            string[] sideArr = {"5", "5", "5"};
            Assert.AreEqual("等边三角形", UnitTestClass.GetTriangle(sideArr));
        }
    }
}

  6. 生成UnitTestDemoTests测试项目,在生成项目后,测试项将出现在测试资源管理器中。 如果测试资源管理器窗口不可见,请选择顶级 Visual Studio 菜单上的“测试”,然后依次选择“**窗口(Windows)”、“测试资源管理器(Ctrl + E,T9)**”,如图所示。

  C# 单元测试(入门)

  7. 在测试资源管理器上可以看到刚刚所写的测试方法,这样在GetTriangle_Test单击右键选择“运行所选定的测试”就可以在Test Explorer里看到单元测试的运行结果,如下图所示。  

  C# 单元测试(入门)

可以看到,我们在单元测试中提供的例子的期望是输出“等边三角形”,运行结果却是“等腰三角形”。再看一看 GetTriangle() 函数的代码,原来是在对在判断三边数值是等边三角形之后没有使用 else if 又用 if 判断为等腰三角形了。通过这个简单的单元测试就能够发现一些意向不到的错误。不要以为这里的bug很低级,类似的情况确实会在现实中发生。

  8. 把上面的错误更正后,再次运行TestMethod1()就会得到测试已通过的结果,如图所示。

  C# 单元测试(入门)

创建单元测试项目和测试方法,除了以上通过手动创建单元测试项目和根据你的要求进行编写测试用例之外,还可以从你的项目的方法上直接生成单元测试项目和单元测试存根,那样操作更加方便,速度也会更快一些。

2) 通过代码直接生成单元测试项目和单元测试存根
  1. 在代码编辑器窗口中,从上下文菜单右键单击并选择“创建单元测试”。

  C# 单元测试(入门)

  1. 在创建单元测试窗口,选择默认值,或更改用于创建并命名单元测试项目和单元测试的参数值。 单击“确定”,创建单元测试项目。

  C# 单元测试(入门)

这里涉及测试框架的选择,MSTest是VS自带的测试框架。新的MS TEST现在是通过Nuget的包发布了,目前MS发布了两个版本:

  • MS TEST V1:V1的版本依赖于一个包: MSTest.TestFramework
  • MS TEST V2:V2的版本依赖于两个包: MSTest.TestFrameworkMSTest.TestAdapter
    这两个版本使用起来还是大同小异的,MSTest v2 主要是为了**.net core准备的,当然也可以在.net framework**上运行,并且在v1上新加入了一些扩展。
  1. 在生成的测试项目中,将测试代码添加到对应单元测试方法中,以使单元测试有意义。
  2. 此后生成测试项目,并在测试资源管理器中运行测试方法,得到测试结果(与上方步骤一致)。

编写测试代码

你使用的单元测试框架和 Visual Studio IntelliSense 将指导你完成为代码项目的单元测试编写代码。 若要在测试资源管理器中运行,大多数框架要求你添加特定的属性来识别单元测试方法。 框架还提供了一种方法,通常通过断言语句或方法属性,来指示测试方法是否已通过或失败。 其他属性标识可选的安装方法,即在类初始化时和每个测试方法和每个拆卸方法之前的安装方法,这些拆卸方法在每个测试方法之后和类被销毁之前运行。

AAA(准备、执行、断言)模式是编写待测试方法的单元测试的常用方法:

  • 准备(Arrange),单元测试方法的准备部分初始化对象并设置传递给待测试方法的数据;
  • 执行(Act),执行部分调用具有准备参数的待测试方法;
  • 断言(Assert),断言部分验证待测试方法的执行行为与预期相同。
如示例中验证 UnitTest1.GetTriangle() 函数,我们编写了一个测试来验证方法的标准行为:
[TestMethod()]
public void GetTriangle_Test()
{
    // arrange  
    string[] sideArr = { "5", "5", "5" }; // 准备传给待测试方法的数据
    string expected = "等边三角形";

    // act  
    var actual = UnitTestClass.GetTriangle(sideArr); // 调用测试方法

    // assert  
    Assert.AreEqual(expected, actual); // 验证待测试方法的执行结果是否与预期相同
}
为单元测试设置超时值:

在某些情况下(例如通过网络获取数据),常常不希望程序卡住而占用太多时间,通过设置测试方法的超时时间,来测试一个方法是否在预期时间内执行。

[TestMethod()]
[Timeout(2000)] // 毫秒 要在单个测试方法上设置超时时间
public void GetTriangle_Test()
{   ... 
}
[TestMethod()]
[Timeout(TestTimeout.Infinite)] // 毫秒 将超时时间设置为允许的最大值
public void GetTriangle_Test()
{   ... 
}
MSTest参数化测试:

什么是参数化测试?

答:简单的说,就是同样的逻辑,根据输入参数不同给出不同的结果。因为只是参数不同,所以并不希望把测试方法写多遍,但是又希望对每个参数的测试成为一个独立的测试用例。举例说,假定我有一个数学计算的方法是把两个整数相加求和,我希望证明这个方法对于任意两个数都是通过的。

在MSTest中可以通过DataRow Attribute 来指定测试用例的参数,实现参数化测试:

/// <summary>
/// 相加(待测试方法)
/// </summary>
/// <param name="num1">数值1</param>
/// <param name="num2">数值2</param>
/// <returns>计算结果</returns>
public static int Add(int num1, int num2)
{
    return Math.Abs(num1 + num2);
}

/// <summary>
/// 测试方法
/// </summary>
[TestMethod()]
[DataRow(10, 20)]
[DataRow(-2, -5)]
[DataRow(1, -2)]
[DataRow(5, null)]
public void Add_Test(int num1, int num2)
{
    Assert.AreEqual(UnitTestClass.Add(num1, num2), num1 + num2);
}

测试了所有可能的情况,以达到更好的覆盖率。上方给出示例Add方法的单元测试运行测试结果如下图所示。

  C# 单元测试(入门)

测试结果:测试结果指出对两个数相加操作的方法,目标方法还取了绝对值,与相应结果不符。

测试调试

可以使用测试资源管理器为你的测试启动调试会话。 使用 Visual Studio 调试程序可以无缝地逐句得使你在单元测试和所测试项目之间来回反复。 若要开始调试:

  1. 在 Visual Studio 编辑器中,在想要调试的一个或多个测试方法中设置断点

  2. 在测试资源管理器中,选择测试方法,然后点击右键从快捷菜单选择“调试选定的测试”。

    C# 单元测试(入门)

  3. 进入调试模式

F5 继续。

F10 执行下一行代码,但不执行任何函数调用。

F11 在执行进入函数调用后,逐条语句执行代码。

Shift + F11 执行当前执行点所处函数的剩余行。

Shift + F5 停止运行程序中的当前应用程序。可用于“中断”模式和“运行”模式。

NUnit 测试框架

1)下载安装NUnit插件

  我们在VS中选择工具菜单栏下的扩展和更新,选择联机并在搜索框中输入NUnit。有2个版本的Nunit适配器,分别为NUnit 3.x(最新版为3.4.1)和NUnit 2.x(最新版为2.6.4),都支持Visual Studio 2012+。若想在VS2010中集成,需要安装NUnit 2.6.4安装包(可在官网下载)与VS2010 NUnit整合插件下载,下载安装完毕就能在 VS2010 的视图=>其他窗口中看到 Visual Nunit (或使用快捷键Ctrl + F7),打开该视图,将之拖到合适的位置。

2)创建NUnit单元测试项目

未完待续..

使用 Visual Studio 2017进行实时单元测试

Live Unit Testing 是 Visual Studio 2017 版本 15.3 中提供的一项技术,可在我们更改代码,然后保存的时候,它会自动生成自动测试,最后得出结果。

1)实时单元测试:

  • 让你更有信心地对代码进行重构和更改。 Live Unit Testing 在编辑代码时自动执行所有受影响的测试,确保所做更改不会中断测试。

  • 指示单元测试是否充分覆盖代码,并显示未被单元测试覆盖的代码。 Live Unit Testing 以图形方式实时描绘代码覆盖率,以便一眼就能看到每行代码覆盖的测试数,目和未被任何单元测试覆盖的行。

2)进行实时单元测试:

  1. 在类库项目中创建一个待测试方法,如下:

    ///

    /// 相加(待测试方法) /// /// /// /// public static int Add(int num1, int num2) { return num1 + num2; }

  2. 根据以上创建单元测试项目的过程,创建一个单元测试项目(测试框架可以使用 Live Unit Testing 的 MSTest 测试框架(默认)。 还可使用 xUnit 和 NUnit 测试框架)。在测试项目添加对被测试类库项目的引用来提供测试库访问权限。

3. 在测试项目类中,将模板提供的样本单元测试方法代码替换为以下代码:

[TestMethod()]
[DataRow(10, 20)]
[DataRow(-2, -5)]
[DataRow(1, -2)]
[DataRow(5, null)]
public void Add_Test(int num1, int num2)
{
    Assert.AreEqual(UnitTestClass.Add(num1, num2), num1 + num2);
}

  4. 从顶级 Visual Studio 菜单中依次选择“测试” > “Live Unit Testing” > “启动” Visual Studio 启动 Live Unit Testing,使其自动运行所有测试。

    C# 单元测试(入门)

  5.  完成运行测试后,“测试资源管理器” 显示整体结果和各个测试的结果。 此外,代码窗口以图形方式显示测试代码覆盖率和测试结果。 如下图所示,三项测试均已成功执行。 它还显示测试中已覆盖 Add() 方法中的所有代码路径,并已成功执行这些测试(用绿色复选标记“”指示)。 UnitTestClass.cs 中的其他方法有部分代码没有代码覆盖率(用蓝线“”指示)

  C# 单元测试(入门)

还可通过在代码窗口中选择一个特定的代码覆盖率图标来获得有关测试覆盖率和测试结果的更多详细信息。 若要查看此详细信息,请执行以下操作:

单击行上的绿色复选标记“”, 如下图所示,Live Unit Testing 指示只有一个测试覆盖该行的代码,并且都已成功执行。

C# 单元测试(入门)

Live Unit Testing 中“”标识的主要问题是代码覆盖率不完整,可以通过添加测试方法或改变测试参数,如下图,可以看到代码覆盖率已扩展到 GetTriangle() 的每一行代码。

在你修改源代码时,Live Unit Testing 将自动执行新增的和修改后的测试。

C# 单元测试(入门)

  1. 处理测试失败:

将 Add() 做些许修改,修改为计算两数相加的绝对值,在保存后,Live Unit Testing 指示 Add() 方法执行失败,如下图所示:

C# 单元测试(入门)

7. 停止实时单元测试:

  C# 单元测试(入门)

使用 IntelliTest 为你的代码生成单元测试

IntelliTest 浏览你的 .NET 代码,以生成测试数据和单元测试套件。 对于代码中的每个语句,将生成执行该语句的测试输入。 为代码中的每个条件分支执行案例分析。 例如,分析 if 语句、断言和可能引发异常的所有操作。 此分析用于为你的每个方法生成参数化单元测试的测试数据,从而创建具有较高代码覆盖率的单元测试。

当你运行 IntelliTest 时,你可轻松看到哪些测试会失败,并可添加任何必要的代码来修复它们。 你可选择要保存到测试项目中的已生成测试,以提供回归套件。 当你更改代码时,重新运行 IntelliTest,以使生成的测试与你的代码更改同步。

IntelliTest 仅可用于 C# 且不支持 x64 配置。

IntelliTest 入门

若要生成单元测试,你的类型必须是公共类。 否则,先创建单元测试,然后再生成它们。

  1. 在 Visual Studio 中打开解决方案。 然后打开包含你要测试的方法的类文件。

  2. 在代码中右键单击一种方法并选择“创建 IntelliTest”,为方法中的代码创建生成单元测试项目。

  C# 单元测试(入门)

  接受默认格式以生成测试,或更改项目和测试的命名方式。 你可以创建新的测试项目或将你的测试保存到现有项目。

  C# 单元测试(入门)

  3. 创建测试项目成功之后,选择上图中“运行 IntelliTest”,为方法中的代码运行IntelliTest单元测试项目。

IntelliTest 使用不同的输入多次运行你的代码。 每次运行都会在表中表示出来,显示输入测试数据以及产生的输出或异常。

  C# 单元测试(入门)

  要为一个类中的所有公共方法生成单元测试,只需右键单击类而不是特定的方法。 然后选择“运行 IntelliTest”。 使用“浏览结果”窗口中的下拉列表,显示类中每个方法的单元测试和输入数据。

  C# 单元测试(入门)

  对于通过的测试,检查结果列中报告的结果是否与你对代码的预期要求匹配。 对于失败的测试,根据需要修复你的代码。 然后重新运行 IntelliTest 来验证修复。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这