【问题标题】:How to unit test multiple dependencies如何对多个依赖项进行单元测试
【发布时间】:2019-09-30 08:39:32
【问题描述】:

我想用 C#(使用 NUnit)编写一些单元测试,但我不知道如何继续。

所以我有一个类,它呈现一个用户模型,DataModel 具有四个属性。我在我的 ExcelInit 构造函数中发送这个模型。

DataModel dm = new DataModel();
dm.PopulateDataModel(holder);

ExcelInit d1 = new ExcelInit(dm);
d1.BeginProcess();

然后在 BeginProcess 我开始我的实际方法。

public class ExcelInit
{
    public DataModel _model { get; set; }

    public ExcelInit(DataModel model)
    {
        _model = model;
    }

    public void BeginProcess()
    {
        DataReader dr = new DataReader();
        CellLocation cl = new CellLocation();
        //Gets the corresponding cellAddress for the cell that contains "2x5" from our hidden config-sheet
        var correspondingCellsAddress = cl.FindCorrespondingCellAddress(_model.boxSize);
        //returns the range that we need to copy i.e. the whole range of the box ala B3:AG13
        var srcRange = dr.GetRangeForSourceDestination(correspondingCellsAddress);
        //Last row of sheet 6 (the sheet we populate) with input-boxes
        var lastRow = dr.FindLastRowByName("Your_Data_Sheet") + 3;
        //gets the cell we'll copy our range to
        var destRange = "B" + lastRow.ToString();

        DataHandler dh = new DataHandler();
        dh.CopyBox(srcRange, destRange, _model);
    }

但是正如你所看到的,我在 BeginProcess 中创建了很多类,我的老师告诉我不能这样做(我需要使用 DI 才能进行测试)。但我的问题是,当我需要在我的方法中使用这么多类时,我该如何使用 DI?还是我的方法错了,我不应该依赖这么多不同的东西?

【问题讨论】:

  • 好吧,您可以将DataReader 作为参数注入您的方法,而不是在内部创建它。这同样适用于您的DataHandler。但除此之外,我在这里看不到太多可注入的东西。您的老师具体指的是哪些课程?
  • 没什么特别的,他只是总是告诉我(使用接口将类注入构造函数而不是创建新关键字)所以我可以创建测试但我只是不明白我是否需要不使用任何新关键字和只需将 DataReader、DataHandler 和 CellLocation 放入 DI,但如果是这样,我是否需要在调用此方法的类中创建新实例?然后我仍然使用 new 关键字,然后我无法测试
  • @8329396 因为你应该依赖抽象(接口)而不是特定的实现,当你依赖抽象时,你可以创建一个模拟并定义假行为。总结一下,在创建 ExcelInit 时使用 new,但在构造函数中使用接口,以便以后可以使用 Mock
  • "在调用此方法的类中创建新实例?"没错,这就是你应该做的。 DI 的重点不是 if 您使用 new(即使是 DI 容器也必须以某种方式创建实例,无论是通过反射还是使用 new(...)),而是 where。因此,在您的测试方法中,您创建了依赖项 - 例如阅读器 - 因此您可以在被测系统中简单地使用它们,而不是在那里创建它们。
  • 这是您需要与老师进行的讨论。所以不应该是你的第一站。去和他/她谈谈,寻求一个例子或一些真正的指导。然后你就会知道该怎么做。这里没有人能猜出你的老师的意思/期望。去问问。

标签: c# unit-testing dependency-injection


【解决方案1】:

DI 的重点不是避免创建实例。即使是 DI 容器也必须以某种方式创建依赖项的实例,例如通过使用反射。

相反,DI 是关于创建这些实例的何处。话虽如此,您不应该让您的被测系统(SuT,在您的情况下是类ExcelInit)创建自己的依赖项,而只是将它们注入到系统中,例如通过使用构造函数注入或只是将参数添加到您的方法中:

public void BeginProcess(DataReader dr, CellLocation cl, DataHandler dh)
{
    //Gets the corresponding cellAddress for the cell that contains "2x5" from our hidden config-sheet
    var correspondingCellsAddress = cl.FindCorrespondingCellAddress(_model.boxSize);
    //returns the range that we need to copy i.e. the whole range of the box ala B3:AG13
    var srcRange = dr.GetRangeForSourceDestination(correspondingCellsAddress);
    //Last row of sheet 6 (the sheet we populate) with input-boxes
    var lastRow = dr.FindLastRowByName("Your_Data_Sheet") + 3;
    //gets the cell we'll copy our range to
    var destRange = "B" + lastRow.ToString();

    dh.CopyBox(srcRange, destRange, _model);
}

现在在你的测试中你有这个:

var target = new ExcelInit();
target.BeginProcess(new DataReader(), new CellLocation(), new DataHandler());

当然,这将执行完全相同的指令。但是您现在也可以这样做:

var target = new ExcelInit();
target.BeginProcess(new SomeSpecificDataReader(), new CellLocation(), new DataHandler());

由于您只是注入阅读器而不是在 SuT 中创建它,因此您的实际 SuT 根本不会改变。所以实际上您的测试系统并不依赖于特定的文件,而是依赖于数据。此数据的存储位置对您的BeginProcess-方法没有影响。例如,您可以提供另一个 excel 文件(假设您的系统使用 excel 文件)、xml 文件甚至一些内存数据存储。

【讨论】:

  • 您能否提供一个测试用例,说明 DataReader、CellLocation 和 DataHandler 的外部化创建如何帮助创建更好的测试?
  • @Euphoric 见我的SomeSpecificDataReader-example。当然,仅仅注入依赖不会让你的测试自动变得更好。
  • 不明白为什么这被否决了,对我来说似乎合乎逻辑,它允许测试模拟预期结果和测试流程?也许我错过了一些东西,但 +1 以平衡事情
  • @NDJ 您能否描述一下您希望如何模拟 Excel 的类以用于测试目的?
  • 在我看来你只需要模拟 FindCorrespondingCellAddress 来返回有效的东西吗?我参与了一个模拟预期调用的项目,我们并没有完全重新创建所有 excel 的功能
【解决方案2】:

您的老师似乎让您感到困惑,甚至可能让他自己也感到困惑。

如果依赖是你控制的,他的建议会奏效。那不是你的情况。您直接使用 Excel 的 API 并注入您使用 new 创建的类不会帮助您提高可测试性。如果您要听从老师的建议,您将不得不为整个 Excel 的 API 创建测试替身,我认为这是不可能的,因为该 API 不是为可测试性而设计的。

遵循老师建议的一种方法是创建自己的 API 来封装 Excel。这可以在考虑可测试性的情况下进行。但是看看你的代码,我觉得这只是将问题推低了一层。您会使您的解决方案复杂化,同时没有对真正重要的代码进行自动化测试。

我建议让你的老师举例说明他的意思。以及注入这些类将如何通过编写实际测试来提高代码的可测试性。我敢肯定,如果他的建议对在您的场景中创建更多可测试的代码没有实际帮助,他也会意识到他的建议。

【讨论】:

    猜你喜欢
    • 2014-05-29
    • 2010-12-25
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 1970-01-01
    • 2019-01-01
    • 1970-01-01
    相关资源
    最近更新 更多