想象一下,我有以下课程:
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());
但是我正确地说错误地说测试对象不是模拟。
方法是否正确?我该怎么办?
答案 0 :(得分:4)
不,请不要写这样的测试用例。单元测试不是“只测试一件事”,而是关于确保每个测试都是一个单元,即它对任何其他测试都没有影响。
您所有的测试应该感兴趣的是您班级的公共API。不要测试内部或私有方法,它们是你班级内部工作的一部分,不要试图模仿你班级的部分来测试其他部分。您对MethodC
的测试必须间接测试MethodA
和MethodB
,否则您的测试毫无意义。
对于有兴趣就如何编写好单元测试的优秀演讲感兴趣的人,我建议留出一小时并观看2013年NDC的'Ian Cooper: TDD, where did it all go wrong' video。
答案 1 :(得分:3)
根据您让我们想象的例子,您问题的简单答案是您的方法不正确。通过遇到您遇到的麻烦,代码试图告诉您,您在代码中有一些混合的问题,这些问题耦合得太紧密,并且您没有使用有凝聚力的代码,而是使用粘性代码。 (Glenn Vanderburg在http://www.vanderburg.org/Blog/Software/Development/cohesion.rdoc发现了一个关于凝聚力主题的博客文章
如果您觉得需要模拟MethodA
和MethodB
来测试MethodC
,这是一个信号,表明其中一些事情,甚至可能全部都缺失了代码。
困难告诉你的第一件事是MethodC
可能在代码中的错误位置。 MethodC
很可能属于另一个以MethodA
和MethodB
作为依赖关系的对象,通过构造函数注入或参数注入。
第二个突出的问题是,您在测试MethodC
时遇到了问题,因为MethodB
依赖于什么会导致我认为是全局/单身行为。您已将MethodB
定义为returns a random number
,而不是依赖于填充NumberGenerator
角色的对象。通过显式调用对此角色的依赖(接口),它将允许您根据用途传递角色的不同实现。根据生产代码或测试代码的不同,可以使用并轻松交换的三个明显的实现是:
RandomNumberGenerator
:每次调用时都会返回一个随机数,SequentialNumberGenerator
:返回序列中的下一个数字(可以生成多种类型的序列),例如每次增加一个设定的数字,或某种类型的数学序列,例如, Fibbonacci或Collatz ContsistantNumberGenerator
:每次调用时返回相同的数字,非常在测试中有用。你的例子可能告诉你的第三件事是对前一个问题的改进,并指出你不仅依赖于你无法控制的可变状态,而且依赖于非确定性的可变状态,你会即使向对象发送大量其他消息以试图进入该状态,也无法强制进入给定状态。我知道您没有可以发送的消息,这些消息将设置环境以在下次调用时返回所需的随机数以获取它;即使有这样的方式,所需的设置量也会模糊你实际想要测试的内容。
我希望其中一条建议可以帮助您解决问题。
答案 2 :(得分:1)
由于两个原因(假设您正在使用Rhino.Mocks),这将无效:
答案 3 :(得分: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());
}
}
<强> BUT!强>
这不会通过测试。原因是 Rhino.Mocks 和大多数其他模拟框架一样,不包括MS Moles / Shims,TypeMock和其他使用profiler API的人,不能强制执行从内部查看模拟方法的类型。这是因为模拟框架围绕原始框架创建代理类型,并将模拟逻辑注入代理,而不是原始类型本身。
因此,正如建议的那样,您应该将 MethodA 和 MethodB 提取到单独的依赖项中,以防您在 MethodC 中进行测试。
另一种完全可行的方法是从 ToTest 类派生,覆盖 MethodA 和 MethodB ,并测试派生的实例类。