【问题标题】:What Is Object Under Test vs. Collaborator here?什么是被测对象与协作者?
【发布时间】:2014-05-05 05:50:18
【问题描述】:

我正在尝试弄清楚如何使用 Moq 进行我的第一个 Mock。我也是 mocking 新手。

假设我有以下 TDD 测试:

[TestMethod]
public void PreAuthorize_WithEmptyRequest_ReturnsNonNullResponse()
{
    // Arrange
    var preAuthorizeRequest = new PreAuthorizeRequest();

    // Act
    var authorizeResponse = _dataProcessor.SendRequest(preAuthorizeRequest);

    // Assert
    Assert.IsNotNull(authorizeResponse);
}

上述测试的场景是,如果我们发送一个没有状态的对象实例,我们仍然应该返回一个响应对象(它不应该崩溃)。所以我想在 Mock 和 Stub 术语中我想我想测试 dataProcessor 的 SendRequest 方法的行为,因为它会发回 PreAuthorizeResponse 的实例......对吗?

下面是关于这些类型的信息:

    public interface IPaymentRequest
    {
        string SecurityToken { get; set; }
        int RetailID { get; set; }
        int ProcessorId { get; set; }
    }

    public class PreAuthorizeRequest : IPaymentRequest
    {
        public string SecurityToken { get; set; }
        public int RetailID { get; set; }
        public int ProcessorId { get; set; }
        public PreAuthorizeTransaction Transaction { get; set; }
    }

       public IPaymentResponse SendRequest(IPaymentRequest request)
        {
            ITransaction transaction = PaymentUtilities.GetPaymentRequestTransactionType(request);
            IPaymentResponse response = PaymentUtilities.GetPaymentResponseType(request);
            var transactionType = PaymentUtilities.GetPaymentRequestTransactionTypeEnum(request);
            var requestValidator = new PaymentRequestValidator();

            requestValidator.ValidateRequest(request);

            var requestIsValid = requestValidator.RequestIsValid;

            if (!requestIsValid)
            {
                response = PaymentUtilities.BuildPaymentResponse(request, requestValidator, transaction, transactionType);
                return response;
            }
IAutoPaymentProcessor autoPaymentProcessor = CreateAutoPaymentProcessor(request);
            var configValidator = new ConfigurationValidator(autoPaymentProcessor);

            configValidator.ValidateConfigurationSettings();
            bool settingsAreValid = configValidator.ConfigIsValid;

            if (!settingsAreValid)
            {
                response = PaymentUtilities.BuildPaymentResponse(request, requestValidator, transaction, transactionType);
                return response;
            }

            response = SetConfigSettings(request, response);

            Document dataResponseDoc = SendRequest(request, response);
            response.PaymentProcessorId = (int)Enums.PaymentProcessorType.Data;
            response.ProviderAuthCode = dataResponseDoc != null ? dataResponseDoc.get("Response.authcode") : string.Empty;
            response.ProviderReference = dataResponseDoc != null ? dataResponseDoc.get("Response.data_reference") : string.Empty;

            return response;
        }

所以我认为不需要模拟,换句话说,模拟上的验证调用对吗?我想我只需要一个存根来测试我是否得到了 PreAuthorizeResponse 的实例。所以我猜这是 Moq 的存根,对吧?

那么,如果我正确地认为我只需要在这里使用存根,那么我将如何使用 Moq 来做到这一点?

我试过这个,但我觉得这是错误的,因为我觉得 SUT 是我的数据处理器,我相信你不应该在测试中模拟对象:

    [TestMethod]
public void PreAuthorize_WithEmptyRequest_ReturnsNonNullResponse()
{
    // Arrange - setup data
    var dataProcessorStub = new Mock<IPaymentProcessor>();
    var preAuthorizeRequest = new PreAuthorizeRequest();

    // Act
    //setup - expectations
    dataProcessorStub.Setup(p => p.SendRequest(It.IsAny<PreAuthorizeRequest>())).Returns(It.IsAny<PreAuthorizeResponse>());

    // excercise
    var preAuthorizeResponse = dataProcessorStub.Object.SendRequest(preAuthorizeRequest);

    // Assert
    Assert.IsInstanceOfType(preAuthorizeResponse, typeof(PreAuthorizeResponse));
}

【问题讨论】:

    标签: c# mocking moq


    【解决方案1】:

    不,您并不是真的想模拟您要测试的对象。您应该模拟该对象的依赖项(例如数据库对象)。

    dataProcessor 是被测对象(也称为被测系统),其他一切都是协作者(SUT 的依赖项)。

    但是,由于SendRequest 方法中存在大量硬依赖项,您会发现很难正确测试。

    至少,你想模拟PaymentRequestValidator,这样当你发送一个特定类型的请求时,你可以让模拟设置说它是无效的,然后在代码中处理它,这反过来会导致SendRequest 方法返回响应。

    不过,要实现这一点,您需要重构代码,以便传入请求验证器的模拟实例。此外,可能还有很多其他对象。

    例如,您很可能需要模拟 PaymentUtilities 对象,以便您可以使用返回 Mock 对象的方法,这些对象本身已设置为返回此测试的特定内容。同样,ConfigurationValidator - 从测试中调用它时会返回有效配置(可能是不同的测试),还是你也需要模拟它?

    这正在进入依赖注入控制反转的领域。我不会通过提供链接来侮辱您,但是这些主题在印刷和网络上的文献中都有很好的介绍。

    【讨论】:

    • 那么当我的 SendRequest 方法中使用了该验证器时,我将如何模拟 PaymentRequestValidator?
    • 是的,您的 SendRequest 方法可能做得太多了。您可以修改 SendRequest 方法以接受 IPaymentRequestValidator,或修改数据处理器类本身以接受支付请求验证器对象作为构造函数参数或属性。
    • 这垃圾不容易是不是:)
    • 另外,SendRequest 正在使用一个数据层类,我认为它也应该被模拟?这是另一个依赖项
    • 那你觉得这篇文章怎么样,他们说要模拟以存根stackoverflow.com/questions/10505704/…
    【解决方案2】:

    查看代码,如果您尝试测试SendRequest 方法,似乎不需要进行任何模拟。您需要模拟您要测试的内容的依赖关系,而不是被测对象本身。因此,如果您的 PaymentProcessor 具有测试所需的任何外部依赖项,您需要模拟这些。

    SendRequest 方法确实采用了一个您可以模拟的接口,但只传递一个新创建的PreAuthorizeRequest 会更容易。

    [TestMethod]
    public void PreAuthorize_WithEmptyRequest_ReturnsNonNullResponse()
    {
        // Arrange
        var preAuthorizeRequest = new PreAuthorizeRequest();
    
        // Act
        var authorizeResponse = _dataProcessor.SendRequest(preAuthorizeRequest);
    
        // Assert
        Assert.IsNotNull(authorizeResponse);
    }
    

    您也可以模拟请求:

     [TestMethod]
     public void PreAuthorize_WithEmptyRequest_ReturnsNonNullResponse()
     {
         // Arrange
         var request = new Mock<IPaymentRequest>();
    
         // Act
         var authorizeResponse = _dataProcessor.SendRequest(request);
    
         // Assert
         Assert.IsNotNull(authorizeResponse);
     }
    

    【讨论】:

    • 所以你说在这种情况下我们不想模拟处理器类,因为它是正在测试的对象(并且它的方法正在测试中)所以我们在这里唯一可以模拟的是 IPaymentRequest对吗?
    • 是的,但是我也有其他人提到的问题是我的 SendRequest 方法做得太多,并且有其他我无法模拟的依赖项,所以我将不得不重构该方法或以某种方式注入诸如验证类之类的东西,以及通过接口或抽象类或我猜的东西进入其中的数据层类
    • 这不是没有任何依赖关系的重点吗?那么这是否意味着我应该模拟当前在 SendRequest 中耦合的内容,应该将其解耦以便我可以模拟它们(验证、数据库类型等)?
    • 我无法发送新的 Mock();到 SendRequest,它有一个类型不匹配错误,说它不能将新的 Mock 转换为 IPaymentRequest
    • 没关系,我认为这是新的 var preAuthorizeRequestStub = new Mock();然后 preAuthorizeRequestStub.Object
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多