单元测试(unittesting),是指对软件中的最小可测试单元(函数/模块/类)进行检查和验证。
单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
单元测试从长期来看,可以提高代码质量,减少维护成本,降低重构难度。但是从短期来看,加大了工作量,对于进度紧张的项目中的开发人员来说,可能会成为不少的负担。
单元测试应该遵循以下原则:
可靠性、可维护性、可读性;
尽量避免测试中的逻辑,一个单元测试应该是一系列的方法调用和断言;
避免重复代码;
测试隔离,低耦合,防止不同测试之间的互相影响。
主流C#单元测试工具
我们调研了以下开源C#单元测试工具(开源工具数据来自于GitHub):
测评指标
对C#单元测试工具进行测评主要从功能性及非功能性两部分来进行。其中,功能性测评中包括是否支持测试用例分类、排序等;非功能测评点包括社区活跃度及文档完备性等。
测评环境
工具简介
MsTest
框架介绍
(1)基本介绍
MSTest是一款由微软公司开发的单元测试框架,它能够很好地被应用在VisualStudio中,并且集成在了VisualStudio单元测试框架中,操作简单,上手容易。
从使用的角度来看,如果用户使用的是VisualStudio作为IDE,那么MSTest在对它的集成方面无疑是最方便的,无需下载,无需安装,内置在vs的测试框架模板中。在VS中使用MsTest生成测试项目和新建一个C#项目一样方便;如果用户不使用VS,那么也可以通过命令行执行.exe文件来执行单元测试,但是MsTest不提供自己单独的GUI界面。
MsTest中核心的概念有TestClass(测试类)、TestMethod测试方法、断言和初始化及清理方法。
断言:断言是一段代码,当运行于测试一个条件或行为针对预期的结果。通过调用Assert类中的方法来执行。
许多时候会把VsTest和MsTest混淆,其实这两个概念之间还是有一定区别的。
VsTest是VisualStudio测试平台(VisualStudioTestPlatform)的简称,是一个开放且可扩展的测试平台,可用于运行测试,收集诊断数据和报告结果。VsTest支持运行在各种测试框架中并使用可插拔适配器模型编写的测试。根据用户的选择,所需的测试框架及其相应的适配器可以视情况以vsix或NuGet软件包的形式获取。可以使用测试平台公开的公共API来编写适配器。
MsTest是指微软开发的单元测试框架。目前MsTest最新的版本是MsTestV2,V2的版本依赖于两个包:MSTest.TestFramework和MSTest.TestAdapter,用户在使用时可以通过Nuget下载这两个包来使用MsTest进行单元测试。MsTestV2主要是为了.netcore准备的,也可以在.netframework上运行,并且较V1版本新加入了一些扩展。
下图是VsTest的总体架构图,从图中可以看到,整个VsTest架构体系主要有四个组件:测试运行器、测试执行器、数据收集器和IDE。
其中,测试执行器部分主要包含一个执行测试的引擎,它把发现和执行测试的责任委托给了一些可扩展的测试适配器,允许测试平台基于第三方测试框架发现/执行测试。这将通过相应框架的适配器来完成,该适配器了解如何在该框架中定义测试,并可以运行它们以向测试平台提供结果。例如,MsTest适配器了解MsTest框架中编写的测试,VsTest中就会使用MsTest适配器发现并执行它们。
在图中还可以看到,测试运行器部分还包含一个VsTestconsole,其作用是使用命令行运行不同框架的测试。但是如果是采用MsTest作为测试框架,也可以使用MsTest.exe作为命令行执行的工具。
(2)工具特点
支持为测试用例设置分类,执行时执行指定分类的测试方法:【TestCategory】
支持为测试用例设置参数:[DataRow】
提供断言方法,判定期望值和实际值是否一致
支持使用断言等方式,对返回异常的测试用例进行异常判断:[ExpectedException]
图2-1MSTest中带参及异常判断实例
支持通过注解等方式跳过执行带有该注解的测试用例:[Ignore]
图2-2MSTest中timeout属性实例
NUnit
(1)基本介绍
NUnit是专门针对于.NET的自动化单元测试框架,是XUnit家族的一个成员,最初是由Java的单元测试框架JUnit而来,作者最终用C#对其进行重新编写,NUnit完全由C#编写,使其更加符合C#习惯,并充分利用了.NET中反射、客户属性等特性。因此,该工具具有丰富的单元测试历史的同时,也具有适当的C#风格。
由于其独立的历史,NUnit还具有与其他工具良好交互的特点,并且在支持多种平台,包括:.NETCore、XamarinMobile、CompactFramework及Silverlight等。NUnit在快速测试运行方面也享有盛誉,并且还具有一些不错的附加功能,例如允指定给定测试的多个输入等。
NUnit采用分层体系架构,主要有三层:测试运行器层(TestRunner)、测试引擎层(TestEngine)和框架层(Framework),其中,TestRunner层主要包含各种运行程序,包括独立程序和在其它程序下运行的任务或者插件;TestEngine层则是NUnit平台的核心,它提供公共API,供希望查找,加载和运行测试并显示测试结果的应用程序使用;Framework层主要是为了兼容各个版本的NUnit程序。
NUnit中主要有三个抽象类:TestFixtureBuilderAttribute、TestCaseBuilderAttribute和IncludeExcludeAttribute。
TestFixtureBuilderAttribute是任何知道如何从所提供的类中构建某种测试fixture的属性的基类,testfixture是指基于用户类的任何测试。
TestCaseBuilderAttribute是任何知道如何从给定方法构建测试用例的属性的基类。测试用例可以是简单的(没有参数)也可以是参数化的(接受参数),并且总是基于MethodInfo。
IncludeExcludeAttribute是任何用于根据字符串属性include、exclude和Reason来决定是在当前运行中包含测试还是排除的属性的基类,抽象类是使这些属性可供派生类使用,派生类负责对它们采取操作。
在使用方面,NUnit可以通过控制台或自己独立的GUI来运行,在使用VisualStudio作为IDE时,NUnit也提供了相应的适配器,可以更好地和VisualStudio搭配使用。
(2)工具特点
NUnit2中有包含GUI界面;
支持为测试用例设置分类,执行时执行指定分类的测试方法:使用[Category】属性;
提供断言方法,如果断言失败,则方法调用不会有值返回并报告错误。如果一个测试包含多个断言,那么在某次断言失败之后就终止,其后的任何断言都不会执行;
支持为测试用例设置参数:【TestCase]
图2-3NUnit带参测试方法实例
可以指定测试用例的执行顺序:【Order】
支持使用断言等方式,对返回异常的测试用例进行异常判断:Assert.That
图2-4NUnit排序及异常判断方法实例
支持通过注解等方式跳过执行带有该注解的测试用例:[Ignore(“Methodisignored”)】
TestSuite:UNIT3之后取消该属性,因为namespace也可以实现相同的功能。
支持对接主流的代码覆盖率工具,执行完单元测试用例后自动生成覆盖率报告。(ncover)
XUnit.Net
XUnit.NET是一个开源的的单元测试工具,由NUnitv2的原始发明者编写,支持C#,F#,VB.NET版以及其他.NET语言,由.NET基金会支持,它采用了一种非常独特、现代和灵活的单元测试方法。
XUnit.NET强调编写具有较高的可读性,简单性的单元测试,与其它单元测试框架相比,有一些独特的地方:
XUnit比其他.Net单元测试框架更加灵活和可扩展,它允许创建新的属性来控制测试。XUnit支持两种类型的测试,[Fact】和【Theory】,[Fact】通常用来测试不需要参数的方法,并且在XUnit中,用【Skip】属性代替了[Ignore],并要求指定跳过该测试的原因;【Theory】支持数据驱动的测试,可以用[InlineData]属性实现参数的传递,并支持多次执行同一个方法,是XUnit可扩展性强的一个重要体现。
图2-5XUnit.NET带参测试方法实例
XUnit支持更好地进行隔离测试。与其它测试框架不同,在xUnit中,每个测试方法运行后都会进行实例化操作,执行后释放相应的空间,测试之间更加独立,可以以任何顺序执行测试,而不必担心一个测试对其他测试的影响,消除了不同测试方法之间的依赖性。
XUnit取消了【SetUp】和【TearDown]方法,而采用构造函数进行初始化,使用IDisposable进行测试类的后处理等操作,让每个测试对其需要的内容进行初始化。
支持为测试用例设置分类,执行时执行指定分类的测试方法:[Trait("Category","UI")];
支持为测试用例设置参数:[Theory][InilineData];
支持使用断言等方式,对返回异常的测试用例进行异常判断:Assert.Throws.Exception,长期使用[ExpectedException]会发现各种问题。首先,它没有具体说明应该在哪一行代码中引发异常,这会导致微妙且难以跟踪的失败,这些失败会在通过测试时显示出来。其次,由于处理不在测试的常规代码流程之内,因此它没有提供机会全面检查异常本身的详细信息。Assert.Throws允许您测试一组特定的代码以引发异常,并在成功期间返回异常,以便您可以针对异常实例本身编写进一步的断言。
图2-6XUnit.NET异常判断实例
支持通过注解等方式跳过执行带有该注解的测试用例,[Fact(Skip=“reason”)】
工具试用
本节采用一段简易货币转换计算器代码作为被测代码来试用三款工具,其测试用例基本可以涵盖日常单元测试所需的功能。在试用过程中,同时采用了Moq框架及dotCover覆盖率扫描工具。
被测代码——简易货币转换计算器,主要有两个接口:ICaculator和IMoneyEx,和一个ICaculator接口的实现类:Caculator。其功能是实现简单运算及货币汇率转换功能。
ICaculator接口定义如下:
publicinterfaceICalculator
{
intAdd(intparam1,intparam2);
intSubtract(intparam1,intparam2);
intMultipy(intparam1,intparam2);
doubleDivide(intparam1,intparam2);
intConvertUSDtoRMB(intunit);
}
IMoneyEx接口定义如下:
publicinterfaceIMoneyEx
intGetActualUSDValue();
ICaculator接口的实现类Caculator如下:
publicclassCalculator:ICalculator
privateIMoneyEx_feed;
publicCalculator(IMoneyExfeed)
this._feed=feed;
#regionICalculatorMembers
publicintAdd(intparam1,intparam2)
returnparam1+param2;
publicintSubtract(intparam1,intparam2)
returnparam1-param2;
publicintMultipy(intparam1,intparam2)
returnparam1*param2;
publicdoubleDivide(intparam1,intparam2)
returnparam1/param2;
publicintConvertUSDtoRMB(intunit)
returnunit*this._feed.GetActualUSDValue();
#endregion
测试代码
Mock代码
//定义mock逻辑
privateIMoneyExPrvGetMockExchangeRateFeed()
MockmockObject=newMock();
mockObject.Setup(m=>m.GetActualUSDValue()).Returns(500);
returnmockObject.Object;
不同测试工具测试代码
(1)MsTest
namespaceCalculatorPkg.Tests
privatestaticICalculatorcalculator=null;
[TestClass()]
publicclassMsTestCal
//初始化
[TestInitialize]
publicvoidSetup()
IMoneyExfeed=this.PrvGetMockExchangeRateFeed();
calculator=newCalculator(feed);
Console.WriteLine("beforetest...");
//带参测试方法,并对测试方法分类
[TestMethod()]
[DataRow(1,1)]
[TestCategory("cal")]
publicvoidTC1_Add(inta,intb)
intc=a+b;
Assert.AreEqual(c,calculator.Add(a,b));
//忽略测试用例
[Ignore]
publicvoidTC1_Divide9By3()
doubleactualResult=calculator.Divide(9,3);
intexpectedResult=3;
Assert.AreEqual(expectedResult,actualResult);
//异常判断
[ExpectedException(typeof(DivideByZeroException))]
publicvoidTC2_DivideByZero()
doubleactualResult=calculator.Divide(9,0);
//使用mock类
[TestCategory("convert")]
publicvoidTC3_ConvertUSDtoRMBTest()
intactualResult=calculator.ConvertUSDtoRMB(1);
intexpectedResult=500;
//后处理
[TestCleanup]
publicvoidCleanup()
Console.WriteLine("aftertest...");
DotCover结果显示:
(2)Nunit
//添加TestFixture标识类是测试类
[TestFixture]
publicclassNUnitTest
[SetUp]
//测试用例分类,跳过测试方法
[Test(Description="Add1with1.Expectedresultis2.")]
[Category("add")]
[Ignore("Methodisignored")]
publicvoidTC0_Add1With1()
intactualResult=calculator.Add(1,1);
intexpectedResult=2;
//带参测试方法
[Test()]
[Category("sub")]
[TestCase(0,1)]
[TestCase(1,1)]
[TestCase(2,1)]
publicvoidTC0_SUB(inta,intb)
intactualResult=calculator.Subtract(a,b);
intexpectedResult=a-b;
//测试divide方法,以及测试用例排序
[Test(Description="Divide9by3.Expectedresultis3.")]
[Category("divide")]
[Order(2)]
Console.WriteLine("TC1_Divide9By3:"+DateTime.Now.TimeOfDay.ToString());
[Test(Description="Divideanynumberbyzero.ShouldthrowanSystem.DivideByZeroExceptionexception.")]
[Order(3)]
Console.WriteLine("TC2_DivideByZero:"+DateTime.Now.TimeOfDay.ToString());
Assert.That(()=>calculator.Divide(9,0),Throws.TypeOf());
//mock示例
[Test(Description="Convert1USDtoRMB.Expectedresultis500.")]
[Category("convert")]
[Order(1)]
[TearDown]
publicvoidTeardown()
执行顺序结果:
图NUnit测试用例执行顺序验证
即执行情况符合规定的顺序。
(3)XUnit.NET
publicclassXUnitCal
[Fact(Skip="Methodisignored")]
[Trait("Category","add")]
ICalculatorcalculator=newCalculator(feed);
Assert.Equal(expectedResult,actualResult);
[Theory()]
[Trait("Category","multi")]
[InlineData(0,1)]
[InlineData(1,1)]
publicvoidTC0_multi(inta,intb)
intactualResult=calculator.Multipy(a,b);
intexpectedResult=a*b;
[Fact()]
[Trait("Category","divide")]
//doubleactualResult=calculator.Divide(9,0);
Assert.Throws(()=>calculator.Divide(9,0));
[Trait("Category","convert")]
工具对比
本次调研重点研究的三款C#单元测试工具(MsTest、NUnit、XUnit·NET)来说,使用区别度并不是很大,具体如下:
MsTest作为内置的visualstudio测试工具来讲,操作简单,易于使用;另外,如果已经使用visualstudio作为编译器,不用做任何的安装即可使用,也是其较为明显的优点之一。但是其也存在在带参测试时不能同时支持异常判断,以及无法对测试用例排序等缺点。
NUnit作为比较成熟的C#单元测试工具,好处包括可以按名称空间进行测试分组,添加测试用例注释(使用相同的参数多次运行相同的测试)及对测试用例排序等功能,并且它与Opencover和ReportGenerator进行覆盖分析的效果很好。主要的缺点是它没有像MSTest那样集成,但是通过Nuget现在在visualstudio中使用NUnit已经成为一件比较容易的事情。NUnit还有一个不同于其它测试工具的特点是NUnit2中有自己的GUI,可以不通过VS单元来看测试结果,但是如上所说,GUI只在NUnit2中提供,而现在普遍使用的都是NUnit3。
XUnit.NET作为NUnit的进阶简化版,是一种和NUnit极其相似的简单现代的单元测试框架。XUnit.NET不同于其它测试工具的特点主要有两个:一是取消了单元测试框架中的前后处理方法,为每个测试方法都创建测试类的新实例,即提高了测试用例之间的隔离性;二是用断言替代了属性的方式来捕捉异常,不采用Attribute的方式来捕捉异常有两方面的好处:在代码中直接断言(Assert)能捕捉到更多种类的异常;遵守Arrange-Act-Assert(or“3A”)模式:即测试命名上“范围-作用-断言”规范。但是正是由于其简化的特性,其缺点也显而易见,即一些高阶的测试可能无法用XUnit完成,并且和NUnit一样,XUnit与VS的集成没有MsTest那么自然。
总结
本次调研重点测评了三款C#开源单元测试工具,对其特性及基本使用进行了介绍。其实在开源社区蓬勃发展的今天,众多开源工具之间的区别也通过很多次迭代渐渐在缩小,如本次重点调研的三款工具虽各有优势和劣势,但是差异已经不再那么明显,大家在使用时结合自身的业务背景来选择合适的工具即可。
【重点来了】!!请不要忘记时刻学习着~~这点我相信大家应该都能理解,所以不用写什么:除非你是五年、十年工作经验的测试人员,仅此一点“不学习就会落后”!
现在我邀请你进入我们的软件测试学习交流群,备注“入群”,大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路。