Как тестировать не-публичные методы в .NET
Любишь покрывать код тестами? Тебе нравится приятное теплое чувство защищенности, которое возникает при прохождении тестов? Молодец!
Настоящие профессионалы не полагаются на случай, они стелют соломку заранее держат все под контролем.
Хочешь чтобы внутри, за публичным интерфейсом, тоже все было покрыто тестами?
Как и всегда, способов несколько. Есть получше, есть попроще. Поехали!
1. Делаем метод публичнымНет приватного метода — нет проблемы. Можно еще комментарий к методу написать
// For tests only. Do not use directly. Плюсы: очень просто.Минусы: засоряется интерфейс, страдает инкапсуляция.
2. Делаем метод внутренним То есть меняем модификатор доступа с private на internal. Комментарий из первого способа тут тоже сгодится. Получается метод, который доступен из любого места в пределах сборки.
А тестировать-то его как? Просто. Нужно добавить к сборке атрибут InternalsVisibleTo. Этот атрибут даст тестирующей сборке доступ ко всем internal методам и свойствам тестируемой сборки.
Не забудьте, что для функционирования атрибута InternalsVisibleTo требуется, чтобы обе сборки (тестирующая и тестируемая) были одновременно подписаны строгим именем, либо одновременно не подписаны.
Плюсы: достаточно просто, остается контроль за тем, кто имеет доступ к внутренностям сборки.Минусы: все равно засоряется интерфейс, все равно страдает инкапсуляция, появляются дополнительные условия (см. выше про подписывание), атрибут остается в релизной сборке.
3. Делаем метод защищенным Вместо модификатора private метод должен обзавестись модификатором protected. В тестирующей сборке нужно будет сделать наследника от тестируемого класса и — вуаля! — доступ к методу получен.
Плюсы: достаточно просто, есть некоторый контроль за тем, кто имеет доступ к методу.Минусы: все равно засоряется интерфейс, все равно страдает инкапсуляция, метод доступен всем, кто пожелает его унаследовать, класс с таким методом не может быть помечен закрытым для наследования (sealed).
4. Используем PrivateObject Этот класс предоставляет Visual Studio Unit Testing Framework, так что если в проекте используется NUnit или еще что-то, то этот способ не подойдет.
С PrivateObject все просто. Есть класс для тестирования:
public class ClassToTest { private string field;
private void PrintField () { Console.WriteLine (field); } } Есть тестирующий класс:
[TestClass] public class TestClass { [TestMethod] public void TestPrivateMethod () { ClassToTest testedClass = new ClassToTest (); PrivateObject privateObject = new PrivateObject (testedClass);
privateObject.SetField («field», «Don’t panic»); privateObject.Invoke («PrintField»); } }
После выполнения теста в консоли будет строка Don’t panic. Всегда хороший совет, так ведь?
Плюсы: не требуется менять существующий код, достаточно просто.Минусы: применимо только к Visual Studio Unit Testing Framework, при переименовании полей и методов тесты начнут падать.
5. Рефлексия нам поможет Код будет подлиннее, чем в предыдущем способе, но жить можно.
Опять же, есть класс для тестирования:
public class ClassToTest { private string field;
private void PrintField () { Console.WriteLine (field); } } В тесте нужно написать следующее:
ClassToTest obj = new ClassToTest (); Type t = typeof (ClassToTest);
FieldInfo f = t.GetField («field», BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); f.SetValue (obj, «Don’t panic»);
t.InvokeMember («PrintField», BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, obj, null); В консоли снова должен появиться хороший совет.
Вышеприведенный код можно использовать в любых тестах. Хоть для NUnit, хоть для Visual Studio Unit Testing Framework, хоть для любой другой среды тестирования.
Плюсы: не требуется менять существующий код, достаточно просто, применимо для люой среды тестирования.Минусы: без вспомогательного класса в тестах будут километры повторяющегося кода, при переименовании полей и методов тесты начнут падать.
Вместо заключения Теперь, когда ты знаешь, как тестировать не-публичные методы, самое время задуматься:, а надо ли их тестировать?
Аргументы «за»:
Не-публичные методы могут содержать сложную логику, которую стоит проверять. Иногда использование приватных методов в тестах может уменьшить объем кода, необходимого для создания тестовых данных. Тестировать вообще нужно все. Чем больше строк кода покрыто тестами, тем лучше. Аргументы «против»:
Тестировать нужно поведение, а не реализацию. А значит, тестировать нужно только интерфейс класса (только публичные методы и свойства). Тесты для приватных методов мешают рефакторить код, а рефакторить код не менее важно, чем тестировать. Если метод приватный, то это не просто так. Это значит, что трогать его не надо. Лично я играю за команду «против». Я думаю, что тестировать нужно только публичные методы и свойства.