我将如何对该方法进行单元测试?

时间:2020-09-29 07:08:14

标签: c# unit-testing moq xunit

我是单元测试(xUnit)的新手,我不确定在为它编写测试时如何使用该方法。

基本上,我有一个名为Label.cs的类,在构造函数中,我使用DI注入了2个接口。

我也有一个Get()方法:

  1. 构造XML请求
  2. 删除所有无效字符
  3. 调用API
  4. 从响应中读取标签信息
  5. 返回响应

是否可以为这样的方法编写测试?我知道我可以模拟依赖项,将它们传递给构造函数并使用Moq设置方法,但是如果我在Get()方法中有ILabelValidation的4或5个以上验证方法,该怎么办?我需要使用Moq设置所有这些方法吗?

public class Label 
{
    private readonly ICarrierService _carrierService;
    private readonly ILabelValidation _validator;

    public Label(int accountId, ICarrierService carrierService, ILabelValidation validator)
    {
        _carrierService = carrierService;
        _validator = validator;
    }

    public override async Task<CreateShipmentResponse> Get(int accountId, Shipment shipment)
    {
        string xmlRequest = ConstructRequest(shipment);
        
        // strip out any invalid characters inside the request
        xmlRequest = _validator.InvalidCharacterValidation(xmlRequest);

        // what if I have another method that I use from the ILabelValidation?
        // _validator.ValidatePackageCount(3);

        var stringContent = new StringContent(xmlRequest, Encoding.UTF8, "text/xml");

        // make the API request
        var shipmentResponse = await _carrierService.CreateShipment(dict, stringContent);

        var stream = await shipmentResponse.Content.ReadAsStreamAsync();
        Models.Label labelInfo = GetLabelInfo(stream);

        var response = new CreateShipmentResponse();
        response.labels = new List<string>();

        if (!string.IsNullOrEmpty(labelInfo.Base64String))
            response.labels.Add(labelInfo.Base64String);

        return response;
    }
}

2 个答案:

答案 0 :(得分:2)

正如您自己指出的那样,您的方法可以完成多项任务。这通常不是一件好事。当您发现难以测试时,这表明您的设计需要更多的工作。

为了测试事物,最好是方法/组件做的事情越少越好。理想情况是一件事。

很显然,在Label抽象级别上,一件事是Get。但是,在向下钻取时,您发现必须1.创建一个请求,2.发送该请求,3.处理响应。现在,这些是另外三个步骤,它们是下面的抽象级别之一,但是是分开的,应该由较低抽象级别的方法表示。现在这些方法可能更容易测试,但可能需要进一步分解。

使用这种方法,您可以选择仅对Label::Get进行几个健全性测试,并选择涵盖<Your new request creation component>::CreateRequest<Your new response processing component>::ParseResponse的所有边缘情况的详细测试套件。 Label::Get测试可能需要使用间谍双来进行ICarrierService 的请求验证,而CreateRequest也可能对ILabelValidation使用存根。尽管这实际上取决于您的最终设计,这可能与我的假设有所不同。如果不确定双打以及它们与Moq的匹配方式,则可能需要检查Martin Fowler's Mocks aren't stubs

还请注意,执行Label::Get之类的操作可能看起来非常糟糕,但是如果您发现自己必须使用ICarrierService对某些业务逻辑进行单元测试时必须嘲笑Label完全独立于LabelCreateShipmentResponse的获取方式,您的设计还有更多问题。如果您向Label添加了更多的依赖关系,并且突然不得不重新进行数百个不相关的测试,那么这将尤其令人不快。将与一个概念远程相关的所有事物归为一类的想法会随着时间的流逝而导致5 + kLoC的遗留怪物,这些怪物极其难以更改和测试。但是同样,我不了解您的系统的详细信息,因此仅供您考虑。对我来说,拥有一个intICarrierServiceILabelValidation的构造函数似乎很奇怪,因为这三件事可能具有完全不同的生存期和抽象级别。

答案 1 :(得分:0)

除非使用MockBehaviour,否则必须为每个呼叫创建一个设置。在您的情况下这很有意义,因为您将需要通过模型提供数据,以便可以对方法应返回的内容进行有意义的断言。

行为松散,您不需要提供设置,但对模拟的所有方法调用都将返回方法返回类型(空)的默认值,这与您的代码不兼容。