Jak zaprojektować oprogramowanie gry, aby było łatwe do testowania jednostkowego?

25

Czy praktyczne jest zastosowanie środowiska testowego, takiego jak JUnit, w sytuacji tworzenia gier? Jakie rozważania projektowe możesz zastosować, aby Twoja gra była bardziej testowalna? Jakie części gry można / należy przetestować, a które należy / należy pozostawić do testów na ludziach?

Na przykład, jeśli pętla gry jest zamknięta w jednej funkcji, wydaje się, że testowanie byłoby strasznie trudne. Lubię refaktoryzować funkcję „aktualizacji”, która zajmuje deltę czasu i rozwija logikę gry; pozwala to na kilka interesujących sztuczek, takich jak możliwość spowolnienia gry poprzez podawanie jej fałszywych, wolniejszych delt czasowych.

Ricket
źródło
6
Po co marnować roboczogodziny na pisanie testów jednostkowych, skoro masz praktycznie nieskończoną armię niewolniczej siły roboczej, aby przeprowadzić dla Ciebie test playtowy? Żartuję, żartuję ...;)
Mike Strobel
1
Właściwie to nie potrzebujesz dzieciaka, to naprawdę dobra uwaga! Nie myślałem o tym, ale gry są najłatwiejszym rodzajem oprogramowania, które można przetestować. Myślę, że to nieco równoważy trudność automatycznego testowania gry (jednostkowego lub innego).
Ricket
3
Testowanie jednostkowe niekoniecznie polega na upewnieniu się, że aplikacja działa poprawnie jako całość (tj. Testowanie funkcjonalne). Chodzi o to, aby przyszłe zmiany nie uszkodziły istniejącej funkcjonalności. Jasne, użytkownik może zobaczyć, kiedy statek kosmiczny jest odwrócony do góry nogami, ale gdy algorytm patowania jest wyłączony do 0,001, efekty mogą nie być widoczne, dopóki gra nie zostanie uruchomiona przez 200 godzin. Tego rodzaju testy jednostkowe mogą uchwycić, zanim kod kiedykolwiek wyjdzie na jaw.
Wade Williams,
1
Gry to najłatwiejsze oprogramowanie, dzięki któremu użytkownicy mogą grać , ale granie! = Testowanie . Otrzymywanie dobrych raportów o błędach jest dość trudne. Poza tym śledzenie, w którym miejscu kodu występuje błąd, i sprawdzanie, czy nowe zmiany nie psują istniejącego kodu, obie korzystają drastycznie z automatycznych testów.
hojny

Odpowiedzi:

25

Jednym z założeń TDD jest to, że pozwalasz TDD w niektórych przypadkach wpływać na twój projekt. Piszesz test dla systemu, a następnie piszesz kod, aby ten test przeszedł pomyślnie, utrzymując zależności tak płytkie, jak to możliwe.

Dla mnie są tylko dwie rzeczy, których nie testuję w ramach testów jednostkowych:

Po pierwsze, nie testuję elementów wizualnych i tego, jak wyglądają rzeczy. Testuję to i obiekt będzie po właściwym uaktualnieniu, że kamera wyrzuci obiekt poza jego granice, że transformacje (przynajmniej te wykonane poza modułami cieniującymi) są wykonywane poprawnie przed przekazaniem do silnika graficznego , ale kiedy trafi w system graficzny, rysuję linię. Nie lubię próbować kpić z takich rzeczy jak DirectX.

Po drugie, tak naprawdę nie testuję głównej funkcji pętli gry. Testuję, czy każdy system będzie działał, gdy minie rozsądną różnicę, i że systemy działają razem poprawnie, gdy trzeba. Następnie po prostu aktualizuję każdy system z poprawną deltą w pętli gry. Mógłbym faktycznie przetestować, aby pokazać, że każdy system został wywołany z poprawną deltą, ale w wielu przypadkach uważam, że to przesada (chyba że robisz skomplikowaną logikę, aby uzyskać deltę, to nie jest to przesada).

Jeff
źródło
6
Pierwszy akapit jest kluczowy. Faktem jest, że TDD wymaga zaprojektowania kodu lepiej niż bez niego, po prostu pozwalając na testowanie. Oczywiście wielu uważa, że ​​są ekspertami w projektowaniu, ale oczywiście ci, którzy są pod wrażeniem swoich umiejętności, zwykle są tymi, którzy najlepiej się uczą ...
dash-tom-bang
2
Nie zgodziłbym się, że w rezultacie kod jest koniecznie lepiej zaprojektowany. Widziałem wiele obrzydliwości, w których wcześniej czysty interfejs musiał zostać rozbity i przerwany enkapsulacja w celu wstrzyknięcia testowalnych zależności.
Kylotan,
4
Uważam, że dzieje się to częściej w przypadkach, gdy testy są pisane dla wcześniej istniejących klas, a nie na odwrót.
Jeff
@ Kylotan to prawdopodobnie bardziej symptom nieprawidłowego projektu / złego projektu testu. Refaktoryzuj i używaj próbnych testów, aby przeprowadzać czyste testy, nie zhakuj kodu do gorszego stanu; to jest przeciwieństwo tego, co powinno się wydarzyć.
timoxley,
2
Kapsułkowanie jest dobrą rzeczą, ale ważniejsze jest zrozumienie DLACZEGO to dobra rzecz. Jeśli po prostu użyjesz go do ukrycia zależności i dużego brzydkiego interfejsu, prawie wykrzyczysz obciążenie, że twój projekt pod maską prawdopodobnie nie jest zbyt fajny. Hermetyzacja nie polega przede wszystkim na ukrywaniu brzydkiego kodu i zależności. Chodzi przede wszystkim o to, aby kod był łatwiejszy do zrozumienia i pozwolić konsumentowi uzyskać mniej ścieżek, którymi powinien podążać, próbując uzasadnić kod.
Martin Odhelius
19

Noel Llopis opisał testy jednostkowe w takim stopniu, w jakim, moim zdaniem, szukasz:

Jeśli chodzi o testowanie całej pętli gry, istnieje inna technika zapobiegania regresjom funkcjonalnym w kodzie. Chodzi o to, aby twoja gra była odtwarzana przez system powtórek (tj. Najpierw rejestrując dane wejściowe odtwarzacza, a następnie odtwarzając je w innym przebiegu). Podczas odtwarzania generujesz migawkę stanu każdego obiektu dla każdej klatki. Tę kolekcję stanów można następnie nazwać „złotym obrazem”. Podczas refaktoryzacji kodu ponownie uruchom powtórkę i porównaj stan każdej klatki ze stanem złotego obrazu. Jeśli się różni, test się nie powiódł.

Chociaż korzyści płynące z tej techniki są oczywiste, jest kilka rzeczy, na które należy uważać:

  • Twoja gra musi być deterministyczna, dlatego musisz uważać na takie rzeczy, jak zależność od zegara systemowego, zdarzeń systemowych / sieciowych, generatorów liczb losowych, które nie są determinowane. itp.
  • zmiany treści po wygenerowaniu złotego obrazu mogą powodować uzasadnione różnice w stosunku do twojego złotego obrazu. Nie ma sposobu na obejście tego - podczas zmiany treści musisz zregenerować swój złoty obraz.
jpaver
źródło
+1 Warto również zauważyć, że wartość tego podejścia jest bezpośrednio związana z długością „złotego obrazu”: jeśli nie jest wystarczająco długi, możesz łatwo ominąć błędy; jeśli będzie za długo, będziesz musiał dużo czekać. Ważne jest, aby starać się, aby gra działała w tym trybie może być o rząd wielkości szybsza niż w normalnych warunkach czasu wykonywania, aby zaoszczędzić czas. Pominięcie renderowania może pomóc!
Inżynier
9

Zgadzam się z komentarzami Jeffa i Jpavera. Chciałem również dodać, że przyjęcie modelu komponentu do architektury znacznie zwiększa jego testowalność. W przypadku modelu komponentu każdy komponent powinien wykonywać jedną jednostkę pracy i powinien być możliwy do przetestowania w izolacji (lub z ograniczonymi próbnymi obiektami).

Podobnie, inne części gry, które opierają się na bytach, powinny działać tylko na podzbiorze elementów. W praktyce oznacza to, że zwykle można wykpić kilka fałszywych komponentów w celu ułatwienia testowania tych obszarów lub po prostu skomponować częściowe jednostki do celów testowych. np. pomijasz komponenty renderujące i wejściowe, ponieważ nie są one potrzebne do testowania fizyki.

Na koniec zignorowałbym porady dotyczące wydajności, które otrzymujesz od niektórych osób w społeczności gier, dotyczące interfejsów. Pisz dobrze kod z interfejsami, a jeśli napotkasz problemy z wydajnością, możesz łatwo profilować, identyfikować i refaktoryzować rozwiązanie problemu. Nie przekonały mnie obawy Noela dotyczące wpływu interfejsów na wydajność lub złożoności, którą dodali.

To powiedziawszy, nie przesadzaj, próbując przetestować każdą drobiazg niezależnie. Podobnie jak większość rzeczy, testy i projektowanie polegają na znalezieniu właściwej równowagi.

Alex Schearer
źródło
Pamiętaj, że chociaż nie wydajesz się nowy w SX, odpowiedzi nie są uporządkowane, a powiedzenie czegoś takiego jak „oba powyższe komentarze” z pewnością szybko się zdezaktualizuje, ponieważ obecnie jest o jeden głos przed jednym. z dwóch pozostałych odpowiedzi w tym czasie. :)
Ricket
Dzięki, nie myślałem o tym podczas komentowania. Od tego czasu zaktualizowałem komentarz, aby wybrać poszczególne komentarze.
Alex Schearer,
3

Pomyślałem, że dodam drugą odpowiedź w odpowiedzi na komentarz OP, że użytkownik może zastąpić testy jednostkowe. Uważam, że jest to całkowicie błędne, ponieważ celem testów jednostkowych nie jest zapewnienie jakości. Jeśli chcesz mieć testy w celu zapewnienia jakości programu, powinieneś prawdopodobnie zbadać testy scenariuszy lub zainwestować w doskonały monitoring.

(Dzięki doskonałemu monitorowaniu i produktowi działającemu jako usługa rzeczywiście można przekazać klientowi pewne „testy”, ale trzeba mieć wyrafinowany system, który szybko wykrywa błędy i wycofuje odpowiedzialne zmiany. Najprawdopodobniej jest to zbyt wiele dla mały zespół programistów).

Główną zaletą testów jednostkowych jest to, że zmuszają programistę do pisania lepiej zaprojektowanego kodu. Testowanie wymaga od programisty oddzielenia problemów i enkapsulacji danych. Zachęca również programistę do korzystania z interfejsów i innych abstrakcji, aby testował tylko jedną rzecz.

Alex Schearer
źródło
Zgadzam się, z tym wyjątkiem, że testy jednostkowe nie zmuszają programistów do pisania dobrego kodu. Jest jeszcze jedna opcja: naprawdę źle napisane testy. Musisz zaangażować się w ideę testów jednostkowych ze strony programistów, aby zapobiec ich wyczerpaniu.
tenpn,
2
Jasne, ale to nie jest powód, aby wylewać dziecko z kąpielą. Używaj narzędzi odpowiedzialnie, ale najpierw używaj narzędzi.
Alex Schearer,