C# - 单元测试

2021-04-18 23:26

阅读:365

单元测试

集成测试是测试软件是否可以良好的运行在平台环境上,单元测试是测试软件各个独立的单元是否符合功能性需求。程序中最小的可测试单元通常就是一个方法。测试单元时可编写一个小程序,对单元进行测试。但完全手工编写小程序来进行测试会比较麻烦,推荐使用测试框架。常用的测试框架有MSTest,与vs无缝集成,MBUnit,功能强大,NUnit,最常用的测试框架,通过nuget获取。

使用NUnit 

在解决方案中添加Nuget包:NUnit 3 Test Adapter和NUnit

新建一个CUI控制台程序,创建一个计算器

namespace CUI
{
    public class Calculator
    {
        public int sum( int x, int y )
        {
            return x + y;
        }
    }

    class Program
    {
        static void Main( string[] args )
        {

        }
    }
}

创建一个类库项目,命名为MyUnitTests。添加一个类文件

using NUnit.Framework;
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就是一系列的断言,判定并得出符合或不符合预期的结果。

[TestFixture] 
//声明这是一个运行单元测试时将要执行的类型
[Test] 
//声明这是一个运行单元测试时将要执行的方法,可设置重复执行多少次,如:[Test, Repeat( 100 )]
[TestCase( param1 , param2 , …… )]  
//声明这是一个运行单元测试时将要执行的方法,param是被执行的方法的参数 如:[TestCase(1,2)] public void sum( int x, int y )
[SetUp] 
//测试执行前要执行的方法
[TearDown]
//测试执行后要执行的方法
[Ignore]
//排除测试,可应用于类或方法

NUnit类型

Assert
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
//未验证该测试
CollectionAssert类
AllItemsAreInstancesOfType( IEnumerable collection, Type expectedType )
//集合中的各项是否是参数类型的实例

AllItemsAreNotNull()
//集合中的各项均不为空

AllItemsAreUnique()
//集合中的各项是唯一的

IsEmpty( )
//集合为空

IsNotEmpty( )
//集合不为空

IsOrdered( )
//集合的各项已经排序

AreEqual( )
//两个集合长度相等、顺序一样且引用相等,类似的有AreNotEqual()

AreEquivalent( )
//两个集合长度相等且引用相等,类似的有AreNotEquivalent()

DoesNotContain()
//集合中不包含参数对象

IsSubsetOf()
//一个集合是另外一个集合的子集

IsNotSubsetOf()
//一个集合不是另外一个集合的子集

Fake测试(伪对象)

假如你要测试某个类型的某个方法,而初始化该类型时需要一个接口的依赖,可能其他方法需要该接口,但你想测试的那个方法却并不需要使用类型所依赖的接口,但构造函数又需要你注入一个接口,此时可以自行创建一个接口实例,注入到构造函数中,这样可以调用类型的方法以便测试。

public abstract class Servise
{
    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;
    }
}
namespace MyUnitTests
{
    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 NUnit.Framework;
using CUI;
using NMock;


namespace MyUnitTests
{
    [TestFixture]
    public class MyTests
    {
         [Test] 
        public void sum()
        {
            MockFactory mockFactory = new MockFactory( ); //创建工厂实例
            MockService> mock = mockFactory.CreateMockService>( ); //创建模拟对象
            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 );
            //设置模拟对象的方法
            //参考上面
        }       
    }
}

 

参考资料 

单元测试的艺术

Unit断言

 

C# - 学习总目录


评论


亲,登录后才可以留言!