使用Moq在C#中进行单元测试保护的方法

时间:2011-10-10 16:11:55

标签: c# moq

最近我注意到你可以使用Moq对抽象基类进行单元测试,而不是在测试中创建一个实现抽象基类的虚拟类。见How to use moq to test a concrete method in an abstract class?例如。你可以这样做:

public abstract class MyAbstractClass 
{
    public virtual void MyMethod()
    {
        // ...    
    }
}

[Test]
public void MyMethodTest()
{
    // Arrange            
    Mock<MyAbstractClass> mock = new Mock<MyAbstractClass>() { CallBase = true };

    // Act
    mock.Object.MyMethod();

    // Assert
    // ... 
}

现在我想知道是否有类似的技术允许我测试受保护的成员而无需创建包装类。即你如何测试这种方法:

public class MyClassWithProtectedMethod
{
    protected void MyProtectedMethod()
    {

    }
}

我知道Moq.Protected命名空间,但据我所知,它只允许你设置期望,例如。

mock.Protected().Setup("MyProtectedMethod").Verifiable();

我也知道这里显而易见的答案是“不测试受保护的方法,只测试公共方法”,然而这是另一场辩论!我只想知道是否可以使用Moq。

更新:以下是我正常测试的方法:

public class MyClassWithProtectedMethodTester : MyClassWithProtectedMethod
{
    public void MyProtectedMethod()
    {
        base.MyProtectedMethod();
    }
}

提前致谢。

3 个答案:

答案 0 :(得分:8)

对于初学者来说,单元测试抽象方法毫无意义。没有实施!您可能希望对不纯的抽象类进行单元测试,验证是否调用了抽象方法:

[Test]
public void Do_WhenCalled_CallsMyAbstractMethod()
{
    var sutMock = new Mock<MyAbstractClass>() { CallBase = true };
    sutMock.Object.Do();
    sutMock.Verify(x => x.MyAbstractMethod());
}

public abstract class MyAbstractClass
{
    public void Do()
    {
        MyAbstractMethod();
    }

    public abstract void MyAbstractMethod();
}

请注意,我将CallBase设置为部分模拟,以防Do是虚拟的。否则,Moq将取代Do方法的实现。

使用Protected()可以验证是否以类似方式调用了受保护的方法。

使用Moq或其他库创建模拟时,重点是覆盖实现。测试受保护的方法涉及公开现有实现。这不是Moq的目的。 Protected()只是让你访问(大概是通过反射,因为它是基于字符串的)来覆盖受保护的成员。

使用调用受保护方法的方法编写测试后代类,或者在单元测试中使用反射来调用受保护方法。

或者,更好的是,不要直接测试受保护的方法。

答案 1 :(得分:6)

Moq调用受保护成员的另一种方法是以下模板:

  1. 在您的班级中,受保护的成员会将您的功能标记为虚拟。 例如:

    public class ClassProtected
        {
            public string CallingFunction(Customer customer)
            {
                var firstName = ProtectedFunction(customer.FirstName);
                var lastName = ProtectedFunction(customer.LastName);
    
                return string.Format("{0}, {1}", lastName, firstName);
            }
    
            protected virtual string ProtectedFunction(string value)
            {
                return value.Replace("SAP", string.Empty);
            }
        }
    
  2. 然后在您的单元测试中添加对

    的引用
     using Moq.Protected;
    

    在您的单元测试中,您可以编写以下内容:

        [TestFixture]
        public class TestClassProttected
        {
            [Test]
            public void all_bad_words_should_be_scrubbed()
            {
                //Arrange
                var mockCustomerNameFormatter = new Mock<ClassProtected>();
    
                mockCustomerNameFormatter.Protected()
                    .Setup<string>("ProtectedFunction", ItExpr.IsAny<string>())
                    .Returns("here can be any value")
                    .Verifiable(); // you should call this function in any case. Without calling next Verify will not give you any benefit at all
    
                //Act
                mockCustomerNameFormatter.Object.CallingFunction(new Customer());
    
                //Assert
                mockCustomerNameFormatter.Verify();
            }
        }
    

    记下ItExpr。它应该用来代替它。另一个问题在Verifiable等着你。我不知道为什么,但是没有调用Verifiable Verify就不会被调用。

答案 2 :(得分:5)

您已经触及过“测试公共API,而不是私有”的​​思维过程,并且您已经提到了从类继承然后以这种方式测试其受保护成员的技术。这两种都是有效的方法。

在这一切之下,简单的事实是,您认为这个实现细节(因为这是私有或受保护的成员)非常重要,可以直接测试而不是通过使用它的公共API间接测试。如果它 这很重要,也许重要的是推广到它的自己的类。 (毕竟,如果它非常重要,也许MyAbstractClass不应该有责任。)类的实例将在MyAbstractClass内受到保护,因此只有基类和派生类型才能访问对于实例,但是如果需要的话,类本身将是完全可测试的,否则可以在其他地方使用。

abstract class MyAbstractClass 
{
     protected ImportantMethodDoer doer;
}

class ImportantMethodDoer
{
     public void Do() { }
}

否则,您将离开*已经确定的方法。


* Moq可能会或可能不会提供一些获取私人或受保护成员的机制,我不能说,因为我不使用该特定工具。我的答案更多来自建筑的立场。