Чи є статична загально "зла" для тестування одиниць і якщо так, то чому рекомендує Решарпер? [зачинено]


85

Я виявив, що у C # .NET є лише 3 способи розділити тестові (макет / заглушки) залежності, які є статичними в C # .NET:

Зважаючи на те, що два з них не безкоштовні, а один не потрапив до версії 1.0, глузувати з статичних матеріалів не надто просто.

Це робить статичні методи та таке «зло» (у сенсі одиничного тестування)? І якщо так, то чому переосмислювач хоче зробити мені все, що може бути статичним, статичним? (Якщо припустити, що переосмислення не є також "злим".)

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


3
TheLQ: Можна. Я вважаю, що він говорить про те, що не може перевірити статичні методи, оскільки значна частина часу стосується статичних змінних. Таким чином змінюється стан після і між тестом.

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

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

7
"чому переосмислювач хоче, щоб я зробив щось, що може бути статичним, статичним?" Resharper не хоче , щоб ви нічого робили. Це просто дасть вам знати, що модифікація можлива і, можливо, бажана за допомогою аналізу коду POV. Resharper - це не заміна власного судження!
Адам Нейлор

4
@ acidzombie24. Регулярні методи також можуть змінювати статичний стан, щоб вони були такими ж «поганими», як і статичні. Те, що вони також можуть змінювати стан за допомогою коротшого життєвого циклу, ще робить їх ще небезпечнішими. (Я не прихильник статичних методів, але пункт про модифікацію стану - це удар і для звичайних методів, тим більше)
mike30

Відповіді:


105

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

Статичні методи, які не мають стану та не викликають побічних ефектів, повинні бути легко перевірені. Насправді я вважаю такі методи функцією програмування «бідної людини»; ви передаєте методу об'єкт або значення, і він повертає об'єкт або значення. Більше нічого. Я не бачу, як такі методи взагалі негативно вплинуть на тестування одиниць.


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

22
@ Вакано: Але якщо ви пишете статичні методи, які не мають стану та не мають побічних ефектів, вони все одно більш-менш функціонально еквівалентні заглушці.
Роберт Харві

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

20
@Vaccano: Статичний метод, який має результати або випадкові винятки, має побічні ефекти.
Тихон Єлвіс

10
@TikhonJelvis Роберт був говорити про виходи; і "випадкові" винятки не повинні бути побічним ефектом, вони по суті є формою виводу. Сенс полягає в тому, що коли ви тестуєте метод, який викликає статичний метод, ви обмотаєте цей метод та всі перестановки його потенційного виходу, і не можете перевірити свій метод ізольовано.
Ніколь

26

Здається, ви плутаєте статичні дані та статичні методи . Якщо я добре пам’ятаю, Resharper рекомендує робити privateметоди в класі статичних, якщо їх можна зробити так - я вважаю, що це дає малу користь від продуктивності. Це НЕ рекомендується робити «все , що може бути» статичним!

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

public static long Square(int x)
{
    return x * x;
}

то це виразно перевірено і не має побічних ефектів. Ви просто перевіряєте, що, коли проїдете, скажімо, 20, ви повернетесь 400. Без проблем.


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

3
Нічого не відбувається. Навіщо це? Рамка .NET сповнена статичних методів. Ви хочете сказати, що ви також не можете використовувати їх у будь-яких методах, які ви хочете здійснити для тестування?
Дан Дипл

3
Що ж, якщо ваш код знаходиться на виробничому рівні / якості .NET Framework, тоді обов'язково продовжуйте роботу. Але вся суть одиничного тестування - це випробування одиниці, ізольовано. Якщо ви також викликаєте методи в інших підрозділах (статичні вони чи інші), ви зараз тестуєте свій прилад плюс його залежності. Я не кажу, що це не корисний тест, але і не "одиничний тест" за більшістю визначень. (Тому що ви зараз випробовуєте ваш блок тестування та блок, який має статичний метод).
Ваккано

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

6
@Vaccano Отже, як Microsoft тестує .NET Framework, так? Багато класів у Framework посилають статичні методи в інших класах (наприклад, System.Math), не кажучи вже про велику кількість статичних заводських методів тощо. Плюс, ви ніколи не зможете використовувати жодних методів розширення і т.д. основоположний для сучасних мов. Ви можете перевірити їх окремо (оскільки вони, як правило, будуть детермінованими), а потім без проблем їх використовувати у своїх класах. Це не проблема!
Дан Дипл

18

Якщо справжнє питання тут "Як перевірити цей код?":

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

Потім просто перефактуруйте код і введіть, як зазвичай, виклик статичному класу на зразок цього:

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}

9
На щастя, у нас також є питання щодо читабельності коду та KISS для SO :)
gbjbaanb

чи не було б делегату легше і зробити те саме?
jk.

@jk може бути, але його важко використовувати контейнер IoC.
Сонячно

15

Статистика не обов'язково зла, але вони можуть обмежувати ваші варіанти, коли справа стосується тестування одиниць підробками / макетами / заглушками.

Є два загальні підходи до глузування.

Перший (традиційний - впроваджений RhinoMocks, Moq, NMock2; в цьому таборі теж ручні макети та заглушки) покладається на тестові шви та введення залежності. Припустимо, ви перевіряєте одиничний статичний код, і він має залежності. У коді, розробленому таким чином, часто відбувається те, що статики створюють власні залежності, перетворюючи інверсію залежності . Незабаром ви виявите, що не можете вводити зграбні інтерфейси в тестований код, розроблений таким чином.

Другий (знущаються над чим - реалізовуються TypeMock, JustMock та Moles) спирається на API профілю .NET . Він може перехопити будь-яку з ваших інструкцій CIL і замінити шматок вашого коду підробкою. Це дозволяє TypeMock та іншим продуктам у цьому таборі глузувати з чого завгодно: статики, закритих класів, приватних методів - речей, не призначених для перевірки.

Триває дискусія між двома школами думки. Один з них говорить: дотримуйтесь принципів SOLID та проектуйте для встановлення достовірності (що часто включає просту статику). Інший каже: купуйте TypeMock і не хвилюйтесь.


14

Перевірте це: "Статичні методи - смерть для заповіту" . Короткий підсумок аргументу:

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


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

4
@ Роберт Харві - як ви тестуєте метод, який використовує статичний метод з іншого класу (тобто він має залежність від статичного методу). Якщо ви просто дозволите це назвати, то ви не "тестування одиниць", ви "інтеграційне тестування"
Vaccano

10
Не просто читайте статтю в блозі, читайте безліч коментарів, які не погоджуються з нею. Блог - це лише думка, а не факт.
Дан Дипл

8
@Vaccano: Статичний метод без побічних ефектів або зовнішнього стану функціонально еквівалентний значенню. Знаючи це, це не відрізняється від одиничного тестування методу, який створює ціле число. Це одне з ключових розумінь, яке дає нам функціональне програмування.
Стівен Еверс

3
Робота вбудованих систем із забороною або додаток, чиї помилки мають потенціал для створення міжнародних інцидентів, ІМО, коли "перевіряемость" запускає архітектуру, у вас це було скручено, перш ніж ви прийняли методології тестування "кожен останній кут". Також я втомився від версії XP щодо всього, що домінує у кожній розмові. XP не є авторитетом, це галузь. Ось оригінальне, розумне визначення одиничного тестування: python.net/crew/tbryan/UnitTestTalk/slide2.html
Ерік Reppen

5

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

Але він не матиме ключових визначальних властивостей тесту; невдача, коли все не так, проходження, коли вони правильні.

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

З огляду на це, є три варіанти:

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

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

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


3
Ніщо не говорить про те, що одиничний тест - це тест одного класу. Це випробування однієї одиниці. Визначальними властивостями одиничних тестів є те, що вони швидкі, повторювані та незалежні. Посилання Math.Piв методі не робить його інтегральним тестом за жодним розумним визначенням.
сара

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

4

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

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

ПЕРЕД:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

ПІСЛЯ:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

Ці два сумісні з дзвінками. Клієнтів не потрібно змінювати. Тіло функції залишається колишнім.

Потім у вашому коді Unit-Test ви можете заглушити цей виклик так (припустимо, що це у класі, який називається База даних):

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

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

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

який повторно викликатиме статичний ініціалізатор вашого класу.

Функції Lambda не настільки багаті на підтримку, як звичайні статичні методи, тому такий підхід має такі небажані побічні ефекти:

  1. Якщо статичний метод був методом розширення, вам слід спершу змінити його на нерозширений метод. Resharper може зробити це за вас автоматично.
  2. Якщо будь-який із типів даних статичних методів є збіркою з вбудованим інтеропом, наприклад, для Office, вам доведеться обернути метод, обернути тип або змінити його на тип "об'єкт".
  3. Ви більше не можете використовувати інструмент рефакторингу змін підпису Resharper.

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

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

То чому C # має статичні методи? Чому це дозволяє використовувати невіртуальні методи екземпляра?

Якщо ви використовуєте будь-яку з цих "функцій", ви просто не можете створити окремі методи.

То коли ви їх використовуєте?

Використовуйте їх для будь-якого коду, який ви не очікуєте, що хтось коли-небудь захоче заглушити. Деякі приклади: метод Format () класу String, метод WriteLine () класу Console, метод Cosh () класу Math

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


3
  1. Я вважаю, що це частково тому, що статичні методи "швидше" викликати, ніж методи екземпляра. (У лапках, бо пахне мікрооптимізацією) див. Http://dotnetperls.com/static-method
  2. Це говорить вам, що йому не потрібен стан, тому його можна викликати з будь-якого місця, видаляючи накладні витрати, якщо це єдине, що комусь потрібно.
  3. Якщо я хочу знущатися над цим, то я думаю, що це правило, що це оголошено в інтерфейсі.
  4. Якщо це оголошено в інтерфейсі, R # не запропонує вам зробити його статичним.
  5. Якщо це оголошено віртуальним, то і R # не запропонує вам зробити його статичним.
  6. Статичне утримання стану (полів) - це те, що завжди слід уважно розглядати . Статичний стан і нитки змішуються, як літій і вода.

R # - не єдиний інструмент, який зробить цю пропозицію. Аналіз коду FxCop / MS також зробить те саме.

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


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

1
Ні, ви не можете оголосити статику в інтерфейсі. Це було б безглуздо.
МВС

3

Я бачу, що через довгий час ніхто ще не констатував дійсно простий факт. Якщо переробник скаже мені, що я можу зробити метод статичним, це означає для мене величезну річ, я чую його голос, який говорить мені: "Ей, ти, ці логіки не є ВІДПОВІДАЛЬНІСТЮ поточного класу, з якою слід обробляти, тому він повинен залишатися поза в якомусь класі помічників чи щось таке ".


2
Я не погоджуюсь. Більшу частину часу, коли Решарпер каже, що я можу зробити щось статичне, це трохи код, який був загальним для двох або більше методів у класі, і, таким чином, я витяг його з власної процедури. Переходити до помічника було б безглуздою складністю.
Лорен Печтел

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

1
Не стосується питання.
Ігбі Ларгеман

2

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

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


0

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

Що ж стосується testablity, то цей сценарій не повинен бути проблемою, оскільки ви не повинні тестувати приватні методи в будь-якому випадку.

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


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

Доступ до змінних екземплярів по суті означає, що він не може бути статичним. Єдиний стан, який може стати статичний метод, - це змінні класу, і єдина причина, яка повинна статися, це якщо ви маєте справу з синглетом і, таким чином, у вас немає вибору.
Лорен Печтел
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.