Metoda prywatna testu jednostkowego w c ++ przy użyciu klasy znajomego

15

Wiem, że jest to dyskusyjna praktyka, ale załóżmy, że jest to dla mnie najlepsza opcja. Zastanawiam się, jaka jest właściwa technika, aby to zrobić. Podejście, które widzę jest następujące:

1) Zrób klasę przyjaciela klasą, której metodę chcę przetestować.

2) W klasie przyjaciela utwórz metodę publiczną, która wywołuje metodę prywatną testowanej klasy.

3) Przetestuj publiczne metody klasy przyjaciela.

Oto prosty przykład ilustrujący powyższe kroki:

#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;
}

Edytować:

Widzę, że w dyskusji po jednej z odpowiedzi ludzie zastanawiają się nad moją bazą kodu.

Moja klasa ma metody wywoływane przez inne metody; żadna z tych metod nie powinna być wywoływana poza klasą, więc powinny być prywatne. Oczywiście można je zastosować w jednej metodzie, ale logicznie są one znacznie lepiej oddzielne. Metody te są na tyle skomplikowane, że wymagają testów jednostkowych, a ze względu na problemy z wydajnością najprawdopodobniej będę musiał ponownie rozważyć te metody, dlatego byłoby miło mieć test, aby upewnić się, że moje przefakturowanie niczego nie zepsuło. Nie tylko ja pracuję w zespole, ale tylko ja pracuję nad tym projektem, w tym nad testami.

Powiedziawszy powyższe, moje pytanie nie dotyczyło tego, czy dobrą praktyką jest pisanie testów jednostkowych dla metod prywatnych, choć doceniam informację zwrotną.

Akavall
źródło
5
Oto wątpliwa sugestia na wątpliwe pytanie. Nie podoba mi się łączenie przyjaciela, ponieważ wtedy wypuszczony kod musi wiedzieć o teście. Poniższa odpowiedź Nira jest jednym ze sposobów na złagodzenie tego, ale nadal nie lubię zmieniać klasy, aby dostosować się do testu. Ponieważ często nie polegam na dziedziczeniu, czasami po prostu zabezpieczam metody prywatne, które w przeciwnym razie są chronione, i dziedziczę klasę testową i udostępniam ją w razie potrzeby. Spodziewam się co najmniej trzech „syków” dla tego komentarza, ale w rzeczywistości publiczny interfejs API i interfejs API testowania mogą się różnić i nadal różnić się od prywatnego interfejsu API. Meh
J Trana
4
@JTrana: Dlaczego nie napisać tego jako prawidłowej odpowiedzi?
Bart van Ingen Schenau
Dobrze. Nie jest to jeden z tych, z których czujesz się bardzo dumny, ale mam nadzieję, że to pomoże.
J Trana

Odpowiedzi:

23

Alternatywą dla znajomego (cóż, w pewnym sensie), którego często używam, jest wzorzec, który poznałem jako access_by. To całkiem proste:

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

Załóżmy teraz, że klasa B bierze udział w testowaniu A. Możesz to napisać:

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

Następnie możesz użyć tej specjalizacji access_by, aby wywołać prywatne metody A. Zasadniczo to powoduje obciążenie deklaracją przyjaźni w pliku nagłówkowym klasy, która chce wywołać prywatne metody A. Pozwala także dodawać znajomych do A bez zmiany źródła A. Idiomatycznie wskazuje również każdemu, kto czyta źródło A, że A nie oznacza B prawdziwego przyjaciela w sensie rozszerzenia jego interfejsu. Interfejs A jest raczej kompletny, jak podano, i B potrzebuje specjalnego dostępu do A (testowanie jest dobrym przykładem, użyłem tego wzorca również podczas implementacji powiązań Python Boost, czasem funkcja, która musi być prywatna w C ++ jest przydatna do eksponować w warstwie pytona do implementacji).

Nir Friedman
źródło
Ciekawe, czy uzasadniony przypadek użycia jest friend access_bywystarczający, aby pierwszy nie-przyjaciel był wystarczający - będąc zagnieżdżoną strukturą miałby dostęp do wszystkiego w obrębie A? na przykład. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
tangy
10

Jeśli trudno go przetestować, jest źle napisany

Jeśli masz klasę z prywatnymi metodami wystarczająco złożonymi, aby uzasadnić własny test, klasa robi za dużo. Wewnątrz jest kolejna klasa próbująca się wydostać.

Wyodrębnij prywatne metody, które chcesz przetestować, do nowej klasy (lub klas) i upublicznij je. Przetestuj nowe klasy.

Oprócz ułatwienia testowania kodu, refaktoryzacja sprawi, że kod będzie łatwiejszy do zrozumienia i utrzymania.

Kevin Cline
źródło
1
Całkowicie zgadzam się z tą odpowiedzią, jeśli nie możesz całkowicie przetestować swoich prywatnych metod poprzez testowanie publicznych metod, coś jest nie tak i refaktoryzacja prywatnych metod do ich własnej klasy byłaby dobrym rozwiązaniem.
David Perfors
4
W mojej bazie kodu klasa ma bardzo złożoną metodę, która inicjuje graf obliczeniowy. Wywołuje kolejno kilka podfunkcji, aby osiągnąć różne aspekty tego. Każda podfunkcja jest dość skomplikowana, a suma kodu jest bardzo skomplikowana. Jednak podfunkcje są bez znaczenia, jeśli nie zostaną wywołane w tej klasie i we właściwej kolejności. Jedyne, na czym zależy użytkownikowi, to pełne zainicjowanie wykresu obliczeniowego; półprodukty są bezwartościowe dla użytkownika. Chciałbym usłyszeć, jak powinienem to refaktoryzować i dlaczego ma to większy sens niż tylko testowanie metod prywatnych.
Nir Friedman
1
@Nir: Zrób to, co trywialne: wyodrębnij klasę przy użyciu tych wszystkich metod publicznych i spraw, aby twoja istniejąca klasa stała się fasadą wokół nowej klasy.
kevin cline
Ta odpowiedź jest poprawna, ale tak naprawdę zależy od danych, z którymi pracujesz. W moim przypadku nie otrzymuję rzeczywistych danych testowych, więc muszę je utworzyć, obserwując dane w czasie rzeczywistym, a następnie „wstrzyknąć” je do mojej aplikacji i zobaczyć, jak to zrobić. Pojedynczy fragment danych jest zbyt skomplikowany, aby można go było obsługiwać, dlatego znacznie łatwiej byłoby sztucznie utworzyć częściowe dane testowe, które stanowią jedynie podzbiór danych w czasie rzeczywistym, aby można było na nie celować, niż faktycznie odtworzyć dane w czasie rzeczywistym. Funkcja prywatna nie jest złożona wystarczy, aby wezwać do wdrożenia kilku innych małych klas (z odpowiednią funkcjonalnością)
rbaleksandar
4

Nie powinieneś testować prywatnych metod. Kropka. Klasy korzystające z twojej klasy dbają tylko o metody, które udostępnia, a nie te, które używa pod maską do pracy.

Jeśli martwisz się o swój zasięg kodu, musisz znaleźć konfiguracje, które pozwalają przetestować tę prywatną metodę z jednego z publicznych wywołań metod. Jeśli nie możesz tego zrobić, jaki jest sens posiadania tej metody? To po prostu nieosiągalny kod.

Ampt
źródło
6
Prywatne metody mają na celu ułatwienie rozwoju (poprzez rozdzielanie obaw, utrzymywanie SUSZENIA lub dowolną liczbę rzeczy), ale mają one być nietrwałe. Z tego powodu są prywatne. Mogą się pojawiać, znikać lub drastycznie zmieniać funkcjonalność z jednej implementacji na drugą, dlatego powiązanie ich z testami jednostkowymi nie zawsze jest praktyczne, a nawet przydatne.
Ampt
8
Nie zawsze może to być praktyczne lub przydatne, ale jest to bardzo dalekie od stwierdzenia, że ​​nigdy nie powinieneś ich testować. Mówisz o metodach prywatnych, tak jakby były to metody prywatne innej osoby; „mogą pojawić się, zniknąć ...”. Nie, nie mogli. Jeśli testujesz je bezpośrednio, powinno to być spowodowane tym, że sam je utrzymujesz. Jeśli zmienisz implementację, zmienisz testy. Krótko mówiąc, twoje ogólne oświadczenie jest nieuzasadnione. Chociaż dobrze jest ostrzec PO przed tym, jego pytanie jest nadal uzasadnione, a twoja odpowiedź tak naprawdę nie odpowiada.
Nir Friedman
2
Pragnę również zauważyć: OP powiedział z góry, że jest świadomy, że jest to dyskusja na temat praktyki. Więc jeśli i tak chce to zrobić, może naprawdę ma ku temu dobre powody? Żadne z nas nie zna szczegółów jego bazy kodów. W kodzie, z którym pracuję, mamy bardzo doświadczonych i doświadczonych programistów, którzy uważają, że w niektórych przypadkach przydatne jest testowanie metod prywatnych.
Nir Friedman
2
-1, odpowiedź typu „Nie powinieneś testować metod prywatnych”. czy IMHO nie jest pomocne. Temat, kiedy należy testować, a kiedy nie testować metod prywatnych, został wystarczająco omówiony na tej stronie. OP pokazał, że jest świadomy tej dyskusji, i wyraźnie widać, że szuka rozwiązań przy założeniu, że w jego przypadku testowanie prywatnych metod jest właściwą drogą.
Doc Brown
3
Myślę, że problem polega na tym, że może to być bardzo klasyczny problem XY, ponieważ OP uważa, że ​​musi testować swoje prywatne metody z tego czy innego powodu, podczas gdy w rzeczywistości mógłby podejść do testowania z bardziej pragmatycznego punktu widzenia, patrząc na prywatne metody jako zwykłe funkcje pomocnicze dla publicznych, które są umową z użytkownikami końcowymi klasy.
Ampt
3

Można to zrobić na kilka sposobów, ale należy pamiętać, że (w istocie) modyfikują one publiczny interfejs twoich modułów, aby dać ci dostęp do wewnętrznych szczegółów implementacji (skutecznie przekształcając testy jednostkowe w ściśle powiązane zależności klienta, gdzie powinieneś mieć żadnych zależności).

  • możesz dodać deklarację znajomego (klasy lub funkcji) do testowanej klasy.

  • możesz dodać #define private publicna początku swoich plików testowych przed #includesprawdzeniem poprawnego kodu. W przypadku, gdy testowany kod jest już skompilowaną biblioteką, może to spowodować, że nagłówki nie będą już zgodne z już skompilowanym kodem binarnym (i spowodują UB).

  • możesz wstawić makro do testowanej klasy i w późniejszym terminie zdecydować, co to makro oznacza (z inną definicją kodu testowego). Umożliwiłoby to przetestowanie wewnętrznych elementów, ale pozwoliłoby również włamać się do twojej klasy kodu klienta zewnętrznego (tworząc własną definicję w dodawanej deklaracji).

utnapistim
źródło
2

Oto wątpliwa sugestia na wątpliwe pytanie. Nie podoba mi się łączenie przyjaciela, ponieważ wtedy wypuszczony kod musi wiedzieć o teście. Odpowiedź Nira jest jednym ze sposobów na złagodzenie tego, ale nadal nie lubię zmieniać klasy w celu dostosowania się do testu.

Ponieważ często nie polegam na dziedziczeniu, czasami po prostu zabezpieczam metody prywatne, które w przeciwnym razie są chronione, i dziedziczę klasę testową i udostępniam ją w razie potrzeby. Rzeczywistość jest taka, że ​​publiczny interfejs API i testowy interfejs API mogą się różnić i nadal różnić się od prywatnego interfejsu API, co pozostawia cię w rodzaju powiązania.

Oto praktyczny przykład, dla którego uciekam się do tej sztuczki. Piszę kod osadzony i bardzo polegamy na automatach stanowych. Zewnętrzny interfejs API niekoniecznie musi wiedzieć o stanie wewnętrznego automatu stanów, ale test powinien (prawdopodobnie) przetestować zgodność ze schematem automatu stanów w dokumencie projektowym. Mogę ujawnić moduł pobierający „bieżący stan” jako chroniony, a następnie dać do niego dostęp testowy, co pozwoli mi na pełniejsze przetestowanie automatu stanów. Często uważam, że tego typu zajęcia są trudne do przetestowania jako czarna skrzynka.

J Trana
źródło
Chociaż jest to podejście Java, jest to dość standardowe ustawienie domyślnego poziomu funkcji prywatnych, pozwalając innym klasom w tym samym pakiecie (klasy testowe) na ich zobaczenie.
0

Możesz napisać kod z dużą ilością obejść, aby uniknąć konieczności korzystania ze znajomych.

Możesz pisać klasy i nigdy nie mieć żadnych prywatnych metod. Wszystko, co musisz wtedy zrobić, to wykonać funkcje implementacyjne w jednostce kompilacyjnej, pozwolić klasie zadzwonić do nich i przekazać dowolne elementy danych, do których mają dostęp.

Tak, oznacza to, że możesz zmienić podpisy lub dodać nowe metody „implementacji” bez zmiany nagłówka w przyszłości.

Musisz jednak zastanowić się, czy warto. I wiele będzie naprawdę zależeć od tego, kto zobaczy twój nagłówek.

Jeśli korzystam z biblioteki innej firmy, wolałbym nie widzieć deklaracji znajomych do testerów jednostek. Nie chcę też budować ich biblioteki i uruchamiać ich testów. Niestety, zbyt wiele zewnętrznych bibliotek open source, które zbudowałem, robi to.

Testowanie jest zadaniem autorów biblioteki, a nie jej użytkowników.

Jednak nie wszystkie klasy są widoczne dla użytkownika twojej biblioteki. Wiele klas to „implementacja”, a Ty wdrażasz je w najlepszy sposób, aby zapewnić ich prawidłowe działanie. W tych nadal możesz mieć prywatne metody i członków, ale chcesz, aby testerzy jednostek je przetestowali. Więc idź dalej i zrób to w ten sposób, jeśli dzięki temu szybciej powstanie solidny kod, który jest łatwy w utrzymaniu dla tych, którzy tego potrzebują.

Jeśli wszyscy członkowie Twojej klasy należą do Twojej firmy lub zespołu, możesz także nieco bardziej zrelaksować się na temat tej strategii, zakładając, że zezwalają na to standardy kodowania obowiązujące w Twojej firmie.

Dojną krową
źródło