单元测试有一个非常有用的暴露设计问题的习惯。在本例中,由于与框架关注点以及静态关注点的紧密耦合,您已经做出了一些设计选择,这些选择被证明很难测试。
首先,看起来
SqlRestaurantDataCL
实际上取决于连接工厂
public interface IDbConnectionFactory {
IDbConnection GetConnection();
}
它将根据建议重构数据实现,以依赖于该抽象。
public class SqlRestaurantDataCL : IRestaurantDataCL {
private readonly IDbConnectionFactory factory;
public SqlRestaurantDataCL(IDbConnectionFactory factory) {
this.factory = factory;
}
public Restaurant Add(Restaurant restaurant) {
using (var connection = factory.GetConnection()) {
string insertSql = @"INSERT INTO [dbo].[RESTAURANTS]([Cuisine], [Name])
OUTPUT INSERTED.*
VALUES (@Cuisine, @Name)";
restaurant = connection.QuerySingle<Restaurant>(insertSql, new {
Cuisine = restaurant.Cuisine,
Name = restaurant.Name
});
return restaurant;
}
}
//...
}
假设dapper正用于进行上述查询。
通过引入抽象的依赖项,可以在隔离测试时根据需要模拟它们。
public class SqlRestaurantDataCLUnitTest {
[Fact]
public void AddTest() {
//Arrange
var connection = new Mock<IDbConnection>();
var factory = new Mock<IDbConnectionFactory>();
factory.Setup(_ => _.GetConnection()).Returns(connection.Object);
//...setup the connection to behave as expected
var restaurantDataCL = new SqlRestaurantDataCL(factory.Object);
var restaurant = new Restaurant {
Name = "TestName",
Cuisine = CuisineType.None
};
//Act
var result = restaurantDataCL.Add(restaurant);
//Assert
Assert.IsNotType(null, result.Id);
}
}
现在,如果您打算实际接触真实的数据库,那么这不是一个隔离单元测试,而是一个集成测试,它将采用不同的方法。
在生产代码中,工厂可以实现
public class SqlDbConnectionFactory : IDbConnectionFactory {
private readonly ConnectionSetings connectionSettings;
SqlDbConnectionFactory(ConnectionSetings connectionSettings) {
this.connectionSettings = connectionSettings;
}
public IDbConnection GetConnection() {
return new SqlConnection(connectionSettings.Name));
}
}
在哪里?
ConnectionSetings
定义为存储连接字符串的简单POCO
public class ConnectionSetings {
public string Name { get; set; }
}
在合成根目录中,可以从配置中提取设置。
IConfiguration Configuration; //this would have been set previously
public void ConfigureServices(IServiceCollection services) {
//...
var settings = Configuration
.GetSection("ConnectionStrings:OdeToFood")
.Get<ConnectionSetings>();
//...verify settings (if needed)
services.AddSingleton(settings);
services.AddSingleton<IDbConnectionFactory,SqlDbConnectionFactory>();
services.AddSingleton<IRestaurantDataCL,SqlRestaurantDataCL>();
//Note: while singleton was used above, You can decide to use another scope
// if so desired.
}
真的没必要路过
IConfiguration
因为它更像是一个框架问题,实际上只在启动时才相关。