kud*_*ger 7 c# unit-testing dependency-injection inversion-of-control
我用于AutofacIoC
这是我的容器启动器类,其职责是注册依赖项。
public class ContainerInit
{
public static IContainer BuildContainer()
{
var conFac = new ContainerFactory();
var builder = new ContainerBuilder();
builder.Register(conFac).As<IContainerFactory>().SingleInstance();
builder.Register(c=> new MainClass(conFac)).As<IMainClass>().SingleInstance();
builder.Register(c=> new Database(conFac)).As<IDatabase>().SingleInstance();
var logger = LoggUtil.CreateLogger();
builder.Register(logger).As<ILogger>().SingleInstance();
var container = builder.Build();
ContainerFactory.SetContainer(container);
return container;
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法的问题是,我需要传递IContainerFactory给我在应用程序中使用的每个类的构造函数,如下所示
public class MainClass: IMainClass
{
private readonly ILogger _logger;
private readonly IDatabase _db;
public MainClass(IContainerFactory containerFactory)
{
_logger = containerFactory.GetInstance<ILogger>();
_db = containerFactory.GetInstance<IDatabase>(); //example
}
public AddDetails(Data data)
{
//do some business operations
_db.Add(data);
_logger.Information("added");
}
}
Run Code Online (Sandbox Code Playgroud)
因此很难对这些类进行单元测试。
怎样才能想出一个好的解决方案呢?
更好的方法是将类中所需的依赖项传递到构造函数中:
public class MainClass : IMainClass
{
private readonly ILogger _logger;
private readonly IDatabase _db;
public MainClass(ILogger logger, IDatabase db)
{
_logger = logger;
_db = db;
}
public void AddDetails(Data data)
{
//do some business operations
_db.Add(data);
_logger.Information("added");
}
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用Moq等模拟框架来模拟您的类依赖项,并验证依赖项是否被调用:
[TestClass]
public class UnitTest1
{
private Mock<ILogger> _mockLogger = new Mock<ILogger>();
private Mock<IDatabase> _mockDb = new Mock<IDatabase>();
[TestMethod]
public void TestMethod1()
{
// arrange
var mainClass = new MainClass(_mockLogger.Object, _mockDb.Object);
var data = new Data();
// act
mainClass.AddDetails(data);
// assert
_mockDb
.Verify(v => v.Add(data), Times.Once);
}
}
Run Code Online (Sandbox Code Playgroud)
我不会验证您的日志消息,因为这可能会改变并使测试变得脆弱。仅验证对于执行该方法的预期目的所必需的功能。
您当前的服务定位器反模式使您的代码难以单独测试,并使类误导其实际依赖的内容。
MainClass应重构以遵循显式依赖关系原则
public class MainClass : IMainClass
private readonly ILogger logger;
private readonly IDatabase db;
public MainClass(ILogger logger, IDatabase db) {
this.logger = logger;
this.db = db;
}
public void AddDetails(Data data) {
//do some business operations
db.Add(data);
logger.Information("added");
}
}
Run Code Online (Sandbox Code Playgroud)
对于依赖于容器工厂的任何其他类,例如Database.
但是,您还需要相应地重构容器注册
public class ContainerInit {
public static IContainer BuildContainer() {
var builder = new ContainerBuilder();
builder.RegisterType<MainClass>().As<IMainClass>().SingleInstance();
builder.RegisterType<Database>().As<IDatabase>().SingleInstance();
var logger = LoggUtil.CreateLogger();
builder.Register(logger).As<ILogger>().SingleInstance();
var container = builder.Build();
return container;
}
}
Run Code Online (Sandbox Code Playgroud)
测试MainClass需要您仅模拟被测类的必要依赖项。
[TestClass]
public class MainClassTests {
[TestMethod]
public void Should_AddDetails_To_Database() {
// Arrange
var mockDb = new Mock<IDatabase>();
var data = new Data();
var mainClass = new MainClass(Mock.Of<ILogger>(), mockDb.Object);
// Act
mainClass.AddDetails(data);
// Assert
mockDb.Verify(_ => _.Add(data), Times.Once);
}
}
Run Code Online (Sandbox Code Playgroud)