什么是单元测试
单元测试(Unit Test)是指对程序中的最小的可测试单元进行检查,以验证一个明确的功能是否正确,程序是否按预期执行。
为什么要进行单元测试
- 有助于回归测试
回归缺陷是在对应用程序进行更改时引入的缺陷。 测试人员通常不仅测试新功能,还要测试预先存在的功能,以验证先前实现的功能是否仍按预期运行。
使用单元测试,可在每次生成后,甚至在更改一行代码后重新运行整套测试。 让你确信新代码不会破坏现有功能。
- 促进良好的设计
当代码紧密耦合时,可能难以进行单元测试。 如果不为编写的代码创建单元测试,则耦合可能不太明显。
为代码编写测试会自然地解耦代码,因为采用其他方法测试会更困难
- 提供文档
如果你有一套命名正确的单元测试,每个测试应能够清楚地解释给定输入的预期输出。 此外,它应该能够验证其确实有效
- 帮助理解代码
单元测试这么好?为啥推动不起来
- 耗时间
- 系统可测试性差
- 认为测试应由专门的测试人员来做
单元测试步骤
待测试的方法
/// <summary>
/// 除法运算
/// </summary>
/// <param name="numerator">分子</param>
/// <param name="denominator">分母</param>
/// <returns></returns>
public int Division(int numerator, int denominator){
return numerator / denominator;
}
测试用例
[Fact]
public void Division_WithNonZeroDenminator(){
// 准备
const int denominator = 2;
const int numerator = 4;
// 执行
var result = Division(numerator, denominator);
// 断言
Assert.Equals(2, result);
}
单元测试框架
- XUnit 单元测试框架
- Moq Mock库
- NSubstitute Mock库
- shouldly 断言库
使用Fakes隔离测试代码
Fakes 有两种方式:
- 存根将类替换为可实现同一接口的小型替代项。 若要使用存根,你在设计应用程序时必须让每个组件仅依赖接口,而不依赖其他组件。 (“组件”是指一个类或一起开发和更新的一组类,通常包含在一个程序集中。)
- 填充码在运行时修改应用的编译代码,这样就可以运行测试提供的填充码代码,而不用执行指定的方法调用。 填充码可用于替换对无法修改的程序集(如 .NET 程序集)的调用。
考虑一下代码
public int GetDiscountedPrice(int price)
{
if (DateTime.Now.DayOfWeek == DayOfWeek.Tuesday)
{
return price / 2;
}
else
{
return price;
}
}
如果进行单元测试,会随着时间的变化,而导致结果不同
方法一 修改代码,使用接口解耦
public interface IDateTimeProvider
{
DayOfWeek DayOfWeek();
}
public int GetDiscountedPrice(int price, IDateTimeProvider dateTimeProvider)
{
if (dateTimeProvider.DayOfWeek() == DayOfWeek.Tuesday)
{
return price / 2;
}
else
{
return price;
}
}
这样的话,我们就可以通过mock来用一个自己可控的实现来替代不可控的
public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
{
var priceCalculator = new PriceCalculator();
var dateTimeProviderStub = new Mock<IDateTimeProvider>();
dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Monday);
var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
Assert.Equals(2, actual);
}
public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
{
var priceCalculator = new PriceCalculator();
var dateTimeProviderStub = new Mock<IDateTimeProvider>();
dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Tuesday);
var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
Assert.Equals(1, actual);
}
好的单元测试需要注意的点
- 快速
- 原子性
- 单一职责
每个单元测试只做一件事 - 独立
每个单元测试应该是独立的,单元测试之间无相互依赖,结果与单元测试的执行顺序无关 - 隔离外部调用
不依赖文件系统,数据库,本地时间,网络,环境变量等任何外部因素。 - 可重复
- 直观明了的方法名
- 全面但不要冗余
测试用例应该全面考虑:正常输入,边界输入,非法输入,白盒覆盖(准对代码逻辑结构来设计测试用例)
哪些代码需要单元测试
- 帮助类
- 业务逻辑
- 当代码出现bug时,需要在修复bug的同时添加相应的单元测试