Как вы тестируете приватные методы? - PullRequest
466 голосов
/ 30 октября 2008

Я создаю библиотеку классов, которая будет иметь несколько открытых и закрытых методов. Я хочу иметь возможность тестировать приватные методы модульно (в основном при разработке, но также может быть полезно для будущего рефакторинга).

Как правильно это сделать?

Ответы [ 31 ]

340 голосов
/ 30 октября 2008

Если вы хотите выполнить юнит-тест частного метода, возможно, что-то не так. Модульные тесты (вообще говоря) предназначены для проверки интерфейса класса, то есть его открытых (и защищенных) методов. Конечно, вы можете «взломать» решение этой проблемы (даже если просто сделаете методы общедоступными), но вы также можете подумать:

  1. Если метод, который вы хотите протестировать, действительно стоит протестировать, возможно, стоит перенести его в свой собственный класс.
  2. Добавьте дополнительные тесты к публичным методам, которые вызывают приватный метод, тестируя функциональность приватного метода. (Как указали комментаторы, вы должны делать это только в том случае, если функциональность этих частных методов действительно является частью открытого интерфейса. Если они фактически выполняют функции, скрытые от пользователя (то есть модульное тестирование), это, вероятно, плохо).
118 голосов
/ 30 октября 2008

Если вы используете .net, вы должны использовать InternalsVisibleToAttribute .

115 голосов
/ 29 октября 2010

Может быть бесполезно тестировать частные методы. Однако мне также иногда нравится вызывать закрытые методы из тестовых методов. Большую часть времени, чтобы предотвратить дублирование кода для генерации тестовых данных ...

Microsoft предоставляет для этого два механизма:

Accessors

  • Перейти к исходному коду определения класса
  • Щелкните правой кнопкой мыши на названии класса
  • Выберите «Создать приватный метод доступа»
  • Выберите проект, в котором должен быть создан метод доступа => Вы получите новый класс с именем foo_accessor. Этот класс будет динамически генерироваться во время компиляции и будет доступен всем доступным членам.

Однако этот механизм иногда немного усложняется, когда дело доходит до изменений интерфейса исходного класса. Поэтому в большинстве случаев я избегаю этого.

Класс PrivateObject Другой способ - использовать Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
76 голосов
/ 19 октября 2010

Я не согласен с философией «вас должно интересовать только тестирование внешнего интерфейса». Это все равно, что сказать, что в автосервисе должны быть только тесты, чтобы увидеть, вращаются ли колеса. Да, в конечном счете, я заинтересован во внешнем поведении, но мне нравятся мои собственные, частные, внутренние тесты, чтобы они были более конкретными и конкретными. Да, если я проведу рефакторинг, мне, возможно, придется изменить некоторые тесты, но если это не масштабный рефакторинг, мне придется изменить только некоторые из них, и тот факт, что другие (неизменные) внутренние тесты все еще работают, является отличным показателем того, что рефакторинг прошел успешно.

Вы можете попытаться охватить все внутренние случаи, используя только общедоступный интерфейс, и теоретически возможно полностью протестировать каждый внутренний метод (или, по крайней мере, каждый существенный), используя общедоступный интерфейс, но вам, возможно, придется оказаться на своем Для достижения этой цели и связи между тестовыми примерами, выполняемыми через открытый интерфейс, и внутренней частью решения, которое они предназначены для тестирования, может быть трудно или невозможно различить. Указав, что отдельные тесты, которые гарантируют, что внутренний механизм работает должным образом, стоят тех незначительных изменений, которые произошли с рефакторингом - по крайней мере, это был мой опыт. Если вам приходится вносить огромные изменения в свои тесты для каждого рефакторинга, то, возможно, это не имеет смысла, но в этом случае, возможно, вам следует полностью переосмыслить свой дизайн. Хороший дизайн должен быть достаточно гибким, чтобы можно было вносить большинство изменений без значительных изменений.

50 голосов
/ 30 октября 2008

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

Класс:

...

protected void APrivateFunction()
{
    ...
}

...

Подкласс для тестирования:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...
22 голосов
/ 24 октября 2012

Я думаю, что следует задать более фундаментальный вопрос: почему вы пытаетесь сначала протестировать приватный метод? Это запах кода, когда вы пытаетесь протестировать закрытый метод через открытый интерфейс этого класса, тогда как этот метод является частным по причине, так как это деталь реализации. Нужно заботиться только о поведении открытого интерфейса, а не о том, как он реализован под прикрытием.

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

Продукт рефакторинга означает, что закрытый метод теперь является отдельным классом, который стал соавтором исходного класса. Его поведение станет понятным благодаря собственным модульным тестам.

Затем я могу посмеяться над его поведением при попытке проверить исходный класс, чтобы затем сосредоточиться на проверке поведения открытого интерфейса этого класса, а не на проверке комбинаторного взрыва открытого интерфейса и поведения всех его частные методы.

Я вижу это аналогично вождению автомобиля. Когда я веду машину, я не еду с поднятым капотом, поэтому я вижу, что двигатель работает. Я полагаюсь на интерфейс, который обеспечивает автомобиль, а именно на счетчик оборотов и спидометр, чтобы знать, что двигатель работает. Я полагаюсь на то, что машина действительно движется, когда я нажимаю педаль газа. Если я хочу проверить двигатель, я могу проверить его изолированно. : D

Конечно, непосредственное тестирование частных методов может быть последним средством, если у вас есть унаследованное приложение, но я бы предпочел, чтобы унаследованный код был реорганизован для обеспечения лучшего тестирования. Майкл Фезерс написал прекрасную книгу на эту тему. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052

17 голосов
/ 25 мая 2010

Частные типы, внутренние и частные члены таковы по какой-то причине, и часто вы не хотите связываться с ними напрямую. И если вы это сделаете, есть вероятность, что вы сломаетесь позже, потому что нет никакой гарантии, что парни, создавшие эти сборки, сохранят частные / внутренние реализации как таковые.

Но иногда, когда я выполнял некоторые хаки / исследования скомпилированных или сторонних сборок, я сам заканчивал тем, что хотел инициализировать частный класс или класс с помощью частного или внутреннего конструктора. Или иногда, когда я имею дело с предварительно скомпилированными устаревшими библиотеками, которые я не могу изменить - я заканчиваю тем, что пишу несколько тестов для частного метода.

Так родился AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - это быстрый класс-обертка, который облегчит работу с помощью динамических функций и отражения C # 4.0.

Вы можете создавать внутренние / частные типы, такие как

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();
12 голосов
/ 27 марта 2012

Ну, вы можете использовать приватный метод для тестирования двумя способами

  1. вы можете создать экземпляр класса PrivateObject, синтаксис которого следующий:

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
    
  2. Вы можете использовать отражение.

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });
    
10 голосов
/ 30 октября 2008

Я также использовал метод InternalsVisibleToAttribute. Стоит также упомянуть, что, если вы чувствуете себя некомфортно, когда ваши собственные приватные методы становятся внутренними для достижения этой цели, то, возможно, они все равно не должны быть предметом прямых модульных тестов.

В конце концов, вы тестируете поведение вашего класса, а не специфическую реализацию - вы можете изменить последний, не изменяя первый, и ваши тесты все равно должны пройти.

9 голосов
/ 22 июля 2011

Есть 2 типа приватных методов. Статические частные методы и нестатические частные методы (методы экземпляра). В следующих 2 статьях объясняется, как выполнить модульное тестирование частных методов на примерах.

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