使函数虚拟工作时无法使用接口创建最小起订量测试

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);
    }
}

您的 类 的原始设计不是很灵活,因此很难隔离部件进行测试。