验证基类方法是否被调用的单元测试

8

我有一个基类:

public abstract class MyBaseClass
{
    protected virtual void Method1()
    {    
    }
} 

还有一个派生类:

public class MyDerivedClass : MyBaseClass
{
    public void Method2()
    {
        base.Method1();
    }
}

我想为Method2编写一个单元测试,以验证它是否调用了基类上的Method1。我使用的是Moq作为我的mocking库。这个可能吗?
我发现了一个相关的SO链接:Mocking a base class method call with Moq,其中第二个答案建议在mock对象上将CallBase属性设置为true来实现。然而,这并不清楚如何使基类方法(上面例子中的Method1)的调用得到验证。
感谢您的帮助。

2
为什么要求调用基本方法?如果您模拟它,那么您只是验证_mock_是否调用了基本方法。您只是验证base.语法是否有效,还是需要继承类调用基本方法? - D Stanley
2
此外,您应该仅测试结果是否正确。测试特定实现的使用通常表明设计存在问题。 - D Stanley
1
基础方法是否有可以验证的副作用? - D Stanley
@DStanley 是的,有一些可以验证的副作用,我想那可能是我要走的路线。我只是想知道是否使用 Moq 等模拟库确实可以验证从派生类调用了基类方法。 - aw1975
可能吧,但验证“结果”而不是“实现”更有意义。此外,这样可以使您在不必重写测试的情况下更改实现(只要“结果”正确)。 - D Stanley
显示剩余2条评论
3个回答

17

单元测试应该验证行为,而不是实现。 有几个原因:

  • 结果是目标,而不是如何得到结果
  • 测试结果可以让您在不重写测试的情况下改进实现
  • 实现更难模拟

可能能够添加钩子或创建模拟以验证基础方法是否被调用,但您真的关心答案是如何达成的吗? 还是只关心答案是否正确

如果您需要的特定实现具有可验证的副作用,则应验证那个副作用。


2
好答案(+1)...对于提问者还有一件重要的事情:如果您不验证被测试方法的预期行为,那么基类中的未来更改可能会在没有任何通知的情况下破坏CUT行为... - Old Fox
如果您没有测试实现细节,那么如何确保新更改不会破坏必须执行的关键步骤呢?例如,class Query 调用 Execute 方法,该方法本身又调用两个方法 WhereSort。假设我是一名新员工,并且我删除了对 Sort 的调用,现在我的类就无法按预期执行。 - Adrian
@Adrian 单元测试应该验证结果仍然正确(也就是说在这种情况下它们被正确地排序)。我的观点是,从单元测试的角度来看,您不应该关心是否调用特定的“Sort”方法或调用不同的“Sort”方法,或只是在“Execute”方法内部排序。 - D Stanley
好的,这完全有道理。这实际上帮助我以不同的方式思考单元测试。谢谢。 - Adrian

6

从派生类的角度模拟基类是不可能的。在你的简单示例中,我建议使用以下两个选项之一。

选项1:如果MyDerivedClass确实不关心MyBaseClass在做什么,那么使用依赖注入!抽象化万岁!

public class MyClass
{
    private readonly IUsedToBeBaseClass myDependency;

    public MyClass(IUsedToBeBaseClass myDependency){
        _myDependency = myDependency;
    }

    public void Method2()
    {
        _myDependency.Method1();
    }
}

在测试领域的其他地方...

[TestClass]
public class TestMyDependency {
    [TestMethod]
    public void TestThatMyDependencyIsCalled() {
        var dependency = new Mock<IUsedToBeBaseClass>();
        var unitUnderTest = new MyClass(dependency.Object);
        var unitUnderTest.Method2();
        dependency.Verify(x => x.Method1(), Times.Once);
    }
}

选项2:如果需要知道正在做什么,那么就测试是否正在做正确的事情。
在替代测试领域中...
[TestClass]
public class TestMyDependency {
    [TestMethod]
    public void TestThatMyDependencyIsCalled() {
        var unitUnderTest = new MyDerivedClass();
        var unitUnderTest.Method2();
        /* verify base class behavior #1 inside Method1() */
        /* verify base class behavior #2 inside Method1() */
        /* ... */
    }
}

4
你所描述的不是对你代码的测试,而是对该语言行为的测试。这样做是好的,因为它可以确保语言的行为符合我们的预期。当我在学习时,我会写很多小的控制台应用程序。如果我当时知道单元测试就好了,因为它是更好的方法。
但是,一旦你测试并确认语言的行为与你期望的一致,我就不会继续为此编写测试。你只需要测试代码的行为即可。
以下是一个非常简单的示例:
public class TheBaseClass
{
    public readonly List<string> Output = new List<string>();

    public virtual void WriteToOutput()
    {
        Output.Add("TheBaseClass");
    }
}

public class TheDerivedClass : TheBaseClass
{
    public override void WriteToOutput()
    {
        Output.Add("TheDerivedClass");
        base.WriteToOutput();
    }
}

单元测试

    [TestMethod]
    public void EnsureDerivedClassCallsBaseClass()
    {
        var testSubject = new TheDerivedClass();
        testSubject.WriteToOutput();
        Assert.IsTrue(testSubject.Output.Contains("TheBaseClass"));
    }

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