Як ви використовуєте тестові приватні методи?


479

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

Який правильний спосіб це зробити?


3
Можливо, мені чогось не вистачає, а може бути, саме це питання є, ну ... pre-historicв інтернеті років, але одиничне тестування приватних методів тепер є простим і прямим вперед, Visual Studio виробляє необхідні класи аксесуарів, коли це потрібно і попередньо заповнивши логіку тестів фрагментами, що може бути близьким до того, що можна отримати для простих функціональних тестів. Див. Наприклад. msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx
mjv

3
Це здається майже копією stackoverflow.com/questions/34571/… .
Raedwald

Можливо, запитуючий не використовує візуальну студію
Дейв


Відповіді:


122

Якщо ви використовуєте .net, вам слід використовувати InternalsVisibleToAttribute .


86
Гидота. Це збирається у звільнені вами збірки.
Джей

14
@Jay - може не один використовувати #if DEBUGнавколо InternalsVisibleToатрибута , щоб зробити це не відноситься до коду випуску?
mpontillo

20
@Mike, ви могли, але тоді ви можете запускати одиничні тести лише на коді налагодження, а не випускати код. Оскільки код випуску оптимізований, ви можете побачити різну поведінку та різні терміни. У багатопотоковому коді це означає, що ваші тестові одиниці не будуть належним чином визначати умови перегонів. Набагато краще використовувати рефлексію через пропозицію @ AmazedSaint нижче або використовувати вбудований PrivateObject / PrivateType. Це дозволяє вам бачити приватних осіб у версіях випуску, припускаючи, що ваш тестовий джгут працює з цілковитою довірою (що MSTest працює локально)
Jay

121
Що я пропускаю? Чому це буде прийнятою відповіддю, якщо вона насправді не відповідає конкретному питанню тестування приватних методів? InternalsVisibleTo розкриває лише методи, позначені як внутрішні, а не ті, що позначені приватними, як цього вимагає ОП (і причину я приземлився тут). Я думаю, мені потрібно продовжувати використовувати PrivateObject, на що відповів Сім?
Даррен Льюїс

6
@Jay Я знаю , що це трохи пізно приходить, але один з варіантів є використання що - щось на зразок #if RELEASE_TESTнавколо , InternalsVisibleToяк каже Майк, і зробити копію конфігурації побудови релізу , який визначає RELEASE_TEST. Ви можете перевірити свій код випуску за допомогою оптимізацій, але коли ви фактично будуєте для випуску, ваші тести будуть опущені.
Шаз

349

Якщо ви хочете перевірити приватний метод, щось може бути не так. Одиничні тести - це, як правило, призначені для перевірки інтерфейсу класу, тобто його публічних (і захищених) методів. Ви, звичайно, можете "зламати" рішення для цього (навіть якщо лише оприлюднити методи), але ви також можете розглянути:

  1. Якщо метод, який ви хочете протестувати, дійсно варто перевірити, можливо, варто перенести його у власний клас.
  2. Додайте більше тестів до публічних методів, які викликають приватний метод, перевіряючи функціональність приватного методу. (Як зазначають коментатори, ви повинні робити це лише у тому випадку, якщо функціональність цих приватних методів дійсно є частиною загальнодоступного інтерфейсу. Якщо вони фактично виконують функції, приховані від користувача (тобто тестовий пристрій), це, мабуть, погано).

38
Варіант 2 змушує одиничні тести мати знання базової реалізації функції. Мені не подобається це робити. Я вважаю, що одиничні тести повинні перевірити функцію, не припускаючи нічого про реалізацію.
Гермс

15
Недоліками впровадження тестування є те, що тести будуть неміцними, щоб зламати, якщо внести якісь зміни в реалізацію. І це небажано, оскільки рефакторинг настільки ж важливий, як і написання тестів в TDD.
JtR

30
Що ж, тести повинні зламатися, якщо змінити реалізацію. TDD означатиме спочатку зміни тестів.
sleske

39
@sleske - Я не зовсім згоден. Якщо функціональність не змінилася, тест не повинен порушуватися, оскільки тести справді повинні бути тестуванням поведінки / стану, а не реалізацією. Це те, що jtr мав на увазі щодо цього, роблячи тести крихкими. В ідеальному світі вам слід мати змогу перефактурувати код і пройти тести все ще, переконавшись, що ваш рефакторинг не змінив функціональність вашої системи.
Alconja

26
Вибачення за наївність (ще не так багато досвіду з тестуванням), але чи не ідея одиничного тестування перевіряти кожен модуль коду самостійно? Я не дуже розумію, чому приватні методи повинні бути виключені з цієї ідеї.
OMill

118

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

Microsoft пропонує два механізми для цього:

Аксесуари

  • Перейдіть до вихідного коду визначення класу
  • Клацніть правою кнопкою миші назви класу
  • Виберіть "Створити приватний доступ"
  • Виберіть проект, в якому повинен бути створений аксесуар => Ви отримаєте новий клас з ім'ям 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
Метод доступу тестування приватних методів припиняється з VS 2011 року. blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
Sanjit Misra

1
Читаючи документи, знайдені на веб-сайті Microsoft тут, я не бачу згадування класу PrivateObject як застарілого. Я використовую MSVS 2013, і він працює як очікувалося.
стік потоку

3
@RyanGates Перше рішення на веб-сайті, на який ви посилаєтесь, говорить про те, що рішенням доступу до приватних членів є "Використовувати клас PrivateObject для надання доступу до внутрішніх та приватних API в коді. Це знайдено в Microsoft.VisualStudio.QualityTools.UnitTestFramework. dll збори. "
стік потоку

78

Я не згоден з філософією "ви повинні бути зацікавлені лише в тестуванні зовнішнього інтерфейсу". Це трохи схоже на те, що автомайстерня повинна мати лише тести, щоб побачити, чи крутяться колеса. Так, в кінцевому рахунку мене цікавить зовнішня поведінка, але мені подобається, що мої власні, приватні, внутрішні тести мають бути дещо конкретнішими та точнішими. Так, якщо я рефактор, можливо, мені доведеться змінити деякі тести, але якщо це не масовий рефактор, мені доведеться змінити лише декілька, і той факт, що інші (незмінені) внутрішні тести все ще працюють, є чудовим показником того, що рефакторинг пройшов успішно.

Ви можете спробувати охопити всі внутрішні випадки, використовуючи лише загальнодоступний інтерфейс, і теоретично можливо перевірити кожен внутрішній метод (або принаймні кожен, який має значення) повністю, використовуючи публічний інтерфейс, але, можливо, вам доведеться стояти на голові, щоб досягти це та зв'язок між тестовими випадками, що проходять через загальнодоступний інтерфейс, та внутрішньою частиною рішення, яке вони розроблені для тестування, можуть бути важкими або неможливими розрізнити. Зазначивши, індивідуальні тести, які гарантують, що внутрішня техніка працює належним чином, заслуговують на незначні зміни тесту, які виникають при рефакторингу - принаймні, це був мій досвід. Якщо вам доведеться внести величезні зміни у свої тести для кожного рефакторингу, то, можливо, це не має сенсу, але в такому випадку, можливо, вам слід повністю переосмислити свій дизайн.


20
Боюся, я все ще не згоден з вами. Поводження з кожним компонентом як чорним вікном дозволяє без проблем замінювати / вимикати модулі. Якщо у вас є щось, FooServiceщо Xвам потрібно зробити , все, що вам слід подбати, це те, що це дійсно робиться, Xколи просять. Як це не має значення. Якщо в класі є проблеми, які не помітні через інтерфейс (навряд чи), це все-таки дійсно FooService. Якщо це проблема , яка є видимою через інтерфейс, тест на публічних членах повинен виявити його. Вся справа в тому, що доки колесо повертається належним чином, воно може використовуватися як колесо.
Основні

5
Один із загальних підходів полягає в тому, що якщо ваша внутрішня логіка є досить складною, що вам здається, що вона потребує тестування одиниць, можливо, її потрібно винести в якийсь клас помічників з публічним інтерфейсом, який можна перевірити на одиницю. Тоді ваш «батьківський» клас може просто скористатися цим помічником, і кожен може бути перевірений належним чином.
Мир

9
@Basic: Цілком неправильна логіка у цій відповіді. Класичний випадок, коли вам потрібен приватний метод, - це коли вам потрібен якийсь код, щоб повторно використовувати публічні методи. Ви поклали цей код на якийсь метод PrivMethod. Цей метод не повинен піддаватися загальнодоступному, але потребує тестування, щоб переконатися, що публічні методи, які покладаються на PrivMethod, можуть по-справжньому покластися на нього.
Діма

6
@Dima Напевно тоді, якщо є проблема PrivMethod, тест, на PubMethodякому дзвінки PrivMethodповинні піддавати її? Що відбувається, коли ви перейдете SimpleSmtpServiceна свій GmailService? Раптом ваші приватні тести вказують на код, який більше не існує або, можливо, працює інакше і може вийти з ладу, навіть якщо програма може працювати ідеально, як задумано. Якщо є складна обробка, яка застосовуватиметься до обох відправників електронної пошти, можливо, вона повинна бути в тій, EmailProcessorяку можуть використовувати обидва і перевіряти окремо?
Основні

2
@miltonb Ми можемо розглядати це з різних стилів розробки. Напишіть внутрішні, я не схильний їх перевіряти. Якщо є проблема (визначена інтерфейсними тестами), її можна легко відстежити, приєднавши налагоджувач, або клас занадто складний і його слід розділити (з тестуванням публічного інтерфейсу нового блоку класів) IMHO
Basic

51

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

Клас:

...

protected void APrivateFunction()
{
    ...
}

...

Підклас для тестування:

...

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

...

1
Ви навіть можете помістити цей дочірній клас у свій тестовий файл одиниці, а не захаращувати реальний клас. +1 за хитрість.
Тім Абелл

Я завжди вкладаю весь код, пов'язаний з тестом, у проект одиничних тестів, якщо це можливо. Це був просто psuedo-код.
Джейсон Джексон

2
Ця функція не є приватною, її захищено, чистий результат ... ви зробили свій код менш захищеним / піддавались приватному функціонуванню типів дітей
війна

22

Я думаю, що слід задати більш фундаментальне питання - це чому ви намагаєтесь протестувати приватний метод в першу чергу. Це запах коду, який ви намагаєтеся перевірити приватним методом через загальнодоступний інтерфейс цього класу, тоді як цей метод є приватним через певну причину, оскільки це деталізація реалізації. Слід потурбуватися лише про поведінку публічного інтерфейсу, а не про те, як він реалізований під кришками.

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

Продукт рефакторингу означає, що приватний метод тепер є окремим класом, який став співпрацею до початкового класу. Її поведінка стане добре зрозумілою завдяки власним тестам.

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

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

Звичайно, безпосередньо тестування приватних методів може бути крайнім засобом, якщо у вас є застаріле додаток, але я вважаю за краще, щоб застарілий код був відновлений для відновлення для кращого тестування. Майкл Перо написав чудову книгу на цю саму тему. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052


16
Повністю неправильна логіка в цій відповіді. Класичний випадок, коли вам потрібен приватний метод, - це коли вам потрібен якийсь код, щоб повторно використовувати публічні методи. Ви поклали цей код на якийсь метод PrivMethod. Цей метод не повинен піддаватися загальнодоступному, але потребує тестування, щоб переконатися, що публічні методи, які покладаються на PrivMethod, можуть по-справжньому покластися на нього.
Діма

Має сенс під час початкової розробки, але чи хочете ви тестів на приватні методи у вашому стандартному регресійному костюмі? Якщо так, якщо реалізація зміниться, вона може порушити набір тестів. OTOH, якщо ваші тести регресії зосереджуються лише на відкритих видимих ​​загальнодоступних методах, то, якщо приватний метод пізніше перерветься, пакет регресії все-таки повинен виявити помилку. Тоді, якщо потрібно, ви можете запилити старий приватний тест, якщо це потрібно.
Алекс Блакемор

9
Не погоджуючись, вам слід тестувати лише публічний інтерфейс, інакше навіщо потрібні приватні методи. У цьому випадку зробіть їх загальнодоступними і протестуйте їх. Якщо ви тестуєте приватні методи, ви порушуєте інкапуляцію. Якщо ви хочете протестувати приватний метод, і він використовується в декількох публічних методах, його слід перенести в свій клас і протестувати ізольовано. Після цього всі відкриті методи повинні бути делеговані до нового класу. Таким чином, у вас все ще є тести для інтерфейс оригінального класу, і ви можете перевірити, чи поведінка не змінилася, і у вас є окремі тести для делегованого приватного методу.
Велика Кахуна

@ Біг Кахуна - Якщо ви думаєте, що немає випадків, коли вам потрібно перевірити приватні методи, ви ніколи не працювали з досить великим / складним проектом. Багато разів публічна функція, така як перевірка для клієнта, закінчується 20 рядками, викликаючи дуже прості приватні методи, щоб зробити код більш читабельним, але вам все одно доведеться протестувати кожен окремий приватний метод. Тестування 20-разової загальнодоступної функції буде дуже важко дебютувати, коли одиничні тести закінчуються невдачею.
Pedro.The.Kid

1
Я працюю в компанії FTSE 100. Я думаю, що свого часу я бачив кілька складних проектів, дякую. Якщо вам потрібно протестувати на цьому рівні, то кожен приватний метод, як окремий колабораціоніст, повинен бути перевірений ізольовано, оскільки це означає, що вони мають індивідуальну поведінку, яка потребує тестування. Тест для основного об’єкта медіатора потім просто стає тестом на взаємодію. Це просто перевірка правильної стратегії, яка викликається. Ваш сценарій звучить так, що клас, про який йде мова, не відповідає SRP. У нього немає єдиної причини змінити, але 20 => порушення SRP. Почитайте книгу GOOS або дядька Боба. 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();

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 ваш синтаксис неправильний. Потрібно спочатку оголосити екземпляр PrivateClassі скористатися цим. stackoverflow.com/questions/9122708 / ...
SharpC

10

Я також використовував метод InternalsVisibleToAttribute. Варто також зазначити, що, якщо вам неприємно робити свої раніше приватні методи внутрішніми, щоб досягти цього, то, можливо, вони не повинні бути предметом прямих тестових одиниць.

Зрештою, ви протестуєте поведінку свого класу, а не його конкретного впровадження - ви можете змінити останнього, не змінюючи першого, і ваші тести все одно повинні пройти.


Мені подобається тест щодо тестування поведінки, а не впровадження. Якщо ви прив’яжете свої тестові одиниці до впровадження (приватні методи), то тести стануть крихкими та потребуватимуть змін, коли зміниться впровадження.
Боб Хорн

9

Існує 2 типи приватних методів. Статичні приватні методи та нестатичні приватні методи (інстанційні методи). Наступні 2 статті пояснюють, як з’єднати тестові приватні методи із прикладами.

  1. Блок тестування статичних приватних методів
  2. Блок тестування нестатичних приватних методів

Наведіть кілька прикладів, а не лише наведіть посилання
vinculis

Виглядає некрасиво. Без інтелігенції. Погане рішення від МС. Я в шоці!
алерія

Найпростіший спосіб перевірити приватні статичні методи
Грант

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


4

Задекларуйте їх internal, а потім скористайтеся, InternalsVisibleToAttributeщоб дозволити їхній тестовій збірці пристрою бачити їх.


11
Мені не подобається використовувати InternalsVisibleTo, тому що я зробив метод приватним з причини.
swilliams

4

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


Ви б включили тестові аксесуари у виробничу версію (для тестування оптимізацій компілятора тощо), але виключили їх у версії випуску. Але я розщеплюю волосся, і я все-таки підтримав це, тому що думаю, що це гарна ідея розмістити ці речі в одному місці. Дякую за ідею.
CAD заблокували

4

У першу чергу не слід тестувати приватні методи свого коду. Ви повинні тестувати "загальнодоступний інтерфейс" або API, загальнодоступні речі ваших класів. API - це всі публічні методи, які ви піддаєте стороннім абонентам.

Причина полягає в тому, що після того, як ви почнете тестувати приватні методи та внутрішні засоби свого класу, ви з'єднуєте реалізацію свого класу (приватних речей) з вашими тестами. Це означає, що коли ви вирішите змінити деталі своєї реалізації, вам також доведеться змінити свої тести.

З цієї причини вам слід уникати використання InternalsVisibleToAtrribute.

Ось чудова розмова Ian Cooper, яка висвітлює цю тему: Ian Cooper: TDD, де все пішло не так


3

Іноді може бути добре перевірити приватні декларації. По суті, компілятор має лише один відкритий метод: Compile (string outputFileName, params string [] sourceSFileNames). Я впевнений, що ви розумієте, що перевірити такий метод було б складно без тестування кожної "прихованої" декларації!

Ось чому ми створили Visual T #: для спрощення тестів. Це безкоштовна мова програмування .NET (сумісна з C # v2.0).

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

Погляньте на наш веб-сайт: завантажте його безкоштовно .


3

Я здивований, що ніхто ще цього не сказав, але я застосував рішення - зробити статичний метод всередині класу, щоб перевірити себе. Це дає вам доступ до всього публічного та приватного для тестування.

Крім того, мовою сценаріїв (з вміннями OO, як-от Python, Ruby та PHP) ви можете зробити тест файлів під час запуску. Приємний швидкий спосіб переконатися, що ваші зміни нічого не порушили. Це очевидно робить масштабним рішенням для тестування всіх ваших класів: просто запустіть їх усі. (ви також можете це зробити на інших мовах з пустотою main, яка завжди також виконує свої тести).


1
Хоча це практично, але це не дуже елегантно. Це може призвести до безладу кодової бази, а також не дозволяє відокремити тести від реального коду. Можливість зовнішнього тестування відкриває можливість автоматичного тестування скриптів замість написання статичних методів вручну.
Даррен Рейд

Це не заважає вам тестувати зовнішнє ... просто зателефонуйте статичному методу, як би вам не хотілося. База коду також не безладна ... Ви відповідно називаєте метод. Я використовую "runTests", але щось подібне працює.
rube

Ваше право, це не перешкоджає тестуванню зовнішньо, проте воно генерує набагато більше коду, тобто робить базу коду безладним. Кожен клас може мати багато приватних методів для перевірки того, який ініціалізує його змінні в одному або декількох його конструкторах. Для тестування вам потрібно буде написати щонайменше стільки статичних методів, скільки є методів тестування, і методи тестування, можливо, повинні бути великими, щоб ініціалізувати потрібні значення. Це ускладнить підтримку коду. Як говорили інші, тестування поведінки класу є кращим підходом, решта має бути достатньо малим, щоб налагоджувати.
Даррен Рейд

Я використовую таку ж кількість рядків для тестування, як і будь-хто інший (насправді менше, ніж ви прочитаєте пізніше). Вам не доведеться перевіряти ВСІ ваші приватні методи. Тільки ті, які потребують тестування :) Також вам не потрібно тестувати кожен окремим методом. Я роблю це одним закликом. Це фактично утруднює підтримку коду менше, оскільки всі мої класи мають однаковий метод тестування парасолькових одиниць, який виконує всі тести приватного та захищеного блоку по черзі. Потім весь тестовий джгут викликає той самий метод у всіх моїх класах, а технічне обслуговування все знаходиться в моєму класі - тести і все.
рубе

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

Для всіх, хто хоче запускати приватні методи без всякої безладності та безладу. Це працює з будь-якою базовою рамкою тестування, не використовуючи нічого, крім старого хорошого відображення.

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

Ось хороша стаття про одиничне тестування приватних методів. Але я не впевнений, що краще, щоб зробити програму, розроблену спеціально для тестування (це як створення тестів лише для тестування) або використовувати рефлексію для тестування. Напевно, більшість із нас обирає другий шлях.


2

На мою думку, ви повинні лише перевірити публічний API вашого класу.

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

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


Це має бути правильна відповідь ІМО. Якщо у вас є багато приватних методів, це, мабуть, тому, що у вас є прихований клас, який вам слід вибити на його власний публічний інтерфейс.
сунефред

2

Я використовую клас PrivateObject . Але як було сказано раніше, краще уникати тестування приватних методів.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);

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, але ви включаєте тестування.


Я не згоден з цим, тому що ви також дозволите своїм споживачам успадкувати від базового класу та використовувати захищені функції. Це було щось, що ви хотіли запобігти в першу чергу, зробивши ці функції приватними або внутрішніми.
Нік Н.

1

1) Якщо у вас є застарілий код, то єдиний спосіб перевірити приватні методи - це відображення.

2) Якщо це новий код, у вас є такі варіанти:

  • Використовувати роздуми (до складних)
  • Пишіть одиничний тест у тому ж класі (робить виробничий код некрасивим, маючи також тестовий код)
  • Refactor та зробіть метод загальнодоступним у якомусь класі util
  • Використовуйте примітку @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. Коли ви створюєте тестовий модуль для приватного методу, до вашого тестового проекту додається папка Test References, а до цієї папки додається аксесуар. У логіці методу одиничного тестування також посилається на приєднувач. Цей аксесуар дозволяє вашому тестуванню пристрою викликати приватні методи в коді, який ви тестуєте. Детальніше дивіться

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


0

Також зауважте, що InternalsVisibleToAtrribute має вимогу, щоб ваша збірка була чітко названа , що створює власний набір проблем, якщо ви працюєте над рішенням, яке раніше не вимагало цієї вимоги. Я використовую аксесуар для тестування приватних методів. Дивіться це питання як приклад цього.


2
Ні, InternalsVisibleToAttributeце не вимагає, щоб ваші збори були чітко названі. Зараз я використовую його в проекті, де це не так.
Коді Грей

1
Для уточнення цього: "І поточна асамблея, і група друзів повинні бути непідписані, або обидва повинні бути підписані сильним ім'ям." - З MSDN
Харі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.