前言
单元测试其实就是依赖反转的一个具体的实现案例。
依赖反转
首先让我们以电扇生产为例子进行演示,因为电扇依赖于电源,所以我们先定义一个电源类:
public class PowerSupply
{
private int _power;
public int GetPower()
{
return 100;
}
}
接下来就可以生产电扇了,借助定义一个电扇类:
public class DeskFan
{
private PowerSupply _powerSupply;
public DeskFan(PowerSupply powerSupply)
{
_powerSupply = powerSupply;
}
public string Work()
{
int power = _powerSupply.GetPower();
if(power < 0)
{
return "Won't work";
}
else if(power < 100)
{
return "Slow";
}
else if(power < 200)
{
return "Work fine";
}
else
{
return "Warning!";
}
}
}
现在有一个问题,那就是电扇类和电源类是紧耦合的,我们想要测试DeskFan实例的Work方法则需要修改电源类的GetPower方法的返回值,这违背了面向对象设计原则中的开闭原则,不是为了修复bug或者增加新功能的情况下不应该去修改已经定义好的类。这个时候可以通过接口来实现依赖反转。我们可以定义一个IPowerSupply的接口:
public interface IPowerSupply
{
int GetPower();
}
然后让电源实现这个接口中的方法:
public class PowerSupply:IPowerSupply
{
public int GetPower()
{
return 100;
}
}
此时电扇类中_powerSupply字段类型修改为IPowerSupply类型:
public class DeskFan
{
private IPowerSupply _powerSupply;
public DeskFan(IPowerSupply powerSupply)
{
_powerSupply = powerSupply;
}
public string Work()
{
int power = _powerSupply.GetPower();
if(power < 0)
{
return "Won't work";
}
else if(power < 100)
{
return "Slow";
}
else if(power < 200)
{
return "Work fine";
}
else
{
return "Warning!";
}
}
}
于是乎我们就可以使用测试电源来测试电扇的工作状态了吗,先定义一个测试电源类:
public class TestPowerSupply:IPowerSupply
{
public int GetPower()
{
return 300;
}
}
然后测试电扇的输出:
TestPowerSupply testPowerSupply = new TestPowerSupply();
DeskFan deskFan = new DeskFan(testPowerSupply);
Console.WriteLine(deskFan.Work()); // Output: Warning
Console.ReadKey();
这就将原来自顶向下的依赖关系(电扇依赖于某一个具体的电源):
转变为电扇依赖于电源,但并非某一具体的电源,可以是厂家自产的电源,也可以是用于测试的电源(原来向下的箭头向上了,这便是依赖反转的由来):
单元测试
一般情况下,TestPowerSupply不应该写在源代码中,而是应该新建一个测试项目用来测试电扇的工作情况。我们新建一个xunit的单元测试(在Visual studio中搜索xunit即可创建,创建完成以后要引用一下你要测试的项目)。
此处我们用命令行的方法新建一个xunit,在你的项目目录旁边打开PowerShell(按住shift键右击空白处选择PowerShell):
mkdir ConsoleApp.Tests
cd ConsoleApp.Tests
dotnet new xunit --framework netcoreapp3.1
dotnet add reference ..\ConsoleApp\ConsoleApp.csproj
dotnet restore
dotnet build
看一下输出结果:
用你习惯的IDE打开项目,然后编写如下代码进行测试:
using System;
using Xunit;
namespace ConsoleApp.Tests
{
public class DeskFanTests
{
[Fact]
public void PowerHigherThan200_Warning()
{
TestPowerSupply testPowerSupply = new TestPowerSupply();
DeskFan fan = new DeskFan(testPowerSupply);
var expected = "Warning!";
var actual = fan.Work();
Assert.Equal(expected,actual);
}
}
public class TestPowerSupply: IPowerSupply
{
public int GetPower()
{
return 201;
}
}
}
在PowerShell中输入dotnet test,显示通过:
不过这样还是有一个弊端,那就是每次测试的时候都要写一个测试类,会比较麻烦,于是可以使用第三方库Moq来测试,在Nuget中添加Moq,或者在命令行中继续输入 dotnet add package
添加Moq包
使用Moq可以少些很多代码:
using System;
using Xunit;
using Moq;
namespace ConsoleApp.Tests
{
public class DeskFanTests
{
[Fact]
public void PowerHigherThan200_Warning()
{
var mock = new Mock<IPowerSupply>();
mock.Setup(ps => ps.GetPower()).Returns(() => 201);
DeskFan fan = new DeskFan(mock.Object);
var expected = "Warning!";
var actual = fan.Work();
Assert.Equal(expected, actual);
}
}
}
参考文献
- C#语言入门详解(029)