Как вы тестируете приватные методы?

479

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

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

Эрик Лабашоски
источник
3
Возможно, я что-то упускаю, или, может быть, просто этот вопрос, ну ... pre-historicс точки зрения интернет-лет, но модульное тестирование частных методов теперь легко и просто: Visual Studio создает необходимые классы доступа, когда это необходимо, и предварительно заполнив логику тестов фрагментами, чертовски близкими к тому, что можно пожелать для простых функциональных тестов. Смотрите, например. msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx
mjv
3
Это кажется почти дубликатом stackoverflow.com/questions/34571/… .
Raedwald
Спрашивающий, возможно, не использует визуальную студию
Дейв
3
Не проводите модульное тестирование внутренних компонентов
Марк

Ответы:

122

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

TcKs
источник
86
Тьфу. Это компилируется в ваши выпущенные сборки.
Джей
14
@Jay - может не один использовать #if DEBUGвокруг InternalsVisibleToатрибута , чтобы сделать это не относится к коду выпуска?
mpontillo
20
@ Майк, вы могли бы, но тогда вы можете запускать модульные тесты только на отладочном коде, но не на выпуск кода. Поскольку код выпуска оптимизирован, вы можете увидеть различное поведение и разные временные параметры. В многопоточном коде это означает, что ваши модульные тесты не будут надлежащим образом определять условия гонки. Гораздо лучше использовать отражение с помощью предложенного @ AmazedSaint ниже или использовать встроенный PrivateObject / PrivateType. Это позволяет вам видеть рядовых в сборках Release, предполагая, что ваш тестовый жгут работает с полным доверием (что делает MSTest, работающий локально)
Jay
121
Что мне не хватает? Почему это будет принятый ответ, если он на самом деле не отвечает на конкретный вопрос тестирования частных методов? InternalsVisibleTo предоставляет только методы, помеченные как внутренние, а не те, которые помечены как частные, как было запрошено OP (и причина, по которой я здесь оказался) Я думаю, что мне нужно продолжать использовать PrivateObject в ответ на Seven?
Даррен Льюис
6
@ Джей, я знаю, что это немного поздно, но один из вариантов - использовать что-то похожее #if RELEASE_TESTна то, InternalsVisibleToчто предлагает Майк, и сделать копию своей конфигурации сборки релиза, которая определяет RELEASE_TEST. Вы можете протестировать свой код релиза с оптимизацией, но когда вы действительно соберете релиз, ваши тесты будут пропущены.
Шаз
349

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

  1. Если метод, который вы хотите протестировать, действительно стоит протестировать, возможно, стоит перенести его в свой собственный класс.
  2. Добавьте больше тестов к открытым методам, которые вызывают приватный метод, тестируя функциональность частного метода. (Как отметили комментаторы, вы должны делать это только в том случае, если функциональность этих частных методов действительно является частью открытого интерфейса. Если они фактически выполняют функции, скрытые от пользователя (то есть модульное тестирование), это, вероятно, плохо).
Йерун Хейманс
источник
38
Вариант 2 заставляет модульные тесты иметь знания о базовой реализации функции. Мне не нравится это делать. Я вообще думаю, что модульные тесты должны проверять функцию, не предполагая ничего о реализации.
Herms
15
Недостатки тестовой реализации в том, что тесты будут хрупкими, чтобы их сломать, если вы внесете какие-либо изменения в реализацию. И это нежелательно, поскольку рефакторинг так же важен, как написание тестов в TDD.
JtR
30
Ну, тесты должны прерваться, если вы измените реализацию. TDD будет означать изменение тестов в первую очередь.
sleske
39
@Sleske - Я не совсем согласен. Если функциональность не изменилась, то нет причин прерывать тест, поскольку тесты действительно должны тестировать поведение / состояние, а не реализацию. Вот что имел в виду jtr, делая ваши тесты хрупкими. В идеальном мире вы должны иметь возможность реорганизовать свой код, и ваши тесты по-прежнему будут проходить, подтверждая, что ваш рефакторинг не изменил функциональность вашей системы.
Alconja
26
Извиняюсь за наивность (пока не так много опыта в тестировании), но разве не идея модульного тестирования для тестирования каждого модуля кода самостоятельно? Я не очень понимаю, почему частные методы должны быть исключены из этой идеи.
OMill
118

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

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[] {/* ... */} );
Семь
источник
2
Как вы вызываете частные статические методы?
StuperUser
18
Частные средства доступа устарели в Visual Studio 2012 .
Райан Гейтс
3
Метод доступа для тестирования Private Methods устарел с VS 2011 года. blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
Санджит
1
Читая документы , найденные на веб - сайте Microsoft, здесь я не вижу никакого упоминания о классе PrivateObject устаревшим. Я использую MSVS 2013, и он работает, как ожидалось.
stackunderflow
3
@RyanGates Первое решение на сайте, на который вы ссылались, гласит, что решение для доступа к закрытым членам состоит в том, чтобы «Использовать класс PrivateObject для облегчения доступа к внутренним и частным API в вашем коде. Это можно найти в Microsoft.VisualStudio.QualityTools.UnitTestFramework. Сборка длл. "
stackunderflow
78

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

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

Даррелл Планк
источник
20
Боюсь, я все еще не согласен с вами. Обработка каждого компонента как черного ящика позволяет без проблем заменять модули. Если у вас есть FooServiceчто делать X, все, о чем вы должны заботиться, это то, что оно действительно делает по Xзапросу. Как это происходит, не имеет значения. Если в классе есть проблемы, не обнаруживаемые через интерфейс (маловероятно), это все еще допустимо FooService. Если это проблема , которая является видимой через интерфейс, тест на публичных членах должен обнаружить его. Все дело в том, что пока колесо правильно вращается, его можно использовать как колесо.
Basic
5
Один общий подход заключается в том, что если ваша внутренняя логика достаточно сложна, и вы чувствуете, что она требует модульного тестирования, возможно, ее необходимо извлечь в некоторый вспомогательный класс с открытым интерфейсом, который можно тестировать модулем. Тогда ваш «родительский» класс может просто использовать этого помощника, и каждый может пройти соответствующее тестирование.
Мир
9
@Basic: Совершенно неверная логика в этом ответе. Классический случай, когда вам нужен закрытый метод, - это когда вам нужно, чтобы какой-то код использовался публичными методами. Вы помещаете этот код в какой-то PrivMethod. Этот метод не должен быть открыт для общественности, но нуждается в тестировании, чтобы убедиться, что открытые методы, которые полагаются на PrivMethod, действительно могут полагаться на него.
Дима
6
@Dima Конечно, тогда, если есть проблема с PrivMethodтестом, PubMethodкакие вызовы PrivMethodдолжны это выявить? Что происходит, когда вы меняете SimpleSmtpServiceна GmailService? Внезапно ваши частные тесты указывают на код, который больше не существует или, возможно, работает по-другому и не сможет работать, даже если приложение может работать идеально, как задумано. Если есть сложная обработка, которая применима к обоим отправителям электронной почты, возможно, она должна быть в той, EmailProcessorкоторая может использоваться обоими и проверяться отдельно?
Basic
2
@miltonb Мы можем смотреть на это с разных стилей разработки. WRT внутренности, я не склонен их тестировать. Если есть проблема (как выявлено в тестах интерфейса), ее легко отследить, подключив отладчик, или класс слишком сложен и должен быть разбит (с открытым интерфейсом протестированного модуля новых классов) IMHO
Basic
51

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

Класс:

...

protected void APrivateFunction()
{
    ...
}

...

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

...

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

...
Джейсон Джексон
источник
1
Вы даже можете поместить этот дочерний класс в файл модульного теста вместо того, чтобы загромождать реальный класс. +1 за хитрость.
Тим Абелл
Я всегда помещаю весь связанный с тестами код в проект модульных тестов, если это возможно. Это был просто псевдо-код.
Джейсон Джексон
2
Эта функция не является частной, она защищена, итоговый результат ... вы сделали свой код менее защищенным / подверженным частным функциям дочерних типов
War
22

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

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

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

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

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

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

Большая Кахуна
источник
16
Совершенно неверная логика в этом ответе. Классический случай, когда вам нужен закрытый метод, - это когда вам нужно, чтобы какой-то код использовался публичными методами. Вы помещаете этот код в некоторый PrivMethod. Этот метод не должен быть открыт для общественности, но нуждается в тестировании, чтобы убедиться, что открытые методы, которые полагаются на PrivMethod, действительно могут полагаться на него.
Дима
Имеет смысл во время начальной разработки, но хотите ли вы тестировать закрытые методы в вашем стандартном регрессионном костюме? Если это так, если реализация изменится, это может нарушить набор тестов. OTOH, если ваши регрессионные тесты фокусируются только на видимых извне открытых методах, то, если приватный метод ломается позже, набор регрессии все равно должен обнаружить ошибку. Затем, если необходимо, вы можете стереть старый частный тест, если это необходимо.
Алекс Блейкмор
9
Не согласен, вы должны тестировать только открытый интерфейс, иначе зачем нужны частные методы. Сделайте их всех публичными в этом случае и протестируйте их все. Если вы тестируете приватные методы, вы нарушаете окружение. Если вы хотите протестировать закрытый метод, и он используется в нескольких открытых методах, то его следует переместить в его собственный класс и протестировать изолированно. Затем все публичные методы должны делегировать этот новый класс. Таким образом, у вас все еще есть тесты для интерфейс исходного класса, и вы можете убедиться, что поведение не изменилось, и у вас есть отдельные тесты для делегированного частного метода.
Большая Кахуна
@ Big Kahuna - Если вы думаете, что нет случая, когда вам нужно тестировать частные методы, вы никогда не работали с большим / достаточно сложным проектом. Много раз публичная функция, такая как специфичные для клиента валидации, заканчивается 20 строками, просто вызывающими очень простые приватные методы, чтобы сделать код более читабельным, но вам все равно придется тестировать каждый отдельный приватный метод. 20-кратное тестирование публичной функции затруднит дебют, когда модульные тесты не пройдут.
Pedro.The.Kid
1
Я работаю в компании FTSE 100. Я думаю, что видел несколько сложных проектов в свое время, спасибо. Если вам необходимо выполнить тестирование до этого уровня, то каждый закрытый метод в качестве отдельных соавторов должен тестироваться изолированно, поскольку это подразумевает, что у них есть индивидуальное поведение, которое требует тестирования. Тест для основного объекта-посредника становится тестом взаимодействия. Это просто проверка правильности выбранной стратегии. Ваш сценарий звучит так, будто рассматриваемый класс не следует SRP. У него нет единственной причины для изменения, но 20 => нарушение SRP. Прочитайте книгу ГСНО или дядю Боба. YMWV
Большая
17

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

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

Так родился 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();
amazedsaint
источник
12

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

  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 });
неизвестный
источник
Хороший ответ, но для №1 ваш синтаксис неправильный. Вы должны объявить экземпляр PrivateClassfirst и использовать его. stackoverflow.com/questions/9122708/…
SharpC
10

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

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

philsquared
источник
Мне нравится пункт о тестировании поведения, а не реализации. Если вы привязываете свои модульные тесты к реализации (приватные методы), то эти тесты станут хрупкими и их придется менять при изменении реализации.
Боб Хорн
9

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

  1. Модульное тестирование статических частных методов
  2. Модульное тестирование нестатических частных методов
Venkat
источник
Приведите несколько примеров, а не только дайте ссылку
vinculis
Выглядит некрасиво Нет смысла Плохое решение от MS. Я в шоке!
alerya
Самый простой способ проверить приватные статические методы
Грант
8

MS Test имеет хорошую встроенную функцию, которая делает приватные члены и методы доступными в проекте, создав файл с именем VSCodeGenAccessors

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

С классами, производными от BaseAccessor

такие как

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }
Маркус Кинг
источник
8
Файлы gen'd существуют только в VS2005. В 2008 году они были созданы за кулисами. И они мерзость. А связанная с ним задача Shadow является нестабильной на сервере сборки.
Рубен Бартелинк
Также средства доступа были объявлены устаревшими в VS2012-2013.
Зефан Шредер
5

На CodeProject есть статья, в которой кратко обсуждаются плюсы и минусы тестирования частных методов. Затем он предоставляет некоторый код отражения для доступа к закрытым методам (аналогично коду, представленному Маркусом выше). Единственная проблема, которую я обнаружил в примере, заключается в том, что код не учитывает перегруженные методы.

Вы можете найти статью здесь:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

Pedro
источник
4

Объявите их internal, а затем используйте, InternalsVisibleToAttributeчтобы разрешить их сборке тестового модуля.

Джеймс Керран
источник
11
Мне не нравится использовать InternalsVisibleTo, потому что я сделал метод частным по причине.
swilliams
4

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

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

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

Причина в том, что, как только вы начинаете тестировать закрытые методы и внутренние компоненты вашего класса, вы связываете реализацию вашего класса (личные вещи) с вашими тестами. Это означает, что когда вы решите изменить детали реализации, вам также придется изменить свои тесты.

По этой причине вам следует избегать использования InternalsVisibleToAtrribute.

Вот большой доклад Иана Купера, который освещает эту тему: Ian Cooper: TDD, где все пошло не так

cda01
источник
3

Иногда бывает полезно протестировать частные объявления. По сути, компилятор имеет только один открытый метод: Compile (string outputFileName, params string [] sourceSFileNames). Я уверен, что вы понимаете, что было бы сложно протестировать такой метод без проверки каждого «скрытого» объявления!

Вот почему мы создали Visual T #: чтобы облегчить тестирование. Это бесплатный язык программирования .NET (совместимый с C # v2.0).

Мы добавили оператор «.-». Это просто ведет себя как "." оператор, за исключением того, что вы также можете получить доступ к любой скрытой декларации из ваших тестов, не меняя ничего в вашем тестируемом проекте.

Взгляните на наш веб-сайт: загрузите его бесплатно .

Людовик Дюбуа
источник
3

Я удивлен, что никто еще не сказал этого, но решение, которое я использовал, состоит в том, чтобы создать статический метод внутри класса, чтобы проверить себя. Это дает вам доступ ко всему общему и частному для тестирования.

Кроме того, на языке сценариев (с возможностями OO, такими как Python, Ruby и PHP), вы можете сами выполнить проверку файла при запуске. Хороший быстрый способ убедиться, что ваши изменения ничего не сломали. Это, очевидно, делает масштабируемое решение для тестирования всех ваших классов: просто запустите их все. (Вы также можете сделать это на других языках с помощью void main, которая также всегда выполняет свои тесты).

деревенщина
источник
1
Хотя это практично, но не очень элегантно. Это может создать некоторую путаницу в кодовой базе, а также не позволит вам отделить ваши тесты от вашего реального кода. Возможность внешнего тестирования открывает возможность автоматизировать тестирование с помощью сценариев вместо написания статических методов вручную.
Даррен Рейд
Это не мешает вам проводить внешнее тестирование ... просто вызовите статический метод так, как вам нравится. Кодовая база также не беспорядочная ... вы называете метод соответствующим образом. Я использую "runTests", но ничего подобного работает.
Руб
Ваше право, это не препятствует внешнему тестированию, однако генерирует намного больше кода, то есть делает кодовую базу грязной. Каждый класс может иметь множество закрытых методов для тестирования, которые инициализируют свои переменные в одном или нескольких своих конструкторах. Для тестирования вам нужно написать как минимум столько же статических методов, сколько существует методов для тестирования, и методы тестирования могут быть большими для инициализации правильных значений. Это усложнит обслуживание кода. Как уже говорили другие, тестирование поведения класса - лучший подход, остальные должны быть достаточно маленькими для отладки.
Даррен Рейд
Я использую такое же количество строк для тестирования, как и все остальные (на самом деле меньше, чем вы прочтете позже). Вам не нужно проверять ВСЕ ваши личные методы. Только те, которые нуждаются в тестировании :) Вам также не нужно тестировать каждый в отдельном методе. Я делаю это одним звонком. Это фактически делает обслуживание кода МЕНЬШЕ сложным, так как все мои классы имеют один и тот же метод зонтичного модульного теста, который построчно выполняет все приватные и защищенные модульные тесты. Затем весь тестовый комплект вызывает один и тот же метод для всех моих классов, и все обслуживание находится в моем классе - тесты и все такое.
Руб
3

Я хочу создать четкий пример кода, который вы можете использовать в любом классе, в котором вы хотите протестировать закрытый метод.

В вашем тестовом классе просто включите эти методы, а затем используйте их, как указано.

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$ this -> _ callMethod ('_ someFunctionName', массив (param1, param2, param3));

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

Дэймон Хоган
источник
3

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

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

Затем в ваших реальных тестах вы можете сделать что-то вроде этого:

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");
Эрик Стоун
источник
2

MbUnit получил хорошую обертку для этого под названием Reflector.

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

Вы также можете установить и получить значения из свойств

dogReflector.GetProperty("Age");

Относительно "частного теста" я согласен, что .. в идеальном мире. нет смысла делать частные юнит-тесты. Но в реальности вы можете захотеть написать приватные тесты вместо рефакторинга кода.

Карл Бергквист
источник
4
Просто для информации, Reflectorбыл заменен на более мощный Mirrorв Gallio / MbUnit v3.2. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
Ян Тревин
2

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

Johnny_D
источник
2

На мой взгляд, вам следует только протестировать открытый API вашего classe.

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

Хороший публичный API решает непосредственную задачу клиентского кода и полностью решает эту задачу.

jpchauny
источник
Это должен быть правильный ответ ИМО. Если у вас много частных методов, то это, вероятно, потому, что у вас есть скрытый класс, который вы должны использовать в своем публичном интерфейсе.
sunefred
2

Я использую класс PrivateObject . Но, как упоминалось ранее, лучше избегать тестирования частных методов.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
vsapiha
источник
2
CC -Dprivate=public

«CC» - это компилятор командной строки в системе, которую я использую. -Dfoo=barделает эквивалент #define foo bar. Таким образом, эта опция компиляции эффективно меняет все личные вещи на публичные.

Марк Харрисон
источник
2
Что это? Это относится к Visual Studio?
YeahStu
«CC» - это компилятор командной строки в системе, которую я использую. «-Dfoo = bar» делает эквивалент «#define foo bar». Таким образом, эта опция компиляции эффективно меняет все личные вещи на публичные. ха-ха!
Марк Харрисон
В Visual Studio установите определение в своей среде сборки.
Марк Харрисон
1

Вот пример, сначала подпись метода:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

Вот тест:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}
Чак Сэвидж
источник
1

Чтобы сделать это, нужно иметь свой метод protectedи написать тестовое устройство, которое наследует ваш класс для тестирования. Таким образом, вы не поворачиваете свой метод public, но включаете тестирование.

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

1) Если у вас есть унаследованный код, то единственный способ проверить приватные методы - это рефлексия.

2) Если это новый код, то у вас есть следующие варианты:

  • Используйте отражение (для сложных)
  • Написать модульный тест в том же классе (делает рабочий код уродливым, если в нем также есть тестовый код)
  • Рефакторинг и сделать метод общедоступным в некотором классе утилит
  • Используйте аннотацию @VisibleForTesting и удалите приватную

Я предпочитаю метод аннотации, самый простой и наименее сложный. Единственная проблема заключается в том, что мы увеличили видимость, что, я думаю, не является большой проблемой. Мы всегда должны кодировать интерфейс, поэтому, если у нас есть интерфейс MyService и реализация MyServiceImpl, то у нас могут быть соответствующие тестовые классы MyServiceTest (методы интерфейса тестирования) и MyServiceImplTest (тестировать частные методы). В любом случае все клиенты должны использовать интерфейс таким образом, что хотя видимость приватного метода была увеличена, это не должно иметь большого значения.

Ануй
источник
1

Вы также можете объявить его как открытый или внутренний (с InternalsVisibleToAttribute) при сборке в режиме отладки:

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

Он раздувает код, но будет privateв сборке релиза.

Алекс Н
источник
0

Вы можете сгенерировать тестовый метод для частного метода из Visual Studio 2008. Когда вы создаете модульный тест для частного метода, папка «Ссылки на тест» добавляется в ваш тестовый проект, а средство доступа добавляется в эту папку. Метод доступа также упоминается в логике метода модульного тестирования. Этот метод доступа позволяет вашему модульному тесту вызывать закрытые методы в тестируемом коде. Для подробностей взгляните на

http://msdn.microsoft.com/en-us/library/bb385974.aspx

Сарат
источник
0

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

Чак Ди
источник
2
Нет, InternalsVisibleToAttributeэто не требует , чтобы ваши сборки сильно названы. В настоящее время я использую его в проекте, где это не так.
Коди Грей
1
Чтобы прояснить это: «Как текущая сборка, так и сборка друга должны быть подписаны или оба должны быть подписаны строгим именем». - Из MSDN
Хари