Блок тестування приватного методу в c ++, використовуючи клас друзів


15

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

1) Складіть клас друзів з класу, чий метод я хочу перевірити.

2) У класі друзів створіть загальнодоступні методи, які викликають приватні методи тестового класу.

3) Перевірте загальнодоступні методи класу друзів.

Ось простий приклад для ілюстрації вищезазначених кроків:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

Редагувати:

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

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

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


5
Ось сумнівна пропозиція щодо сумнівного питання. Мені не подобається зв'язок друга, оскільки тоді випущений код повинен знати про тест. Відповідь Ніра нижче - це один із способів полегшити це, але я все ще не дуже люблю змінювати клас, щоб відповідати тесту. Оскільки я часто не покладаюся на спадщину, я іноді просто захищаю приватні методи інакше, і тестовий клас успадковують та виставляють за потребою. Я очікую, що принаймні три "бу-шипі" для цього коментаря, але реальність полягає в тому, що публічний API і тестовий API можуть відрізнятися і все-таки відрізнятися від приватного API. Мех.
J Trana

4
@JTrana: Чому б не написати це як належну відповідь?
Барт ван Іґен Шенау

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

Відповіді:


23

Альтернатива другові (ну, в певному сенсі), яку я часто використовую - це модель, яку я дізнався як access_by. Це досить просто:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

Тепер, припустимо, клас B бере участь у тестуванні А. Ви можете написати це:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

Потім ви можете скористатися цією спеціалізацією access_by для виклику приватних методів А. В основному, це робиться для того, щоб покласти наголошення про дружбу у файлі заголовка класу, який хоче викликати приватні методи A. Це також дозволяє додавати друзів до A без зміни джерела. Ідіоматично він також вказує на того, хто читає джерело A, що A не вказує на B справжнього друга в сенсі розширення його інтерфейсу. Швидше за все, інтерфейс A є повним, як задано, і B потрібен спеціальний доступ до A (тестування є гарним прикладом, я також використовував цю схему при впровадженні примусових прив'язків python. Іноді функція, яка повинна бути приватною в C ++, зручна для виставити в шар python для реалізації).


Цікаво про те, чи справжній випадок використання для створення friend access_by, хіба перший недруг є достатнім - будучи вкладеною структурою, він отримав би доступ до всього в межах А? напр. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
tangy

10

Якщо важко перевірити, це погано написано

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

Витягніть приватні методи, які ви хочете перевірити, у новий клас (або класи) та зробіть їх загальнодоступними. Перевірте нові класи.

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


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

4
У моїй кодовій базі клас має дуже складний метод, який ініціалізує обчислювальний графік. Він викликає кілька підфункцій послідовно для досягнення різних аспектів цього. Кожна підфункція є досить складною, а загальна сума коду дуже складною. Однак підфункції є безглуздими, якщо їх не викликають у цьому класі та у правильній послідовності. Все, про що піклується користувач, - це повністю ініціалізована обчислювальна графіка; проміжні продукти не потрібні користувачеві. Будь ласка, я хотів би почути, як мені це робити рефактор, і чому це має більше сенсу, ніж просто тестувати приватні методи.
Нір Фрідман

1
@Nir: Зробіть тривіальне: витягніть клас із усіма цими методами загальнодоступними та зробіть свій існуючий клас фасадом навколо нового класу.
кевін клайн

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

4

Ви не повинні тестувати приватні методи. Період. Класи, що використовують ваш клас, дбають лише про методи, які він пропонує, а не ті, які він використовує під капотом для роботи.

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


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

8
Це не завжди може бути практичним чи корисним, але це дуже далеко від того, що ти ніколи не повинен тестувати їх. Ви говорите про приватні методи, ніби вони є чиїсь приватні методи; "вони могли з'явитися, зникнути ...". Ні, вони не могли. Якщо ви випробовуєте їх безпосередньо, це має бути лише тому, що ви їх підтримуєте самі. Якщо ви змінюєте реалізацію, ви змінюєте тести. Коротше кажучи, ваша заява на бланк є невиправданою. Хоча добре попередити ОП про це, його питання все ще виправдане, а ваша відповідь насправді не відповідає на нього.
Нір Фрідман

2
Дозвольте також зазначити: ОП заявила напередодні, що він знає, що це дискусія. Тож якщо він хоче все-таки це зробити, можливо, у нього справді є вагомі причини? Ніхто з нас не знає подробиць його кодової бази. У коді, з яким я працюю, у нас є дуже досвідчені та досвідчені програмісти, і вони вважали, що в деяких випадках корисно використовувати тести приватних методів.
Нір Фрідман

2
-1, відповідь на кшталт "Ви не повинні тестувати приватні методи". ІМХО не корисний. Тема, коли тестувати, а коли не тестувати приватні методи, була достатньо обговорена на цьому сайті. ОП показала, що він знає про цю дискусію, і, очевидно, він шукає рішення, припускаючи, що в його випадку тестування приватних методів - це шлях.
Док Браун

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

3

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

  • ви можете додати декларацію друга (класу чи функції) до тестованого класу.

  • ви можете додати #define private publicдо початку тестових файлів до введення тестового #includeкоду. Якщо протестований код є вже складеною бібліотекою, це може змусити заголовки більше не збігатися з уже складеним бінарним кодом (і викликати UB).

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


2

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

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

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


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

0

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

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

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

Ви повинні зважувати, чи варто це чи ні. І багато чого буде насправді залежати від того, хто буде бачити ваш заголовок.

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

Тестування - це робота письменників бібліотеки, а не її користувачів.

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

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

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