繁体   English   中英

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

[英]How would I unit test this method?

我是单元测试 (xUnit) 的新手,我不太确定在为它编写测试时如何处理这种方法。

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

我还有一个 Get() 方法,它:

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

是否可以为这样的方法编写测试? 我知道我可以模拟依赖项,将它们传递给构造函数并使用 Moq 设置方法,但是如果我在 Get() 方法内部有 4 或 5 个来自 ILabelValidation 的验证方法怎么办? 我需要使用 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;
    }
}

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

为了测试事物,最好方法/组件做尽可能少的事情。 最好是一件事。

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

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

另请注意,执行Label::Get可能看起来非常 OOP 之类的东西,但是如果您发现自己在使用Label完全独立于LabelCreateShipmentResponse某些业务逻辑实现单元测试时必须模拟ICarrierService获得,您的设计还有一些问题。 如果您向Label再添加一个依赖项并且突然不得不返工数百个不相关的测试,这会变得特别令人不快。 随着时间的推移,将与一个概念远程相关的所有东西都放在一个类中的想法会导致 5+kLoC 的遗留怪物极难改变和测试。 但同样,我不详细了解您的系统,所以这仅供您考虑。 对我来说,拥有一个接受intICarrierServiceILabelValidation的构造函数似乎很奇怪,因为这三个东西可能具有完全不同的生命周期和抽象级别。

除非您使用 MockBehaviour.Loose,否则您必须为每个调用创建一个设置。 这在您的场景中是有意义的,因为您需要通过模型提供数据,以便您可以对方法应该返回的内容做出有意义的断言。

对于松散的行为,您不需要提供设置,但模拟上的所有方法调用都将返回方法返回类型 (null) 的默认值,该值不适用于您的代码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM