首先介绍一下什么是单元测试:
那么,如何检测代码的功能逻辑是否正确呢?
调试,是临时的,且不完整的,例如,一个函数有十多种输入,调试不可能全部覆盖,能五六种就不错了。而系统测试,并不针对某个具体的函数,不关注某个函数的功能逻辑是否正确。
要检测某个函数的功能逻辑,就必须要依照分类列出数据,检测代码是否对每一个分类都做了处理,而且每一个分类的处理是否正确。
——这就是单元测试。
单元测试的基本方法 由上面的分析可以看出,单元测试的基本方法就是:依数据的分类列出输入,执行被测试程序,然后,判断输出是否符合预期。
单元测试能达到什么样的效果呢?那就是:无论别人怎么样,我总是对的!
这里的“别人”,是指关联代码。“我”,是指当前正在编写或测试的代码。单元测试要做到的是,无论关联代码是否有错,都要保证我是对的。具体来说,我要考虑关联代码会产生什么样的数据,这些数据要如何分类处理,只要我的分类和处理是正确的,那么,无论别人怎么样,我总是对的。
单元测试的优点:
- 验证行为------验证程序的正确性
- 设计行为
- 文档行为------通过看单元测试,能够知道函数实现的什么功能,传入的参数情况,以及异常情况的处
单元测试的技巧:
单元测试主要关注点就是程序出错的地方: 空、边界 和异常
当对象为空会怎样? 设计容器操作时,算法是否会超过边界? 出错了是否抛出了指定异常。
单元测试编写方法:(配置式编程)
编写单元测试时,最好能够批量进行测试,将测试数据与测试代码分离。当心增加测试用例时,只要新增加测试数据集即可。
单元测试的原则:
1.测试代码和被测代码是同等重要的,需要被同时维护 测试代码不是附属品 不但要重构代码,也要重构单元测试 2. 单元测试一定是隔离的 一个测试用例的运行结果不能影响其他测试用例 测试用例不能相互依赖,应该能够以任何次序执行 3. 单元测试一定是可以重复执行的 不能依赖环境的变化(依赖时间,依赖环境变量) 保持单元测试的简单性和可读性(维护成本高) 4. 尽量对接口进行测试 5. 单元测试应该可以迅速执行 使用Mock 对象对数据库,网络的依赖性进行解耦 6. 自动化单元测试 持续集成 |
单元测试位置:
1. 和被测代码放到一起 方便查找,但是会造成扰乱 2. 放到单独的目录下,但是保证和被测代码位使用同样的包名 方便查找 可以直接访问包级的变量和方法 3. 最好创建一个单元测试的project,让它依赖被测试的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;
|
初始化类对象时,需要传入接口类对象:
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); }
} } |