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

本文关键字:单元测试 保护 方法 Moq | 更新日期: 2023-09-27 18:10:56

最近引起了我的注意,您可以使用Moq对抽象基类进行单元测试,而不是在测试中创建一个实现抽象基类的虚拟类。参见如何使用moq测试抽象类中的具体方法?例如:

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。受保护的命名空间,但据我所知,它只允许您设置期望,例如

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

我也知道这里明显的答案是"不要测试受保护的方法,只测试公共方法",但这是另一个争论!我只是想知道是否可以使用最小起订量。

Update:下面是我通常测试的方法:

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

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

在Moq中调用protected member的另一种方式是:

  1. 在你的类中,用protected成员标记你的函数为virtual。例如:

    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);
            }
        }
    
然后在单元测试中添加对 的引用
 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。应该用它来代替它。另一个难题在可验证店等着你。我不知道为什么,但是如果不调用可验证验证将不会被调用

对于初学者来说,对抽象方法进行单元测试是没有意义的。没有实现!您可能想要对非纯抽象类进行单元测试,以验证是否调用了抽象方法:

[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或其他库创建mock时,重点是重写实现。测试受保护的方法需要公开现有的实现。这不是Moq的设计目的。Protected()只允许您访问(可能是通过反射,因为它是基于字符串的)来覆盖受保护的成员。

要么编写一个带有调用受保护方法的方法的测试后代类,要么在单元测试中使用反射来调用受保护的方法。

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

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

在这一切背后,一个简单的事实是,您认为这个实现细节(因为这就是私有或受保护的成员)足够重要,可以直接测试,而不是通过将使用它的公共API间接测试。如果它这么重要,也许它足够重要,可以提升到它自己的类。(毕竟,如果它如此重要,也许MyAbstractClass不应该承担这样的责任。)类的实例将在MyAbstractClass中受到保护,因此只有基类型和派生类型可以访问实例,但是类本身将是完全可测试的,如果需要,可以在其他地方使用。

abstract class MyAbstractClass 
{
     protected ImportantMethodDoer doer;
}
class ImportantMethodDoer
{
     public void Do() { }
}

否则,您就只能使用您已经确定的方法了。


*Moq可能会也可能不会提供一些机制来获取私有或受保护的成员,我不能说,因为我不使用那个特定的工具。我的回答更多是从建筑的角度出发。