Блок тестування приватних методів у C #


292

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

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

Помилка виконання:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Відповідно до intellisense - і, отже, я думаю, компілятор - ціль типу TypeA_Accessor. Але під час виконання він має тип TypeA, а значить, додавати список не вдається.

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


Вам потрібен аксесуар для приватного типу TypeB. Accessor TypeA_Accessor забезпечує доступ до приватних та захищених методів TypeA. Однак TypeB не є методом. Це клас.
Діма

Accessor забезпечує доступ до приватних / захищених методів, членів, властивостей та подій. Він не забезпечує доступ до приватних / захищених класів у вашому класі. І приватні / захищені класи (TypeB) призначені для використання лише методами володіння класом (TypeA). Тому в основному ви намагаєтеся додати приватний клас (TypeB) поза межами TypeA до "myList", який є приватним. Оскільки ви використовуєте accessor, немає доступу до myList. Однак ви не можете використовувати TypeB через аксесуар. Можливим рішенням було б переміщення TypeB поза TypeA. Але це може зламати ваш дизайн.
Діма

Відчуйте , що тестування закритих методів повинні бути зроблено наступним stackoverflow.com/questions/250692 / ...
nate_weldon

Відповіді:


275

Так, не тестуйте приватні методи .... Ідея тестування одиниці полягає у тестуванні блоку на його загальнодоступному "API".

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

Порада / Інструмент мислення ..... Існує думка, що жоден метод ніколи не повинен бути приватним. Це означає, що всі методи повинні жити на відкритому інтерфейсі об'єкта .... якщо ви вважаєте, що вам потрібно зробити його приватним, він, швидше за все, живе на іншому об'єкті.

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


385
Я не погоджуюсь. У OOD приватні методи та властивості - це невід'ємний спосіб не повторювати себе ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). Ідея програмування та інкапсуляції чорної скриньки - приховати технічні деталі від підписника. Тому дійсно необхідно мати у своєму коді нетривіальні приватні методи та властивості. І якщо це нетривіально, його потрібно перевірити.
AxD

8
це не властиво, деякі мови OO не мають приватних методів, приватні властивості можуть містити об'єкти з публічними інтерфейсами, які можна перевірити.
Кіт Ніколас

7
Суть цієї поради полягає в тому, що якщо ви об'єктом займаєтесь однією справою, і є ДРУГИМ, то часто є мало причин мати приватні методи. Часто приватні методи роблять щось, за що об’єкт не є дійсно відповідальним, але є досить корисним, якщо це не тривіально, то це, як правило, інший об’єкт, оскільки його, ймовірно, порушує SRP
Кіт Ніколас,

28
Неправильно. Ви можете скористатися приватними методами, щоб уникнути дублювання коду. Або для перевірки. Або для багатьох інших цілей, про які Громадський світ не повинен знати.
Джордж

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

666

Ви можете використовувати PrivateObject Class

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

25
Це правильна відповідь, тепер, коли Microsoft додав PrivateObject.
Зої

4
Хороша відповідь, але зауважте, що PrivateMethod потрібно "захищати" замість "приватного".
HerbalMart

23
@HerbalMart: Можливо, я вас неправильно розумію, але якщо ви припускаєте, що PrivateObject може отримати доступ лише до захищених членів, а не до приватних, ви помиляєтесь.
kmote

17
@JeffPearce Для статичних методів можна використовувати "PrivateType pt = new PrivateType (typeof (MyClass));", а потім викликати InvokeStatic на об'єкт pt, як ви б назвали Invoke на приватному об'єкті.
Стів Хібберт

12
У разі , якщо хто - небудь задавався питанням, як з MSTest.TestFramework v1.2.1 - класи PrivateObject і PrivateType недоступні для проектів , орієнтованих на .NET 2.0 Сердечник - Там це питання GitHub для цього: github.com/Microsoft/testfx/issues/366
шиїтаке

98

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

Те саме стосується і цієї дискусії.

введіть тут опис зображення

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

Якщо ви хочете викликати приватні методи, ви можете скористатися класом "PrivateObject" та зателефонувати до методу виклику. Ви можете переглянути це непримітне відео YouTube ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ), яке показує, як використовувати "PrivateObject", а також обговорює, чи тестування приватних методів є логічним чи ні.


55

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

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

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

І одиничне випробування:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}

Я не впевнений, що розумію. InternalsVisibleToAttribute робить методи та атрибути, позначені як "внутрішні", доступними, але мої поля та методи "приватні". Ви пропонуєте мені змінити речі з приватних на внутрішні? Я думаю, що я неправильно розумію.
junichiro

2
Так, це я пропоную. Це трохи "хакі", але принаймні вони не "публічні".
Джефф

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

5
Так, я також плутаю тих, хто стверджує, що приватні методи не повинні перевірятися в одиничному тесті. Public API - це вихід, але іноді неправильна реалізація також дає правильний результат. Або впровадження спричинило деякі погані побічні ефекти, наприклад, використання ресурсів, які не потрібні, посилання на об'єкти, які не дозволяють збирати gc ... тощо. Якщо вони не надають інший тест, який може охопити приватні методи, а не одиничний тест, інакше я вважаю, що вони не можуть підтримувати 100% перевірений код.
mr.Pony

Я згоден з MasterMastic. Це має бути прийнятою відповіддю.
XDS

27

Один із способів перевірити приватні методи - через рефлексію. Це стосується і NUnit, і XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);

call methods статичні та нестатичні ?
Кікенет

2
Мінус методів, що залежать від рефлексії, полягає в тому, що вони мають тенденцію ламатися при перейменуванні методів за допомогою R #. Це може бути не великою проблемою для малих проектів, але на величезних кодових базах стає своєрідним нудотою, щоб такі одиничні тести ламалися таким чином, а потім потрібно було обходити їх і швидко виправити. У цьому сенсі я мої гроші йдуть на відповідь Джеффа.
XDS

2
@XDS Занадто поганий nameof () не працює для отримання імені приватного методу поза його класом.
Габріель Морін

12

Е-е-е ... Тут виникла абсолютно однакова проблема: протестуйте простий , але ключовий приватний метод. Прочитавши цю тему, схоже, що "я хочу просвердлити цю просту дірку в цьому простому шматку металу, і я хочу переконатися, що якість відповідає специфікаціям", а потім з'являється "Гаразд, це непросто. Перш за все, для цього немає відповідного інструменту, але ви можете побудувати в своєму саду обсерваторію гравітаційно-хвильового характеру. Прочитайте мою статтю на веб-сайті http://foobar.brigther-than-einstein.org/ По-перше, звичайно, ви доведеться відвідувати деякі передові курси квантової фізики, тоді вам потрібні тони надпрохолодного нітрогенію, а потім, звичайно, моя книга, доступна в Amazon "...

Іншими словами...

Ні, спочатку спочатку.

Кожен метод, будь-який приватний, внутрішній, захищений, загальнодоступний , повинен бути перевіреним. Повинен бути спосіб реалізувати такі тести без такої приналежності, яку тут було представлено.

Чому? Саме завдяки архітектурним згадкам, зробленим до цього часу деякими авторами. Можливо, просте повторення програмних принципів може усунути деякі непорозуміння.

У цьому випадку звичайними підозрюваними є: OCP, SRP та, як завжди, KIS.

Але почекай хвилинку. Ідея зробити все загальнодоступним - це менш менш політичне та своєрідне ставлення. Але. Що стосується коду, навіть у тодішній спільноті з відкритим кодом, це не догма. Натомість "приховувати" щось хороша практика, щоб полегшити знайомство з певним API. Наприклад, ви ховали б самі основні обчислення нового блоку побудови цифрового термометра - не приховувати математику за реальною вимірюваною кривою цікавим читачам кодів, а щоб запобігти тому, щоб ваш код не залежав від деяких, можливо, раптом важливі користувачі, які не змогли протистояти використанню вашого приватного, внутрішнього, захищеного коду для реалізації власних ідей.

Про що я говорю?

приватний подвійний TranslateMeasurementIntoLinear (подвійний фактичнийMeasurement);

Легко проголосити Епоху Водолія або те, що називають сьогодні, але якщо мій фрагмент датчика отримає від 1,0 до 2,0, реалізація Translate ... може змінитися від простого лінійного рівняння, яке легко зрозуміле і "повторно" зручно "для всіх, до досить складного розрахунку, який використовує аналіз чи будь-що інше, і тому я би порушив код інших. Чому? Тому що вони не розуміли самих принципів кодування програмного забезпечення, навіть не KIS.

Щоб зробити цю казку короткою: нам потрібен простий спосіб перевірити приватні методи - без прихильності.

Перший: З новим роком всім!

Друге: Репетируйте уроки архітектора.

По-третє: Модифікатором "громадськості" є релігія, а не рішення.


5

Ще один варіант, про який не згадувалося, - це просто створити одиничний тестовий клас як дочірнього об'єкта, який ви тестуєте. Приклад NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

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

ЯКЩО ...

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

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

EDIT: Мені справді весело, що люди голосують за цю відповідь, оскільки я чітко описую це як погану ідею. Це означає, що люди погоджуються зі мною? Я так розгублений .....


Це творче рішення, але начебто хакі.
shinzou

3

З книги « Ефективна робота зі застарілим кодом» :

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

Спосіб її виправити, за словами автора, полягає у створенні нового класу та додаванні методу як public .

Автор пояснює далі:

"Хороший дизайн перевіряється, а дизайн, який не перевіряється - поганий."

Отже, в цих межах ваш єдиний реальний варіант - зробити метод publicабо в поточному, або в новому класі.


2

TL; DR: витяг приватного методу до іншого класу, тест на цьому класі; детальніше про принцип СРП (єдиний принцип відповідальності)

Здається, що вам потрібен витяг з privateметоду до іншого класу; в цьому має бути public. Замість того, щоб намагатися перевірити privateметод, слід випробувати publicметод цього іншого класу.

У нас є такий сценарій:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Нам потрібно перевірити логіку _someLogic; але здається, що він Class Aбере більшу роль, ніж потрібно (порушує принцип СРП); просто рефактор на два класи

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

Таким чином someLogicможна було б протестувати на А2; в А1 просто створіть якийсь фальшивий A2, а потім введіть у конструктор, щоб перевірити, чи A2 викликається функцією з ім'ям someLogic.


0

У VS 2005/2008 ви можете використовувати приватний доступ для тестування приватного члена, але цей спосіб був зниклий у більш пізній версії VS


1
Хороша відповідь ще в 2008 році, можливо, на початку 2010 року. Тепер зверніться до альтернатив PrivateObject і Reflection (див. Декілька відповідей вище). У VS2010 були помилки (аксесуари), MS заборонив це у VS2012. Якщо ви не змушені залишатися у VS2010 або старшій версії (> 18 років для збирання інструментів), заощаджуйте час, уникаючи приватних користувачів. :-).
Цефан Шрьодер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.