W jaki sposób należy zorganizować kod testu jednostkowego C ++ w celu uzyskania maksymalnej wydajności testu jednostkowego?

47
  • To pytanie nie dotyczy ram testowania jednostek.
  • To pytanie nie dotyczy pisania testów jednostkowych.
  • To pytanie dotyczy tego, gdzie umieścić kod UT i jak / kiedy / gdzie go skompilować i uruchomić.

W Praca Skutecznie z Kodeksem Legacy , Michael Feathers twierdzi, że

dobre testy jednostkowe ... działają szybko

i to

Test jednostkowy trwający 1/10 sekundy to powolny test jednostkowy.

Myślę, że te definicje mają sens. Myślę też, że implikują, że musisz zachować zestaw testów jednostkowych i zestaw tych testów kodu, które trwają dłużej osobno, ale sądzę, że to cena, którą płacisz za nazwanie czegoś tylko testem jednostkowym, jeśli przebiega (bardzo) szybko .

Oczywiście problemem w C ++ jest to, że do "run" Urządzenie Test ( s ), trzeba:

  1. Edytuj kod (produkcja lub test jednostkowy, w zależności od tego, w jakim „cyklu” jesteś)
  2. Skompilować
  3. Połączyć
  4. Rozpocznij Test jednostki wykonywalny ( s )

Edytuj (po dziwnym zamkniętym głosowaniu) : Zanim przejdę do szczegółów, postaram się streścić tutaj:

W jaki sposób można skutecznie zorganizować kod testu jednostkowego C ++, aby zarówno edycja (testowa) kodu, jak i uruchomienie kodu testowego były efektywne?


Pierwszym problemem jest więc zdecydować , gdzie umieścić kod jednostki testowej, tak aby:

  • „naturalne” jest edytowanie i wyświetlanie w połączeniu z powiązanym kodem produkcyjnym.
  • łatwo / szybko rozpocząć cykl kompilacji dla aktualnie zmienianej jednostki

Sekund związanym z tym problemem jest więc co do kompilacji tak, że opinia jest natychmiastowa.

Ekstremalne opcje:

  • Każda jednostka-test-test-jednostka znajduje się w osobnym pliku cpp, a ten plik cpp jest kompilowany + połączony osobno (wraz z testowanym plikiem źródłowym jednostki kodu) do jednego pliku wykonywalnego, który następnie uruchamia ten jeden test jednostki.
    • (+) Minimalizuje to czas uruchamiania (kompilacja + link!) Dla pojedynczej jednostki testowej.
    • (+) Test przebiega bardzo szybko, ponieważ testuje tylko jedną jednostkę.
    • (-) Wykonanie całego pakietu będzie wymagało uruchomienia bazillionu procesów. Problemem może być zarządzanie.
    • (-) Koszt rozpoczęcia procesu stanie się widoczny
  • Drugą stroną byłoby posiadanie - nadal - jednego pliku cpp na test, ale wszystkie testowe pliki cpp (wraz z testowanym kodem!) Są połączone w jeden plik wykonywalny (na moduł / na projekt / wybór).
    • (+) Czas kompilacji nadal będzie OK, ponieważ skompiluje się tylko zmieniony kod.
    • (+) Wykonanie całego pakietu jest łatwe, ponieważ jest tylko jeden plik exe do uruchomienia.
    • (-) Łączenie zestawu zajmie wieki, ponieważ każda ponowna kompilacja dowolnego obiektu spowoduje ponowne połączenie.
    • (-) (?) Uruchomienie kombinezonu potrwa dłużej, chociaż jeśli wszystkie testy jednostkowe są szybkie, czas powinien być OK.

Jak zatem przeprowadzane są testy jednostkowe C ++ w świecie rzeczywistym ? Jeśli uruchamiam te rzeczy co noc / co godzinę, druga część nie ma tak naprawdę znaczenia, ale pierwsza część, a mianowicie, jak „połączyć” kod UT z kodem produkcyjnym, tak aby „naturalne” dla programistów trzymanie obu skupienie zawsze ma znaczenie. (A jeśli programiści skupią się na kodzie UT, będą chcieli go uruchomić, co spowoduje powrót do drugiej części).

Doceniamy prawdziwe historie i doświadczenie!

Uwagi:

  • To pytanie celowo pozostawia nieokreśloną platformę i system make / project.
  • Pytania oznaczone UT i C ++ to świetne miejsce do rozpoczęcia, ale niestety zbyt wiele pytań, a zwłaszcza odpowiedzi, jest zbyt mocno skoncentrowanych na szczegółach lub konkretnych ramach.
  • Jakiś czas temu odpowiedziałem na podobne pytanie dotyczące struktury testów jednostek doładowania. Uważam, że brakuje tej struktury do „prawdziwych”, szybkich testów jednostkowych. Drugie pytanie uważam za zbyt wąskie, stąd nowe pytanie.
Martin Ba
źródło
6
Dalszą komplikację wprowadza idiom C ++ polegający na przenoszeniu jak największej liczby błędów do kompilacji czasu - dobry zestaw testów jednostkowych często musi być w stanie przetestować, czy pewne użycie nie kompiluje się.
3
@Closers: Czy możesz podać argumenty, które doprowadziły Cię do zamknięcia tego pytania?
Luc Touraille,
Nie bardzo rozumiem, dlaczego to musiało zostać zamknięte. :-(Gdzie masz szukać odpowiedzi na takie pytania, jeśli nie na tym forum?
2
@Joe: Dlaczego potrzebuję testu jednostkowego, aby sprawdzić, czy coś się skompiluje, kiedy kompilator i tak mi to powie?
David Thornley,
2
@David: Ponieważ chcesz się upewnić, że się nie skompiluje . Szybki przykład to obiekt potokowy, który przyjmuje dane wejściowe i generuje dane wyjściowe, Pipeline<A,B>.connect(Pipeline<B,C>)powinien się kompilować, Pipeline<A,B>.connect(Pipeline<C,D>)ale nie powinien się kompilować: Typ wyjściowy pierwszego stopnia jest niezgodny z typem wejściowym drugiego stopnia.
sebastiangeiger

Odpowiedzi:

6

Mamy wszystkie testy jednostkowe (dla modułu) w jednym pliku wykonywalnym. Testy są podzielone na grupy. Mogę wykonać pojedynczy test (lub niektóre testy) lub grupę testów, podając nazwę (test / grupa) w wierszu polecenia programu uruchamiającego testy. System kompilacji może uruchomić grupę „Kompilacja”, dział testowy może uruchomić „Wszystkie”. Deweloper może umieścić kilka testów w grupie takiej jak „BUG1234”, gdzie 1234 to numer śledzenia problemów w sprawie, nad którą pracuje.

ur.
źródło
6

Po pierwsze, nie zgadzam się z „1) Edytuj kod (produkcyjny) i test jednostkowy”. Powinieneś modyfikować tylko jeden na raz, w przeciwnym razie, jeśli wynik się zmieni, nie będziesz wiedział, który to spowodował.

Lubię umieszczać testy jednostkowe w drzewie katalogów, które przesłania główne drzewo. Jeśli mam /sources/componentA/alpha/foo.cci /objects/componentA/beta/foo.o, to chcę coś takiego jak /UTest_sources/componentA/alpha/test_foo.cci /UTest_objects/componentA/beta/test_foo.o. Używam tego samego drzewa cieni do obiektów stub / mock i wszelkich innych źródeł, których potrzebują testy. Będą pewne przypadki krawędzi, ale ten schemat bardzo upraszcza rzeczy. Dobre makro edytora może bez problemu pobrać źródło testowe obok źródła tematu. Dobry system kompilacji (np. GNUMake) może skompilować oba i uruchomić test za pomocą jednej komendy (np. make test_foo) I może zarządzać bazillionem takich procesów - tylko tych, których źródła zmieniły się od czasu ostatniego testowania - dość łatwo (mam nigdy nie uważałem, że narzut związany z uruchomieniem tych procesów jest problemem, to O (N)).

W tym samym środowisku można przeprowadzać testy na większą skalę (już nie testy jednostkowe), które łączą ze sobą wiele obiektów i uruchamiają wiele testów. Sztuką jest posortowanie tych testów według czasu potrzebnego do ich zbudowania / uruchomienia i odpowiednie dostosowanie ich do codziennego harmonogramu. Uruchom test trwający jedną sekundę lub mniej, kiedy tylko masz na to ochotę; rozpocznij dziesięciosekundowy test i rozciągnij; pięciominutowy test i zrób sobie przerwę; półgodzinny test i idź na lunch; sześciogodzinny test i idź do domu. Jeśli okaże się, że marnujesz dużo czasu, np. Ponownie analizujesz ogromny test po zmianie tylko jednego małego pliku, robisz to źle - nawet gdyby połączenie było natychmiastowe, nadal przeprowadzałbyś długi test, gdy nie został wezwany.

Beta
źródło
ad (1) - tak, to było niechlujnie sformułowane
Martin Ba