假设我们正在测试一个C类,它有两个方法M1和M2,其中M1在执行时调用M2。
测试M2是好的,但我们如何测试M1?困难在于,如果我没有误解,我们需要模拟M2。
如果是这样,我们如何在测试同一类中定义的方法时模拟另一种方法?
[编辑]
C类没有基类。
答案 0 :(得分:7)
您可以通过将模拟的CallBase属性设置为true
来完成此操作。
例如,如果我有这个类:
public class Foo
{
public virtual string MethodA()
{
return "A";
}
public virtual string MethodB()
{
return MethodA() + "B";
}
}
我可以设置MethodA并调用MethodB:
[Fact]
public void RunTest()
{
Mock<Foo> mockFoo = new Mock<Foo>();
mockFoo.Setup(x => x.MethodA()).Returns("Mock");
mockFoo.CallBase = true;
string result = mockFoo.Object.MethodB();
Assert.Equal("MockB", result);
}
答案 1 :(得分:6)
您应该将对M1
的调用传递给M2
方法的实际实例。
通常,您应该测试类的黑盒行为。您的测试不应该关注M1
恰好调用M2
- 这是一个实现细节。
这与模拟外部依赖项(您应该这样做)不同......
例如,假设我有这样一个类:
class AccountMerger
{
public AccountMerger(IAccountDao dao)
{
this.dao = dao;
}
public void Merge(Account first, Account second, MergeStrategy strategy)
{
// merge logic goes here...
// [...]
dao.Save(first);
dao.Save(second);
}
public void Merge(Account first, Account second)
{
Merge(first, second, MergeStrategy.Default);
}
private readonly IAccountDao dao;
}
我希望我的测试表明:
调用Merge(first, second, strategy)
会导致保存两个已使用提供的规则合并的帐户。
调用Merge(first, second)
会导致保存已使用默认规则合并的两个帐户。
请注意,这两个要求都是根据输入和效果来表达的 - 特别是我并不关心课程如何实现这些结果,只要它。
第二种方法碰巧使用第一种方法并不是我关心的事情,甚至我想强制实施 - 如果我这样做,我会写非常脆弱的测试。 (甚至有一种观点认为,如果你使用模拟框架搞乱了测试中的对象,你甚至不再测试原始对象,那么 你测试的是什么?)这是一个内部依赖关系,在不违反要求的情况下可以很愉快地改变:
// ...
// refactored AccountMerger methods
// these still work, and still fulfil the two requirements above
public void Merge(Account first, Account second, MergeStrategy strategy)
{
MergeAndSave(first, second, strategy ?? MergeStrategy.Default);
}
public void Merge(Account first, Account second)
{
// note this no longer calls the other Merge() method
MergeAndSave(first, second, MergeStrategy.Default);
}
private void MergeAndSave(Account first, Account second, MergeStrategy strategy)
{
// merge logic goes here...
// [...]
dao.Save(first);
dao.Save(second);
}
// ...
只要我的测试只检查输入和效果,我就可以轻松地进行这种重构 - 我的测试甚至可以帮助我这样做,因为他们确保我在进行更改时没有打破课程。
另一方面,我使用AccountMerger
来IAccountDao
在合并后保存帐户(尽管AccountMerger
不应该关心DAO的实现,只是它有Save()
方法。)这个DAO是模拟的主要候选者 - AccountMerger
类的外部依赖,感觉效果我想检查某些输入。
答案 2 :(得分:4)
您不应该在目标类中模拟方法,您应该只模拟外部依赖项。
如果在测试M1时模拟M2似乎有意义,那通常意味着你的班级做了太多事情。考虑拆分类并将M2保持在一个类中并将M1移动到更高级别的类,这将使用包含M2的类。然后嘲笑M2很容易,你的代码实际上会变得更好。