Rhino Mock和Mock的概述

3
有没有人或地方可以用通俗易懂的语言解释这个问题,而不是“用自身定义术语”?
4个回答

9

所以,你有一个依赖于其他东西的类。

让我们使用一个比喻:汽车需要发动机。

汽车依赖于发动机。测试汽车和发动机协同工作很容易,但是如果没有发动机测试汽车,或者汽车正确地“调用”发动机呢?我们可以把一些东西(模拟对象)放在发动机的位置上,然后按下油门(进行调用),并验证虚假的(模拟的)发动机是否收到了节气门的正确输入。通过使用模拟对象测量,而不是验证整个系统,您只需隔离地测试您想要的东西。

实际上,它变得更加复杂和更加强大,但是......


3
如果你为你的类编写单元测试,那么在某些时候你会遇到一个情况,即你的测试执行调用外部资源的代码。最常见的情况是数据库,但也可能是其他的。我通常使用信用卡计费服务提供商作为例子,因为在这种情况下,很明显你不想每次运行测试时都实际调用服务。

在这种情况下,通常会用一种不使用任何资源的虚拟服务来替换服务对象。这被称为存根或模拟对象。存根和模拟之间的区别似乎是一些讨论的主题,但本质上它们是相同的。

像Rhino Mock这样的模拟框架可以帮助你创建模拟对象,这些对象的响应方式与实际服务的响应方式相同。你可以将它们与实际服务调用的录音进行比较,每次执行测试时都可以回放。


2
为了真正理解 Mocks,首先必须掌握四个概念:
- 什么是交互测试 - 什么是隔离框架(如 rhino mocks) - 什么是 fake 对象 - 什么是 stub 对象
最后,什么是 mock 对象。
交互测试是指检查一个特定方法(正在测试的方法)是否使用所需的参数调用另一个类(外部依赖项)中的另一个方法。 想象一下,在某个类中,有一个方法记录每次使用无效参数进行调用的记录。为了说明存根(stubs)和模拟对象(mocks)之间的区别,我添加了两个外部依赖项(IStringAnalyzer 和 ILogger)。
class SomeClass
{
    IStringAnalyzer stringAnalizer;
    ILogger logger;

    public SomeClass(IStringAnalyzer stringAnalyzer, ILogger logger)
    {   
        this.logger = logger;
        this.stringAnalyzer = stringAnalyzer;
    }


    public void SomeMethod(string someParameter)
    {
        if (stringAnalyzer.IsValid(someParameter))
        {
            logger.Log("Invalid string");
        }else
        {
            //do something with someParameter
        }
    }
}

在这个例子中,您想要测试使用无效参数调用SomeClass的SomeMethod方法是否会调用ILogger中的log方法,并传递字符串参数“Invalid string”。您不想使用真实的IStringAnalyzer和ILogger实现,因为它们可能存在错误,而且因为这是单元测试,您只想一次测试一个东西,如果您同时测试多个东西,实际上您在进行集成测试。只测试一个东西的原因是,如果测试失败,您立即知道它失败的原因是正在测试的那个东西。
您需要提供两种替代实现,一种是IStringAnalyzer的替代实现,另一种是ILogger的替代实现,以便您可以正确地进行此测试。这些替代实现之间在所需执行的操作方面存在差异。对于IStringAnalyzer,您只需要在调用时返回false,以便要测试的方法将通过您想要测试的代码路径。您确实不关心参数的值(someParameter)。
对于ILogger的日志方法,您希望知道它是否被调用,并且是否使用“Invalid string”进行了调用,以便您可以在测试中进行断言。
这两种IStringAnalyzer和ILogger的替代实现都被称为“伪造”(因为它们伪造了外部依赖项),但其中一个是存根(IStringAnalyzer),另一个是模拟(ILogger)。存根仅用于在测试中将您带到所需的位置(在本例中,IStringAnalyzer的IsValid方法返回false)。模拟是为了允许您检查与外部依赖项的交互是否正确完成(在本例中为ILogger)。有些人将模拟(或此类模拟)称为测试间谍(在我看来是一个更好的名称)。是的,还有其他类型的模拟(尽管我从未使用过)。关于这个的一个很好的来源是Michael Feathers的《与遗留代码一起工作》和Roy Osherove的《单元测试的艺术》。
您可以手动创建存根和模拟,例如:
class StringAnalyzerStub : IStringAnalyzer 
{   
    public bool whatToReturn;

    public StubStringAnalyzerStub(bool whatToReturn)
    {
        this.whatToReturn = whatToReturn;
    }

    public bool IsValid(string parameter)
    {
        return whatToReturn;
    }
}


class LoggerMock : ILogger
{
    public string WhatWasPassedIn;

    public void Log(string message)
    {
        WhatWasPassedIn = message;
    }
}

以下是测试内容:

[Test]
public void SomeMethod_InvalidParameter_CallsLogger
{
    IStringAnalyzer s = new StringAnalyzerStub(false); //will always return false when IsValid is called
    ILogger l = new LoggerMock();
    SomeClass someClass = new SomeClass(s, l);

    someClass.SomeMethod("What you put here doesnt really matter because the stub will always return false");

    Assert.AreEqual(l.WhatWasPassedIn, "Invalid string");
}

手动进行此操作容易出错且难以维护,因此需要像Rhino Mocks这样的隔离框架。它们允许您动态创建这些模拟和存根。以下是使用Rhino Mocks(使用排列、行动、断言语法)的相同测试的示例:

[Test]
public void SomeMethod_InvalidParameter_CallsLogger
{
    Rhino.Mocks.MockRepository mockRepository = new Rhino.Mocks.MockRepository();
    IStringAnalyzer s = mockRepository.Stub<IStringRepository>();
    s.Expect(s => s.IsValid("something, doesnt matter").IgnoreParameters().Return(false);
    ILogger l = mockRepository.DynamicMock<ILogger>();
    l.Log("Invalid string");
    SomeClass someClass = new SomeClass(s, l);
    mockRepository.ReplayAll();

    someClass.SomeMethod("What you put here doesnt really matter because the stub will always return false");

    l.AssertWasCalled(l => l.Log("Invalid string"));
}

不允许用自身来定义术语 :)

声明:我在文本编辑器中编写了所有内容,因此代码中可能会有一些语法错误...


1

Mock是一种代理,您可以指示其以某种方式运行。这在测试中非常有用,因为您可以通过使用模拟对象来替换实际实例来消除依赖性。

与存根不同,模拟对象会跟踪实际使用情况,因此您的测试可以验证模拟对象是否按预期使用。

有关更多信息,请参阅Fowler的文章here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接