单元测试
集成测试是测试软件是否可以良好的运行在平台环境上,单元测试是测试软件各个独立的单元是否符合功能性需求。程序中最小的可测试单元通常就是一个方法。测试单元时可编写一个小程序,对单元进行测试。但完全手工编写小程序来进行测试会比较麻烦,推荐使用测试框架。常用的测试框架有MSTest,与vs无缝集成,MBUnit,功能强大,NUnit,最常用的测试框架,通过nuget获取。
使用NUnit
在解决方案中添加Nuget包:NUnit 3 Test Adapter和NUnit
新建一个CUI控制台程序,创建一个计算器
{
public class Calculator
{
public int sum( int x, int y )
{
return x + y;
}
}
class Program
{
static void Main( string[] args )
{
}
}
}
创建一个类库项目,命名为MyUnitTests。添加一个类文件
using CUI;
namespace MyUnitTests
{
[TestFixture] //声明这是一个运行单元测试时将要执行的类型
public class MylTests
{
[Test] //声明这是一个运行单元测试时将要执行的方法
public void sum( )
{
//在方法中将要测试以下逻辑
Calculator calculator = new Calculator( );
int result=calculator.sum( 1, 2 );
Assert.AreEqual( 3, result ); //测试控制台中的Calculator类型的sum方法返回的计算结果是否符合预期
}
}
}
在visual studio菜单中选择测试 - 窗口 - 测试资源管理器
可以选择全部运行(所有在MyUnitTests类库中被描述为TestFixture的类型中的被描述为Test的方法都会得到执行),或选择运行,具体可参考菜单提供的功能。点击全部运行或运行,单元测试将自动执行,如果测试未通过你编写的测试逻辑,则会显示红色。
将Assert.AreEqual方法的第一个参数改为5,测试不会通过
NUnit特性
利用Nunit提供的几个类型所提供的方法,可以在任何标记为Test的方法中编写你需要的测试逻辑。Nunit所提供的API就是一系列的断言,判定并得出符合或不符合预期的结果。
//声明这是一个运行单元测试时将要执行的类型
[Test]
//声明这是一个运行单元测试时将要执行的方法,可设置重复执行多少次,如:[Test, Repeat( 100 )]
[TestCase( param1 , param2 , …… )]
//声明这是一个运行单元测试时将要执行的方法,param是被执行的方法的参数 如:[TestCase(1,2)] public void sum( int x, int y )
[SetUp]
//测试执行前要执行的方法
[TearDown]
//测试执行后要执行的方法
[Ignore]
//排除测试,可应用于类或方法
NUnit类型
AreEqual()
//值相等,类似的有AreNotEqual()
AreSame()
//引用相等,类似的有AreNotSame()
Contains(object object,ICollection collection)
//参数2是否包含参数1
IsTrue()
//是否真,类似的有IsFalse()
IsNull()
//是否空引用,类似的有IsNotNull()
IsEmpty()
//是否空字符,类似的有IsNotEmpty()
Greater()
//大于,类似的有GreaterOrEqual()
Less()
//小于,类似的有LessOrEqual()
IsInstanceOfType()
//是参数类型的实例,类似的有IsNotInstanceOfType()
Pass()
//强行让测试通过
Fail()
//强行让测试失败
Ignore()
//忽略该测试方法,不执行它
Inconclusive
//未验证该测试
AllItemsAreInstancesOfType( IEnumerable collection, Type expectedType )
//集合中的各项是否是参数类型的实例
AllItemsAreNotNull()
//集合中的各项均不为空
AllItemsAreUnique()
//集合中的各项是唯一的
IsEmpty( )
//集合为空
IsNotEmpty( )
//集合不为空
IsOrdered( )
//集合的各项已经排序
AreEqual( )
//两个集合长度相等、顺序一样且引用相等,类似的有AreNotEqual()
AreEquivalent( )
//两个集合长度相等且引用相等,类似的有AreNotEquivalent()
DoesNotContain()
//集合中不包含参数对象
IsSubsetOf()
//一个集合是另外一个集合的子集
IsNotSubsetOf()
//一个集合不是另外一个集合的子集
Fake测试(伪对象)
假如你要测试某个类型的某个方法,而初始化该类型时需要一个接口的依赖,可能其他方法需要该接口,但你想测试的那个方法却并不需要使用类型所依赖的接口,但构造函数又需要你注入一个接口,此时可以自行创建一个接口实例,注入到构造函数中,这样可以调用类型的方法以便测试。
{
public abstract double Getπ( );
}
public class Calculator
{
private Servise Servise;
public Calculator(Servise servise)
{
this.Servise = servise;
}
//只想断言此方法
public int sum( int x, int y )
{
return x + y;
}
//并不想断言此方法
public double GetCylinVolume( double r, double h )
{
return Servise.Getπ() * r * r * h;
}
}
{
public class MyServis : Servise
{
public override double Getπ( )
{
return 1.2; //随便return一个值,反正用不上
}
}
[TestFixture] //声明这是一个运行单元测试时将要执行的类型
public class MyTests
{
[TestCase(1,2)]
public void sum(int x,int y)
{
Calculator calculator = new Calculator( new MyServis()); //注入
int result=calculator.sum( x,y );
Assert.AreSame( 3, result );
}
}
}
Mock测试(模拟对象)
如果被测试的某个类型需要依赖,而你又不可能马上去创建一个依赖的实例,因为需要手动实现接口的所有成员,需要一堆逻辑,此时可以使用NMock的模拟框架,NMock利用反射动态生成类型所依赖的接口的实例。
在解决方案中添加Nuget包:NMock,如果测试时提示错误,把MSTest的nuget包一并添加到解决方案中。
public abstract class Service
{
public abstract double GetCircleArea( double r );
}
public class Calculator
{
private Service Servise; //依赖
public Calculator( Service servise )
{
this.Servise = servise;
}
public double GetCircleArea( double r )
{
return Servise.GetCircleArea( r );
}
}
using CUI;
using NMock;
namespace MyUnitTests
{
[TestFixture]
public class MyTests
{
[Test]
public void sum()
{
MockFactory mockFactory = new MockFactory( ); //创建工厂实例
Mock<Service> mock = mockFactory.CreateMock<Service>( ); //创建模拟对象
double CircleR = 3;
double returnValue = 3 * CircleR * CircleR;
mock.Expects.One.Method( d => d.GetCircleArea( 0/*形参*/ ) ).With( CircleR/*实参*/ ).Will( Return.Value( returnValue /*返回值*/ ) ); //实现模拟对象的方法
Calculator calculator = new Calculator( mock.MockObject); //注入Mock
Assert.AreEqual( 27, calculator.GetCircleArea( CircleR ) );
//设置模拟对象的属性
mock.Expects.One.GetProperty( p => p.ID, 1 );
//设置模拟对象的事件
mock.Expects.One.EventBinding( p => p.Click+=null );
//设置模拟对象的方法
//参考上面
}
}
}
参考资料