我正在尝试测试这个类:
public class Tasks : ITaskEnumerableProvider
{
protected string ConnectionString;
DAL_EFCore.AdventureWorks2017Context CurrentContext;
public Tasks(DAL_EFCore.AdventureWorks2017Context currentContext)
{
CurrentContext = currentContext;
}
public Tasks(string connectionString)
{
ConnectionString = connectionString;
}
public DAL_EFCore.AdventureWorks2017Context GetContext()
{
if (CurrentContext != null)
return CurrentContext;
else
{
var serviceCollection = new ServiceCollection()
.AddDbContextPool<DAL_EFCore.AdventureWorks2017Context>
(
options => options.UseSqlServer(ConnectionString)
);
var serviceProvider = serviceCollection.BuildServiceProvider();
CurrentContext = serviceProvider.GetService<DAL_EFCore.AdventureWorks2017Context>();
return CurrentContext;
}
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders()
{
return GetContext().Employee
.GroupBy(e => e.Gender)
.Select(n => new SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders
{
Gender = n.Key,
Count = n.Count()
})
.ToList();
}
}
而这个:
public class DataReports : IDataReports
{
protected ITaskEnumerableProvider TaskProvider;
protected string ConnectionString;
protected string DalModeSelected;
public DataReports() {}
public DataReports(DBConfig config)
{
ConnectionString = config.ConnectionString;
DalModeSelected = config.DAL;
}
public ITaskEnumerableProvider GetTaskProvider()
{
switch (DalModeSelected)
{
case "ADO":
return new ADOTaskProvider.Tasks(ConnectionString);
case "EFCore":
return new EFCoreTaskProvider.Tasks(ConnectionString);
default:
throw new FormatException("The format of the variable which represend the selected DAL was not correct");
}
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders()
{
return GetTaskProvider().SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
}
我正在使用此代码进行测试:
[TestMethod]
public void SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest()
{
// Setup Mock Data and context
var options = new DbContextOptionsBuilder<DAL_EFCore.AdventureWorks2017Context>()
.UseInMemoryDatabase(databaseName: "SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest")
.Options;
using (var context = new DAL_EFCore.AdventureWorks2017Context(options))
{
InsertData(options);
}
using (var context = new DAL_EFCore.AdventureWorks2017Context(options))
{
// Mock EFCoreTaskProvider.Tasks
var mockEFCoreTaskProvider = new Mock<EFCoreTaskProvider.Tasks>(ConnectionString);
mockEFCoreTaskProvider.As<IGetContext>();
mockEFCoreTaskProvider.CallBase = true;
mockEFCoreTaskProvider.Setup(x => x.GetContext()).Returns(context);
// Mock CoreReportService.DataReports
var config = new DBConfig { DAL = "EFCore", ConnectionString = ConnectionString };
var mockDataReports = new Mock<DataReports>(config).As<IDataReports>();
mockDataReports.CallBase = true;
mockDataReports.Setup(x => x.GetTaskProvider()).Returns(mockEFCoreTaskProvider.Object);
var test = mockDataReports.Object.SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
Assert.IsTrue(test.Count == 1);
}
}
我正在使用内存数据库来测试数据,但测试用例的test.Count
具有与真实数据库对应的计数。
如果我GetContext()
和GetTaskProvider()
虚拟的,我从虚拟数据库中获得了正确的Count
,但我不希望它们是虚拟的,我也更喜欢如果它们不是公开的,我做错了什么?
这是一个设计问题。
我做错了什么?
DataReports
与实施问题密切相关,也违反了单一责任原则(SRP(和关注点分离(SoC(。
通过DataReports
创建提供程序,它与它们紧密耦合,并防止您在测试时替换它们。
将提供程序创建抽象为自己的关注点
例如
//Abstraction
public interface ITaskProviderFactory {
ITaskEnumerableProvider GetTaskProvider();
}
//Implementation
public class DefaultTaskProviderFactory : ITaskProviderFactory{
private readonly DBConfig config;
public DefaultTaskProviderFactory(DBConfig config) {
this.config = config;
}
public ITaskEnumerableProvider GetTaskProvider() {
switch (config.DAL) {
case "ADO":
return new ADOTaskProvider.Tasks(config.ConnectionString);
case "EFCore":
return new EFCoreTaskProvider.Tasks(config.ConnectionString);
default:
throw new FormatException("The format of the variable which represent the selected DAL was not correct");
}
}
}
并相应地重构DataReports
public class DataReports : IDataReports {
private readonly ITaskProviderFactory factory;
public DataReports(ITaskProviderFactory factory) {
this.factory = factory;
}
private ITaskEnumerableProvider getTaskProvider() {
return factory.GetTaskProvider();
}
public IEnumerable<SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders> SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders() {
return getTaskProvider().SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
}
}
在生产中的运行时,可以显式注入相应的实现。
对于DataReports
的集成测试,可以根据需要交换实际实现,以验证预期的行为。
例如
[TestMethod]
public void SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest() {
//Arrange
var options = new DbContextOptionsBuilder<DAL_EFCore.AdventureWorks2017Context>()
.UseInMemoryDatabase(databaseName: "SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrdersTest")
.Options;
using (var context = new DAL_EFCore.AdventureWorks2017Context(options)) {
InsertData(options);
}
using (var context = new DAL_EFCore.AdventureWorks2017Context(options)) {
//actual EFCoreTaskProvider.Tasks targeting in-memory database
var taskProvider = new EFCoreTaskProvider.Tasks(context);
//mock factory configured to return the desired provider
var mockFactory = Mock.Of<ITaskProviderFactory>(_ =>
_.GetTaskProvider() == taskProvider //return the actual provider for testing
);
// actual CoreReportService.DataReports (Subject under test)
var dataReports = DataReports(mockFactory);
//Act
var result = dataReports.SalesOrderMinMaxTotalDuePerTerritoryForMarketingOrders().ToList();
//Assert
Assert.IsTrue(result.Count == 1);
}
}
类的原始设计不是很灵活,因此很难隔离零件进行测试。