使函数虚拟工作时无法使用接口创建最小起订量测试
Cannot create Moq test with Interface while making the function virtual works
我正在尝试测试这个 class:
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);
}
}
我正在使用内存数据库来测试数据,但是 TestCase 的 test.Count
具有与真实数据库相对应的计数。
如果我使 GetContext()
和 GetTaskProvider()
虚拟 我从虚拟数据库中得到正确的 Count
但我没有希望它们是虚拟的,如果它们不是虚拟的,我也更喜欢 public,我做错了什么?
这是设计问题。
what am i doing wrong?
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);
}
}
您的 类 的原始设计不是很灵活,因此很难隔离部件进行测试。
我正在尝试测试这个 class:
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);
}
}
我正在使用内存数据库来测试数据,但是 TestCase 的 test.Count
具有与真实数据库相对应的计数。
如果我使 GetContext()
和 GetTaskProvider()
虚拟 我从虚拟数据库中得到正确的 Count
但我没有希望它们是虚拟的,如果它们不是虚拟的,我也更喜欢 public,我做错了什么?
这是设计问题。
what am i doing wrong?
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);
}
}
您的 类 的原始设计不是很灵活,因此很难隔离部件进行测试。