NancyForever

单元测试分享

首先介绍一下什么是单元测试:

那么,如何检测代码的功能逻辑是否正确呢?
调试,是临时的,且不完整的,例如,一个函数有十多种输入,调试不可能全部覆盖,能五六种就不错了。而系统测试,并不针对某个具体的函数,不关注某个函数的功能逻辑是否正确。
要检测某个函数的功能逻辑,就必须要依照分类列出数据,检测代码是否对每一个分类都做了处理,而且每一个分类的处理是否正确。
——这就是单元测试。

单元测试的基本方法 由上面的分析可以看出,单元测试的基本方法就是:依数据的分类列出输入,执行被测试程序,然后,判断输出是否符合预期。

单元测试能达到什么样的效果呢?那就是:无论别人怎么样,我总是对的!
这里的“别人”,是指关联代码。“我”,是指当前正在编写或测试的代码。单元测试要做到的是,无论关联代码是否有错,都要保证我是对的。具体来说,我要考虑关联代码会产生什么样的数据,这些数据要如何分类处理,只要我的分类和处理是正确的,那么,无论别人怎么样,我总是对的。

 单元测试的优点:

  1.   验证行为------验证程序的正确性
  2.   设计行为
  3.   文档行为------通过看单元测试,能够知道函数实现的什么功能,传入的参数情况,以及异常情况的处

单元测试的技巧:

单元测试主要关注点就是程序出错的地方: 空、边界 和异常

当对象为空会怎样? 设计容器操作时,算法是否会超过边界? 出错了是否抛出了指定异常。

单元测试编写方法:(配置式编程)

 编写单元测试时,最好能够批量进行测试,将测试数据与测试代码分离。当心增加测试用例时,只要新增加测试数据集即可。

单元测试的原则:

 

1.测试代码和被测代码是同等重要的,需要被同时维护

     测试代码不是附属品

    不但要重构代码,也要重构单元测试

2. 单元测试一定是隔离的

     一个测试用例的运行结果不能影响其他测试用例

    测试用例不能相互依赖,应该能够以任何次序执行

3. 单元测试一定是可以重复执行的

   不能依赖环境的变化(依赖时间,依赖环境变量)

   保持单元测试的简单性和可读性(维护成本高)

4. 尽量对接口进行测试

5. 单元测试应该可以迅速执行

     使用Mock 对象对数据库,网络的依赖性进行解耦

6. 自动化单元测试

     持续集成

 单元测试位置:

 

 1. 和被测代码放到一起

       方便查找,但是会造成扰乱

2. 放到单独的目录下,但是保证和被测代码位使用同样的包名

     方便查找

     可以直接访问包级的变量和方法

3. 最好创建一个单元测试的project,让它依赖被测试的project

 测试用例的组织和命名:

  1. 对每个project 创建一个单元测试的project
  2. 对每个单元测试project

           创建一个AllTest.cs(根据语言不同后缀不同) ,它包含了所有的package级别的测试用例

        3.  对每个 package

             创建一个《packageName》AllTest.cs,它包含了同一个package下所有的测试用例

        4.  对每个要测试的类

              提供一个或者多个xxTest的测试用例

单元测试框架

在写单元测试之前首先需要选一个单元测试框架,不同的语言都有对应的单元测试框架,比如Java--》junit, c# ---》xunit, python--》pyUnit 等等。

当然我可以用python 做Java语言写的程序的单元测试,我们一般都是用对应语言做对应程序的单元测试。

下面我举的例子都以xunit为例子。

我们执行程序时,都有个入口函数,比如 main函数,在单元测试中也有,xunit 中有[Fact]和[Theory] 两种。 对结果的预期 都是通过断言:Assert

 

 下面通过一个简单的例子来说一下怎样写单元测试:

public  int fibona(int n){

  if (n==1 || n==2){

    return 1;

}

 int first =1;

 int  second =1;

 for(int i=1;i<n;i++) {

     var temp = second;

        second += first;

        first = temp;

}

  return second;

}

 [Fact]

public void Test_Fibonacci()

{

    var act = Fibonacci(10);

    var expect = 55;

    Assert.True(act == expect);

}

[Theory]

[InlineData(10,55)]

public void Test_Fibonacci_N(int n, int expect)

{

    var act = Fibonacci(n);   

    Assert.True(act == expect);

}

 

第二种的好处就是,我们可以传入一组数据,不需要修改代码。

异步方法的支持:

xunit 支持async 和 await 测试

异常的判断:

Assert.Throws    验证测试代码抛出指定异常

Assert.ThrowsAsync()   如果测试代码返回Task,应该使用异步方法

Mock 的使用:

什么是Mock? Mock 就是假的,模拟的,我们什么时候会用到这个呢? 下面列出情景:

 真实的对象不易构造

    例如httpselvlet必须在servlet容器中才能创建处理

 真实的对象非常复杂

   如jdbc中的Connection,ResultSet

真实的对象行为具有不确定性,难于控制他们的输出或者返回结果

真实的对象有些难于触发

    例如:硬盘已满,网络连接断开

真实的对象可能还不存在,例如依赖的另外一个模块还没有开放完毕

下面列举一个例子:

首先定义一个接口ICalculator:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace CalculatorPkg

{

    public interface ICalculator

    {

        int Add(int param1, int param2);

        int Subtract(int param1, int param2);

        int Multipy(int param1, int param2);

        int Divide(int param1, int param2);

        int ConvertUSDtoRMB(int unit);

    }

}

再定义一个类实现这个接口ICalculator :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace CalculatorPkg
{
public class Calculator : ICalculator
{
private readonly IUSD_RMB_ExchangeRateFeed _feed;
public Calculator(IUSD_RMB_ExchangeRateFeed feed)
{
this._feed = feed;
}
#region ICalculator Members
public int Add(int param1, int param2)
{
throw new NotImplementedException();
}
public int Subtract(int param1, int param2)
{
throw new NotImplementedException();
}
public int Multipy(int param1, int param2)
{
throw new NotImplementedException();
}
public int Divide(int param1, int param2)
{
return param1 / param2;
}
public int ConvertUSDtoRMB(int unit)
{
return unit * this._feed.GetActualUSDValue();
}
#endregion
}
}

初始化类对象时,需要传入接口类对象:

namespace CalculatorPkg

{

     public interface IUSD_RMB_ExchangeRateFeed

    {

        int GetActualUSDValue();

    }

}

在对类 Calculator  写单元测试时,首先需要初始化,但是我们不知道 接口类对象具体返回什么,所以我们采用Mock方式:

 

using System;

using Xunit;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

 

 

using CalculatorPkg;

 

using Moq;

 

namespace CalculatorPkgTests

{

    public class CalculatorTester

    {

       

        // 定义mock的逻辑

        private IUSD_RMB_ExchangeRateFeed PrvGetMockExchangeRateFeed()

        {

            Mock<IUSD_RMB_ExchangeRateFeed> mockObject = new Mock<IUSD_RMB_ExchangeRateFeed>();

            mockObject.Setup(m => m.GetActualUSDValue()).Returns(500);

            return mockObject.Object;

        }

        

      [Theory]

       [InlineData(9,3,3)]

        [InlineData(4,2,2)]

        public void TC1_Divide(int a, int b, int exceptResult)

        {

            IUSD_RMB_ExchangeRateFeed feed =this.PrvGetMockExchangeRateFeed();

            Calculator calculator = new Calculator(feed);

            int actualResult = calculator.Divide(a, b);          

            Assert.Equal(expectedResult, actualResult);

        }

     

    }

}

 

 

posted on 2017-05-03 17:59  NancyForever  阅读(602)  评论(0编辑  收藏  举报

导航