c# - 我将如何对这种方法进行单元测试?

标签 c# unit-testing moq xunit

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

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

我还有一个 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测试可能需要一个 spy 替身来使用 ICarrierService 进行请求验证和 CreateRequest也可以为 ILabelValidation 使用 stub .尽管这实际上取决于您的最终设计,但可能与我的假设不同。如果您不确定 double 以及它们如何与最小起订量匹配,您可能需要查看 Martin Fowler's Mocks aren't stubs .

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

关于c# - 我将如何对这种方法进行单元测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64114532/

相关文章:

iphone - 访问 OCUnit 测试目标内的 UIImage

php - 代码覆盖率报告错误地指示 Controller 的 100% 覆盖率

c# - 如何使用最小起订量来验证是否将类似的对象作为参数传入?

javascript - 引用错误:XMLHttpRequest is not defined while using ClearScript(V8ScriptEngine)

c# - 系统时钟调整会影响在 C# 中运行秒表吗?

c# - 如何在 PetaPoco 中检索 xml 列

unit-testing - pytest autouse fixture 导致测试失败

c# - 如何在模拟对象内创建模拟对象?

c# - 如何忽略 MoqVerifySet() 表达式中的空格?

c# - 有没有办法刷新 WPF 中的所有绑定(bind)?