Модульный тест защищенного метода в C # с использованием Moq - PullRequest
15 голосов
/ 10 октября 2011

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

Заранее спасибо.

Ответы [ 3 ]

8 голосов
/ 10 октября 2011

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

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

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

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

7 голосов
/ 01 октября 2015

Еще один способ вызова защищенного члена в 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);
            }
        }
    

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

 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 не будет вызываться.

5 голосов
/ 10 октября 2011

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

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

abstract class MyAbstractClass 
{
     protected ImportantMethodDoer doer;
}

class ImportantMethodDoer
{
     public void Do() { }
}

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


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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...