犀牛模拟和模拟一般

时间:2009-03-04 00:24:11

标签: unit-testing rhino-mocks

是否有任何地方或任何人可以用简单的英语解释这是如何工作的,而不是“ 根据自己定义术语 ”?

4 个答案:

答案 0 :(得分:9)

所以,你有一个依赖别的东西。

让我们用一个比喻:汽车需要引擎。

汽车依赖发动机。很容易测试汽车和发动机是否一起工作,但是如果没有发动机测试汽车,或者汽车“正确地”调用发动机怎么样呢。 我们能做的是用一些东西(模拟)代替发动机,然后按下汽油(打电话),并验证假(模拟)发动机是否接收到节气门体的正确输入。您只是通过使用模拟对象进行测量,而不是验证整个系统,而只是测试了您想要的东西。

在实践中它变得更复杂,更强大,但是......

答案 1 :(得分:3)

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

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

模拟框架作为Rhino Mock可以帮助您创建模拟对象,以响应您期望的实际服务。您可以将每次执行测试时可以重播的服务调用的实际响应所做的记录进行比较。

答案 2 :(得分:2)

要真正了解Mocks,首先要掌握四个概念

什么是交互测试

什么是隔离框架(如犀牛嘲笑)

什么是假的

什么是存根

最后什么是模拟

交互测试是关于检查特定方法(您正在测试的方法)是否使用它应该使用的参数调用另一个类中的另一个方法(外部依赖项)。 想象一下,在某个类中有一个方法,每次使用无效参数调用时都会记录该方法。为了澄清存根和模拟之间的区别,我添加了2个外部依赖项(IStringAnalyzer和ILooger):

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的方法调用是否使用字符串参数“Invalid string”调用ILogger中的log方法。 你想要使用IStringAnalyzer和ILogger的“真实”实现,因为它们可能有错误,而且因为这是 unit 测试,你只想测试一件事情。时间,如果你测试一次测试几件事你真正做的是集成测试。仅一次测试一件事的原因是,如果您的测试失败,您会立即知道它失败了,因为您正在测试它。

您需要提供两个替代实现,一个用于IStringAnalyzer,另一个用于ILogger,以便您可以正确地执行此测试。根据他们需要做的事情,这些替代实施方案将有所不同。对于IStringAnalyzer,您只需要它,当它被调用时返回false,以便测试中的方法将通过您要测试的代码路径。你真的不关心参数的值(someParameter)。

对于ILogger的日志方法,您想要知道它已被调用,并且使用“无效字符串”调用它,以便您可以在测试中断言。

IStringAnalyzer和ILogger的这两个替代实现都被称为“fakes”(因为它们伪造了外部依赖关系),但是一个是Stub(IStringAnalyzer),另一个是mock(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进行相同测试的方式(使用arrange,act,assert语法):

[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"));
}

没有任何术语定义:)

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

答案 3 :(得分:1)

模拟是一种代理人,你可以指示他以某种方式行事。这对于通过使用模拟对象切换实际实例来测试要消除依赖关系的位置非常有用。

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

Fowler的文章here中的更多信息。