Testy jednostkowe C ++: Co testować?

20

TL; DR

Pisanie dobrych, przydatnych testów jest trudne i wiąże się z wysokimi kosztami w C ++. Czy doświadczeni programiści mogą podzielić się uzasadnieniem na temat tego, co i kiedy testować?

Długa historia

Kiedyś zajmowałem się programowaniem opartym na testach, właściwie cały mój zespół, ale nie działało to dobrze dla nas. Mamy wiele testów, ale nigdy nie wydają się obejmować przypadków, w których mamy rzeczywiste błędy i regresje - które zwykle występują, gdy jednostki wchodzą w interakcje, a nie z powodu ich izolowanego zachowania.

Często jest to tak trudne do przetestowania na poziomie jednostki, że przestaliśmy robić TDD (z wyjątkiem komponentów, w których to naprawdę przyspiesza rozwój) i zamiast tego zainwestowaliśmy więcej czasu w zwiększenie zasięgu testu integracji. Podczas gdy testy małych jednostek nigdy nie wykryły żadnych prawdziwych błędów i były po prostu tylko kosztami utrzymania, testy integracyjne były naprawdę warte wysiłku.

Teraz odziedziczyłem nowy projekt i zastanawiam się, jak zacząć go testować. Jest to natywna aplikacja C ++ / OpenGL, więc testy integracyjne nie są tak naprawdę opcją. Ale testowanie jednostkowe w C ++ jest nieco trudniejsze niż w Javie (musisz jawnie tworzyć różne rzeczy virtual), a program nie jest silnie zorientowany obiektowo, więc nie mogę wyśmiewać / usuwać niektórych rzeczy.

Nie chcę się rozrywać i OO-ize wszystko po prostu napisać kilka testów na potrzeby pisania testów. Więc pytam: do czego powinienem pisać testy? na przykład:

  • Funkcje / klasy, które często się zmieniam?
  • Funkcje / klasy, które trudniej przetestować ręcznie?
  • Funkcje / klasy, które są już łatwe do przetestowania?

Zacząłem badać niektóre pełne szacunku podstawy kodu C ++, aby zobaczyć, jak przebiegają testy. W tej chwili szukam kodu źródłowego Chromium, ale trudno mi wyodrębnić z niego uzasadnienie testowania. Jeśli ktoś ma dobry przykład lub post na temat tego, jak podchodzą do tego popularni użytkownicy C ++ (faceci z komitetu, autorzy książek, Google, Facebook, Microsoft, ...), byłoby to bardzo pomocne.

Aktualizacja

Szukałem w tej witrynie i Internecie od momentu napisania tego. Znaleziono kilka dobrych rzeczy:

Niestety, wszystkie z nich są raczej skoncentrowane na Javie / C #. Pisanie wielu testów w Javie / C # nie jest dużym problemem, więc korzyść zwykle przewyższa koszty.

Ale jak napisałem powyżej, w C ++ jest trudniej. Zwłaszcza, jeśli twoja baza kodu nie jest tak bardzo OO, musisz poważnie zepsuć wszystko, aby uzyskać dobry zasięg testu jednostkowego. Na przykład: odziedziczona aplikacja ma Graphicsprzestrzeń nazw, która jest cienką warstwą powyżej OpenGL. W celu przetestowania któregokolwiek z podmiotów - które bezpośrednio korzystają z jego funkcji - musiałbym przekształcić to w interfejs i klasę i wstrzyknąć to we wszystkie byty. To tylko jeden przykład.

Odpowiadając na to pytanie, należy pamiętać, że muszę poczynić dość dużą inwestycję w pisanie testów.

futlib
źródło
3
+1 za trudność w testowaniu jednostkowym C ++. Jeśli Twój test jednostkowy wymaga zmiany kodu, nie rób tego.
DPD,
2
@DPD: Nie jestem pewien, co jeśli naprawdę warto coś przetestować? W obecnej bazie kodu nie mogę prawie niczego przetestować w kodzie symulacyjnym, ponieważ wszystkie one wywołują funkcje graficzne bezpośrednio i nie mogę ich wyśmiewać / usuwać. Wszystko, co mogę teraz przetestować, to funkcje narzędziowe. Ale zgadzam się, zmiana kodu, aby był „testowalny”, wydaje się ... błędny. Zwolennicy TDD często twierdzą, że dzięki temu cały Twój kod będzie lepszy na wszystkie możliwe sposoby, ale pokornie się nie zgadzam. Nie wszystko wymaga interfejsu i kilku implementacji.
futlib
Pozwól, że dam ci ostatni przykład: spędziłem cały dzień próbując przetestować jedną funkcję singe (napisaną w C ++ / CLI), a narzędzie testowe MS Test zawsze zawiesza się podczas tego testu. Wydawało się, że ma problem z prostymi referencjami CPP. Zamiast tego właśnie przetestowałem wyjście funkcji wywołującej i działało dobrze. Zmarnowałem cały dzień na UT jedną funkcję. To była strata cennego czasu. Nie mogłem też znaleźć żadnego narzędzia do stubowania odpowiadającego moim potrzebom. Wszędzie, gdzie to możliwe, robiłem ręczne stubowanie.
DPD,
Tego właśnie chciałbym uniknąć: DI sądzę, że my, twórcy C ++, musimy być szczególnie pragmatyczni w testowaniu. W końcu to przetestowałeś, więc myślę, że to OK.
futlib
@DPD: Zastanowiłem się nad tym i myślę, że masz rację, pytanie brzmi, jaki rodzaj kompromisu chcę zrobić. Czy warto refaktoryzować cały system graficzny, aby przetestować kilka podmiotów? Nie było tam żadnego błędu, o którym wiem, więc prawdopodobnie: Nie. Jeśli zacznie się mylić, napiszę testy. Szkoda, że ​​nie mogę przyjąć twojej odpowiedzi, ponieważ jest to komentarz :)
futlib

Odpowiedzi:

5

Testowanie jednostkowe to tylko jedna część. Testy integracyjne pomagają rozwiązać problem Twojego zespołu. Testy integracyjne można pisać dla wszystkich rodzajów aplikacji, także dla aplikacji rodzimych i OpenGL. Powinieneś sprawdzić „Rosnące oprogramowanie obiektowe kierowane testami” Steve Freemann i Nat Pryce (np. Http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Prowadzi Cię krok po kroku przez rozwój aplikacji z graficznym interfejsem użytkownika i komunikacją sieciową.

Testowanie oprogramowania, które nie było testowane, to inna historia. Sprawdź, czy Michael Feathers „Efektywnie współpracuje ze starszym kodem” (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).

EricSchaefer
źródło
Znam obie książki. Chodzi o to: 1. Nie chcemy korzystać z TDD, ponieważ nie działało to z nami dobrze. Chcemy testów, ale nie religijnie. 2. Jestem pewien, że testy integracji aplikacji OpenGL są w jakiś sposób możliwe, ale wymaga to zbyt wiele wysiłku. Chcę ciągle ulepszać aplikację, a nie rozpoczynać projekt badawczy.
futlib
Należy pamiętać, że testowanie jednostkowe „po fakcie” jest zawsze trudniejsze niż „najpierw test”, ponieważ kod nie jest zaprojektowany do testowania (ponownego użycia, konserwacji itp.). Jeśli mimo wszystko chcesz to zrobić, spróbuj trzymać się sztuczek Michaela Feathersa (np. Szwów), nawet jeśli unikniesz TDD. Np. Jeśli chcesz rozszerzyć funkcję, spróbuj czegoś takiego jak „metoda kiełkowania” i spróbuj przetestować nową metodę. Można to zrobić, ale trudniej IMHO.
EricSchaefer
Zgadzam się, że kod inny niż TDD nie został zaprojektowany do testowania, ale nie powiedziałbym, że nie jest on sam w sobie możliwy do konserwacji ani wielokrotnego użytku - jak powiedziałem powyżej, niektóre rzeczy po prostu nie wymagają interfejsów i wielu implementacji. Z Mockito wcale nie jest to problem, ale w C ++ muszę ustawić wszystkie funkcje, które chcę upodabiać / wyśmiać. W każdym razie, nie dający się przetestować kod jest obecnie moim największym problemem: muszę zmienić kilka bardzo podstawowych rzeczy, aby niektóre części mogły być testowane, i dlatego chcę mieć dobre uzasadnienie co do testowania, aby upewnić się, że warto.
futlib
Oczywiście masz rację, będę ostrożny, aby każdy nowy kod, który piszę, był testowalny. Ale to nie będzie łatwe, biorąc pod uwagę sposób, w jaki teraz działają rzeczy w tej bazie kodu.
futlib
Po dodaniu funkcji / funkcji zastanów się, jak możesz ją przetestować. Czy możesz wstrzyknąć jakieś brzydkie zależności? Skąd wiesz, że funkcja wykonuje to, co powinna? Czy potrafisz zaobserwować jakieś zachowanie? Czy jest jakiś wynik, który można sprawdzić pod kątem poprawności? Czy są jakieś niezmienniki, które możesz sprawdzić?
EricSchaefer
2

Szkoda, że ​​TDD „nie działało dobrze”. Myślę, że to jest klucz do zrozumienia, gdzie się zwrócić. Odwiedź ponownie i zrozum, jak TDD nie działało, co mogłeś zrobić lepiej, dlaczego były trudności.

Oczywiście twoje testy jednostkowe nie wykryły znalezionych błędów. Właśnie o to chodzi. :-) Nie znalazłeś tych błędów, ponieważ zapobiegłeś ich wystąpieniu, zastanawiając się, jak powinny działać interfejsy i jak się upewnić, że zostały odpowiednio przetestowane.

Aby odpowiedzieć, kwestionujesz, jak już stwierdziłeś, kod testowania jednostkowego, który nie jest przeznaczony do testowania, jest trudny. W przypadku istniejącego kodu bardziej efektywne może być użycie testowego środowiska funkcjonalnego lub integracyjnego niż testowego. Przetestuj system ogólnie, koncentrując się na określonych obszarach.

Oczywiście nowy rozwój będzie korzystał z TDD. W miarę dodawania nowych funkcji refaktoryzacja TDD może pomóc przetestować nowy rozwój, jednocześnie umożliwiając opracowanie nowego testu jednostkowego starszych funkcji.

Bill Door
źródło
4
Robiliśmy TDD przez około półtora roku, wszyscy z pasją. Jednak porównując projekty TDD z wcześniejszymi wykonanymi bez TDD (ale nie bez testów), nie powiedziałbym, że są one bardziej stabilne lub mają lepiej zaprojektowany kod. Może to nasz zespół: często łączymy się w pary i recenzujemy, nasza jakość kodu zawsze była całkiem dobra.
futlib
1
Im więcej o tym myślę, tym bardziej myślę, że TDD po ​​prostu nie pasowało do technologii tego konkretnego projektu: Flex / Swiz. Wiele zdarzeń, powiązań i iniekcji powoduje, że interakcje między obiektami są skomplikowane i prawie niemożliwe do przetestowania jednostkowego. Odsprzęgnięcie tych obiektów nie poprawia tego, ponieważ działają one przede wszystkim poprawnie.
futlib
2

Nie zrobiłem TDD w C ++, więc nie mogę tego komentować, ale powinieneś przetestować oczekiwane zachowanie twojego kodu. Chociaż implementacja może się zmienić, zachowanie powinno (zwykle?) Pozostać takie samo. W świecie zorientowanym na Java \ C # oznacza to, że testujesz tylko metody publiczne, piszesz testy pod kątem oczekiwanego zachowania i robisz to przed implementacją (co zwykle lepiej powiedzieć niż zrobić :)).

Dante
źródło