Unit test защищенный метод в С# с использованием Moq

В последнее время мне стало интересно, что вы можете unit test абстрактные базовые классы использовать 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.Protected, однако, насколько я вижу, он позволяет вам настраивать ожидания, например,

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

Я также знаю, что очевидным ответом здесь является "не тестировать защищенные методы, а только проверять публичные методы", однако это еще одна дискуссия! Я просто хочу знать, возможно ли это с помощью Moq.

Обновление: ниже показано, как я обычно тестировал это:

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

Спасибо заранее.

Ответ 1

Во-первых, нет смысла в модульном тестировании абстрактного метода. Там никакой реализации! Вы можете захотеть unit test нечистого абстрактного класса, проверяя, что был вызван абстрактный метод:

[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 или другой библиотекой, вся суть является переопределяющей реализацией. Тестирование защищенного метода предполагает раскрытие существующей реализации. Это не то, что Мок призван сделать. Protected() просто дает вам доступ (предположительно через отражение, поскольку он основан на строках), чтобы переопределить защищенные члены.

Либо написать класс тестового потомка с помощью метода, который вызывает ваш защищенный метод, либо использовать отражение в unit test для вызова защищенного метода.

Или, еще лучше, не проверяйте защищенные методы напрямую.

Ответ 2

Другим способом в Moq для вызова защищенного элемента является следующий шаблон:

  • В вашем классе с защищенным элементом отметьте свою функцию виртуальной. Например:

    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);
            }
        }
    

Затем в unit test добавьте ссылку на

 using Moq.Protected;

и в unit test вы можете написать следующее:

    [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 Verify не вызывается.

Ответ 3

Вы уже коснулись "тестирования публичного API, а не частного" процесса мышления, и вы уже упоминали технологию наследования от класса, а затем тестировали его защищенные члены таким образом. Оба эти метода являются действительными.

Под всем этим простая истина заключается в том, что вы рассматриваете эту деталь реализации (как то, что является частным или защищенным членом), достаточно важным для проверки напрямую, а не опосредованно через публичный API, который будет ее использовать. Если это важно, возможно, это достаточно важно для продвижения в свой класс. (В конце концов, если это так важно, возможно, это ответственность не должна иметь MyAbstractClass.) Экземпляр класса будет защищен внутри MyAbstractClass, поэтому только базовый и производный типы будут иметь доступ к экземпляру, но сам класс будет полностью проверен в противном случае и применим в другом месте, если это станет необходимостью.

abstract class MyAbstractClass 
{
     protected ImportantMethodDoer doer;
}

class ImportantMethodDoer
{
     public void Do() { }
}

В противном случае вы останетесь * с теми подходами, которые вы уже идентифицировали.


* Moq может или не может обеспечить некоторый механизм для доступа к частным или защищенным членам, я не могу сказать, поскольку я не использую этот конкретный инструмент. Мой ответ больше с архитектурной точки зрения.