1

想象一下,我有以下课程:

class ToTest : IToTest
{
    public string MethodA()
    {
        return "Test";
    }

    public int MethodB()
    {
        returns a random number;
    }

    public string MethodC()
    {
        return this.MethodA + " " + this.MethodB.ToString();
    }
}

我现在正在测试 MethodC,所以我知道我应该模拟当前实例的 MethodA 和 MethodB,所以我只测试 MethodC 的有效性,对吗?

我正在使用 Rhino,我做了以下事情:

ToTest testedObject = new ToTest();

testedObject.Expect(t => t.MethodA).Returns("AString");
testedObject.Expect(t => t.MethodB).Returns(1324");

Assert.AreEqual("AString 1324", testedObject.MethodC());

但是我得到一个正确的错误,说testedObject不是Mock。

方法对吗?我应该如何进行?

4

4 回答 4

5

不,请不要这样写测试用例。单元测试不是关于“只测试一件事”,它们都是关于确保每个测试都是一个单元,即它对任何其他测试都没有影响。

你所有的测试都应该对你的类的公共 API 感兴趣。不要测试内部或私有方法,它们是类内部工作的一部分,并且不要试图模拟类的某些部分以测试其他部分。您的测试MethodC 必须间接测试MethodAMethodB否则您的测试毫无意义。

对于任何有兴趣就如何编写好的单元测试进行精彩演讲的人,我建议留出一个小时观看 NDC 2013 上的“Ian Cooper:TDD,哪里出了问题”视频

于 2013-10-23T10:32:30.680 回答
3

根据您让我们想象的示例,您的问题的简单答案是您的方法不正确。通过遇到您遇到的麻烦,代码试图告诉您,您在代码中存在一些复杂的问题,这些问题耦合得太紧密了,并且您拥有的不是具有凝聚力的代码,而是具有粘性的代码。(Glenn Vanderburg 在http://www.vanderburg.org/Blog/Software/Development/cohesion.rdoc上有一篇关于凝聚力主题的精彩博客文章)

如果您觉得需要模拟MethodAMethodB测试MethodC这是一个信号,表明代码中缺少一些东西之一,甚至可能所有这些东西。

困难告诉你的第一件事是它MethodC可能在代码中的错误位置。 MethodC很可能属于另一个对象,该对象通过构造函数注入或参数注入将对象与MethodAMethodB作为依赖项。

突出的第二件事是您在测试时遇到了麻烦,MethodC因为MethodB它依赖于导致我认为是全局/单例行为的原因。您已将 定义MethodBreturns a random number,而不是依赖于填充角色 的对象NumberGenerator。通过显式调用此角色(接口)的依赖关系,它将允许您根据使用情况传入角色的不同实现。根据是生产代码还是测试代码,可以使用和轻松更换的三种明显实现是:

  • a RandomNumberGenerator:每次调用时返回一个随机数,
  • a SequentialNumberGenerator:返回序列中的下一个数字(其中可以生成多种类型的序列),例如每次增加一个设定的数字,或者某种数学序列,例如斐波那契或科拉兹
  • a ContsistantNumberGenerator:每次调用都返回相同的数字,这在测试中非常有用。

您的示例可能告诉您的第三件事是对上一期的改进,指出您不仅依赖于您无法控制的可变状态,而且依赖于您无法控制的非确定性可变状态强制进入给定状态,甚至通过向对象发送许多其他消息以尝试进入该状态。我不知道您可以发送任何消息来设置环境以在下次调用时返回所需的随机数以获取它;即使有这样的方法,所需的设置量也会掩盖您实际尝试测试的内容。

我希望这些建议之一可以帮助您解决问题。

于 2013-10-23T14:02:16.687 回答
1

这不起作用有两个原因(假设您使用的是 Rhino.Mocks):

  • 你不能模拟一个非虚拟的方法。
  • 您只能测试整个实例(或静态成员)。这意味着,当您想单独测试类中的方法而没有其他方法时,您需要一些解决方法,即通过提供工厂或将 MethodC 移动到与 MethodA 和 MethodB 不同的类中。
于 2013-10-23T10:29:25.440 回答
1

您的示例的工作代码将如下所示:

public class ToTest
{
    private Random random = new Random();

    public virtual string MethodA()
    {
        return "Test";
    }

    public virtual int MethodB()
    {
        return random.Next();
    }

    public virtual string MethodC()
    {
        return MethodA() + " " + MethodB();
    }
}

[TestFixture]
public class tests
{
    [Test]
    public void Test_MethodC()
    {
        var mocks = new MockRepository();
        ToTest testedObject = mocks.CreateMock<ToTest>();

        testedObject.Expect(t => t.MethodA()).Return("AString");
        testedObject.Expect(t => t.MethodB()).Return(1324);

        Assert.AreEqual("AString 1324", testedObject.MethodC());
    }
}

但!

这将无法通过测试。原因是Rhino.Mocks与大多数其他模拟框架一样,不包括 MS Moles/Shims、TypeMock 和其他使用分析器 API 的框架,无法强制类型以从内部查看模拟方法。这是因为模拟框架围绕原始类型创建了一个代理类型,并将模拟逻辑注入到代理中,而不是原始类型本身。

因此,正如建议的那样,您应该将MethodAMethodB提取到单独的依赖项中,以防您在MethodC中有要测试的东西。

另一种完全可行的方法是从ToTest类派生,覆盖MethodAMethodB并测试派生类的实例。

于 2013-10-23T10:46:09.420 回答