Autor Wątek: Pisanie GUI oraz projekt ich klas w UML  (Przeczytany 7465 razy)

Offline siso

  • Użytkownik

# Marzec 10, 2012, 00:07:05
Super, tyle teoria, już widzę jak komuś by się chciało pisać fake'ową implementację biblioteki i jeszcze na maksa rozdzielać widok od modelu i to wszystko testować.

Może w aplikacji biznesowej tak, ale tu jest gamedev i trochę inne zasady ;)
Fejkowa implementacja biblioteki to może być zaledwie kilka funkcji, ni mniej ni więcej, tylko te, z których korzysta akurat testowana jednostka. Mało tego - dla każdego przypadku testowego ta implementacja będzie zapewne różna, bo będzie odzwierciedlała oczekiwaną reakcję na ten właśnie przypadek.

Nie ma znaczenia, jaką aplikację testujesz. Każda aplikacja zawiera jakiś podział. Czy go nazwiesz warstwami czy modułami czy jakkolwiek inaczej - odseparowane części da się testować osobno. Nie ma "innych zasad" w gamedevie. Wszędzie zasady są takie same. To jest wytwarzanie oprogramowania :)

Argument o "nie chceniu" jest wysoce nietrafiony. Przestaniesz go używać, kiedy dostrzeżesz korzyści płynące z TDD :) Jedyne niechcenie jakie tu może wystąpić, to wyraźny zakaz Twojego przełożonego, poparty brakiem wiedzy klienta o korzyściach płynących dla niego z Twojego zaangażowaniu w testowanie. Innych nie znalazłem :)

Jeśli tworzy się soft z głową, to nie może być mowy o ciasnym wiązaniu komponentów, bo to zawsze zabija projekty. Przy odpowiednim ich rozmiarze, rzecz jasna ;)

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline zxc

  • Użytkownik

# Marzec 10, 2012, 00:44:58
siso: Już któryś raz słyszę od Ciebie o TTD. Nawet się zainteresowałem, dzięki :).

Możesz podać przykłady gier wyprodukowanych w ten sposób? Po pobieżnym ogląddzie widzę to tak: w przypadku małej gry TTD to strata czasu, w przypadku dużej gry - duża gra to strata czasu, zrób małą. Polecanie TTD startującym developerom to chyba nieporozumienie wobec tego. Jakie masz na to kontrprzykłady. Jakieś success stories?

Offline siso

  • Użytkownik

# Marzec 10, 2012, 01:01:14
siso: Już któryś raz słyszę od Ciebie o TTD. Nawet się zainteresowałem, dzięki :).

Możesz podać przykłady gier wyprodukowanych w ten sposób? Po pobieżnym ogląddzie widzę to tak: w przypadku małej gry TTD to strata czasu, w przypadku dużej gry - duża gra to strata czasu, zrób małą. Polecanie TTD startującym developerom to chyba nieporozumienie wobec tego. Jakie masz na to kontrprzykłady. Jakieś success stories?
Nie, nie mogę Ci podać przykładów. Wybacz. Nie pracuję niestety w gamedevie, nie zbieram statystyk z pracy innych, nie powiem Ci z całą pewnością, że takiego  Wieśka 2 robiono w TDD. Podejrzewam nawet, że go tak nie robiono.
Ale to wszystko nie oznacza, że się nie da. Ani że ktoś faktycznie tego nie używa. Nie ma powodu, żeby ludzie nie zwracali się w stronę dobrych narzędzi z czasem.
I nie ma powodu, żeby to negować, zanim się spróbuje.

A success story będzie, i to jeszcze moja własna, kiedy wreszcie zbiorę się w sobie i skończę taki niewielki projekcik, który mi gdzieś tam ciągle wisi i uparcie nie chce dać się zakończyć ;)

Offline yarpen

  • Użytkownik

# Marzec 10, 2012, 01:09:47
Nie, nie mogę Ci podać przykładów. Wybacz. Nie pracuję niestety w gamedevie, nie zbieram statystyk z pracy innych, nie powiem Ci z całą pewnością, że takiego  Wieśka 2 robiono w TDD. Podejrzewam nawet, że go tak nie robiono.
Nie robiono. Nie znam zadnej gry AAA robionej w ten sposob.

Offline lmmilewski

  • Użytkownik
    • Łukasz Milewski - devblog

# Marzec 10, 2012, 08:23:26
@Avaj nie wiem co rozumiesz przez fake'ową implementację biblioteki, ale nie musisz tego robić.

załóżmy, że masz taki kod (uwaga, będzie pseudokod!):
int foo(Biblioteka* b) {
  int q = b->wtf(time(NULL));  // wywołaj wtf z argumentem time(NULL), ...
  return b->foobar(q); // a wynik przekaż do foobar i zwróć to, co zwróci foobar
}

możesz napisać taki test:
Biblioteka* b = mock.CreateMock(Biblioteka); // stwórz mock Biblioteki. (mock to biblioteka do mocków)
 // najpierw "nagrywamy" co powinno się stać
 b->wtf(mock.Ignore()).AndReturn(10); // oczekuj wywołania 'wtf' z dowolnym argumentem. Zwróć 10
 b->foobar(10).AndReturn(42) // oczekuj wywołania foobar z argumentem 10. Zwróć 42

 mock.ReplayAll(); // przejdź w tryb "odtwarzania"
 int r = foo(b); // wstrzyknięcie (dependency injection) mocka zamiast implementacji Biblioteki
 mock.VerifyAll(); // sprawdź czy wszystkie oczekiwane wywołania wystąpiły
 assertEqual(42, r); // spwardź czy wynik się zgadza

To oczywiście jeden ze sposobów testowania, ale ja go lubię - stąd taki przykład.

Jest haczyk - trzeba używać Dependency Injection. Jeżeli zamiast interfejsu użyjesz gdzieś konkretnej klasy to nie wstrzykniesz mocka. W przykładzie powyżej podmienienie time(NULL) np. jest trudne.

Z drugiej strony, jeżeli używasz DI w dużym projekcie to masz problem z inicjalizacją obiektów i zaczynasz wydziwiać z fabrykami itp. Chyba, że użyjesz frameworka do DI.

Jeżeli ktoś chce się bawić to polecam Python + mox na początek. To jest łatwy start w TDD. W Pythonie framework do mocków może wykorzystywać monkey patching, co sporo ułatwia.

W momencie, w którym zaczynasz pisać fake'ową bibliotekę, zaczynasz robić testy integracyjne, a nie jednostkowe, a tu już powiązanie z TDD nie jest aż tak oczywiste.

@siso Nie jest prawdą, że dla każdej aplikacji da się napisać łatwo testy. Pisanie testów do istniejących projektów C++ jest np. trudne. Głównie przez problemy z zależnościami. Powiem więcej - dla mnie pisanie testów do mojego kodu - napisanego kilka minut/godzin wcześniej było trudne. "test first" jest naprawdę jedynym podejściem, jeżeli nie chcesz się zrazić.

Offline Kos

  • Użytkownik
    • kos.gd

# Marzec 10, 2012, 11:00:27
Używałem unit testów w ograniczonym zakresie, do testowania jakchś tam algorytmów / ustrojstw, czy dobrze działają; nie używałem jeszcze TDD, nie mam jeszcze opinii na jego temat.

Dawałem radę używać (na małą skalę) testów bez podejścia "test first" - pisałem kod, stwierdzałem że jest skomplikowany i "niepewny", po czym pisałem do niego testy. Jeśli nie dawałem rady napisać testów, to go strukturyzowałem, by był rozbity na jakiegokolwiek rodzaju mniejsze jednostki (samo to nieraz znalazło mi buga :)) i testowałem je osobno, z powodzeniem. (Przez "z powodzeniem" rozumiem, że moje buraki wyszły w testach, a nie po wdrożeniu.)

Wyciągam stąd wniosek, że testy są bardzo fajne, ale TDD to niekoniecznie jedyna słuszna droga.

możesz napisać taki test:

Takie testy generalnie nic mi nie mówią. Widzę często takie pseudokodowe przykłady pisane przez entuzjastów, ale to jest straszne uproszczenie i myślę, że nie jestem jedynym "zielonym", któremu to niewiele mówi... :)

Myślę, że trzeba po prostu spróbować, by to polubić.

No dobra, jestem w stanie wyobrazić sobie, że mam jakąś klasę, która w reakcji na coś robi coś skomplikowanego, i akurat z góry wiem, że w konkretnym przypadku jej rezultat to ma być takie i takie wywołanie do obiektu XYZ. Wtedy mock mi pomoże napisać test.

Moje testy jednak zwykle wyglądały tak: Mam dane takie i takie, odpalam na nich algorytm i sprawdzam, czy po algorytmie dane zmieniły się w ten i ten sposób. Albo że nie zmieniły się wcale.
W takiej sytuacji mocki niewiele mi dadzą, bo nie wiem, jakie operacje mają być rezultatem akcji, wiem natomiast, jakie są oczekiwane dane.



Jeszcze trochę przeciwko TDD: Kod jestem w stanie przetestować lepiej, gdy go już napiszę. Jeśli zagadnienie uda mi się zaprogramować, to zapewne gdzieś po drodze udało mi się je zrozumieć :), zrobić sobie w głowie lub na kartce jakiś logiczny model, zidentyfikować różne dziwne edge cases bądź niespodziewane zawiłości. Jeśli mam kod, to znaczy, że zastanowiłem się i podjąłem decyzję, jak algo ma się zachowywać w tych zawiłościach.
Mogłem ofc się gdzieś pomylić, więc piszę testy. Ale teraz znam już konkretne zawiłości problemu (niekoniecznie zawiłości implementacji, ale właśnie samego problemu!) które muszę szczególnie przetestować. Nie znałem tych zawiłości przed stworzeniem kodu do testowania, więc testując później otrzymuję dokładniejsze testy.

Z tego powodu nie spieszę się z wypróbowaniem TDD.

Powyższego bardzo proszę nie mylić z "code tests against interface, not implementation" - to akurat znam i popieram :)



Mam natomiast nawyk pisania "pseudotestów" pseudokodem przed implementacją, bo to mi bardzo ułatwia wyobrażenie sobie, jak wygodnie lub niewygodnie się będzie z kodu korzystało.

Offline Avaj

  • Użytkownik

# Marzec 10, 2012, 12:20:55
@Avaj nie wiem co rozumiesz przez fake'ową implementację biblioteki, ale nie musisz tego robić.
siso wyjechał z "fake'ową implementacją biblioteki" :)

Jak dla mnie TDD to zwykły topdown design, tylko się tak ładnie nazywa i go tak zenterprise'owano

Offline Kos

  • Użytkownik
    • kos.gd

# Marzec 10, 2012, 12:42:07
Topdown design to ogólne pojęcie, a TDD to bardzo, bardzo szczególne podejście do kodowania (i to nie tylko designu).

Offline gotji

  • Użytkownik

# Marzec 10, 2012, 12:44:07
Nie można traktować UT jako lekarstwo na całe zło, testy mają swoje wady i trzeba wiedzieć co testować i jak testować. Złe testy są o wiele gorsze niż ich brak. W necie kiedyś natknąłem się na ciekawy artykuł, który nazywał złe testowanie okradaniem swojego pracodawcy i ciężko było się z nim nie zgodzić. Gry ciężko się testuje bo często tak samo ważne jak rezultat jakiegoś bloku kodu ważna jest wydajność, którą ciężko jest zmierzyć 'z zewnątrz' aplikacji. Sporo kodu bazuje na 'asset'-ach, których nie da się w prosty sposób zasymulować. Testy to ogromny nakład pracy i nie ma tu na myśli ich pierwszego pisania tylko ich utrzymywania. Kod z czasem ewoluuje, a z nim muszą ewoluować testy.

Nie jestem przeciwnikiem testowania, sam używam BDD w pracy od przeszło dwóch lat, ale jak po godzinach koduje małe gry to się wystrzegam takich praktyk:)

Offline siso

  • Użytkownik

  • +1
# Marzec 10, 2012, 14:06:17
Uhm, czyżbym napisał gdzieś, że testy zawsze pisze się łatwo? :) Otóż nie. Nie zawsze jest łatwo. Ale zawsze można kod doprowadzić do takiej formy, że się da.

Widzę, ze jest spora obrona :) To naturalne. Ja też się broniłem. Dopóki dwóch sprytnych gości na szkoleniu nie pokazało mi jak faktycznie należy podchodzić do testowania.

Najważniejsze jest zawsze pytanie Co testować?. Nie jest prawdą, że wszystko. Nie należy pisać testów do rzeczy trywialnych lub takich, których koszt jest niższy niż przygotowanie środowiska do testów. Inna kwestia, że akurat zawsze da się takie środowisko przygotować. Ale czasami można przyjąć, że "nie ma czasu/nie ma pieniędzy".

@Kos
Testowanie z użyciem mocków jest potrzebne wtedy, kiedy musisz zbadać flow. Jeśli wystarczy tylko zadać jakieś wejście i sprawdzić wyjście, jest ok. Idealna sprawa przy algorytmach.
Jeśli masz nawyk pisania pseudokodu, może zechciałbyś spróbować zamiast pseudokodu napisać po prostu test? Test bez implementacji jest przecież pseudokodem :) I zawsze pokaże Ci on, jak się czegoś używa. Przy okazji też uwolni Cię od myślenia o zależnościach.

Mówisz, że piszesz test po implementacji, bo znasz już zasadę działania i wiesz co testować. Dlaczego znasz zasadę działania dopiero teraz? Od czego zacząłeś implementację? W ciemno? :) Zanim zaczniesz, wiesz w największym zarysie co ma się stać. Napisz test, który to sprawdzi. Masz już test, piszesz kod, jest zielono. Napisz następny test, dla następnego wymagania. I nagle ZONK! Odkryłeś kolejne wymaganie, które nie istniało dla Ciebie przed poznaniem zasady działania. Nie ma sprawy, napisz szybko test, który je opisuje i wróć do przerwanej pracy. Kiedy skończyłeś z poprzednim, zaimplementuj to nowe, dopiero odkryte wymaganie.
Refaktoryzuj kod i testy. Jeśli okaże się, że test był zły, zmień go.

@Avaj
Nie wyjechałem z fejkową biblioteką, tylok podsunąłem ten pattern jako jedno z możliwych rozwiązań trudnych, z pozoru nierozwiązywalnych sytuacji.



Testowanie jednostkowe wymaga diametralnej zmiany podejścia do programowania. Zwłaszcza TDD, gdzie nie myśli się o kodzie, a o wymaganiach, testach do nich i dopiero kodzie, jako skutku ubocznym. No, prawie ;)

Offline gotji

  • Użytkownik

# Marzec 10, 2012, 21:06:33
Uhm, czyżbym napisał gdzieś, że testy zawsze pisze się łatwo? :) Otóż nie. Nie zawsze jest łatwo. Ale zawsze można kod doprowadzić do takiej formy, że się da.
 

Chodzi o to, że czasami nie warto stawać na głowie po to żeby kod doprowadzać do takiej formy, bo może to powodować inne problemy chociażby wydajnościowe. Nie wspominając już o wzroście kosztu, który z założenia TDD ma się zmniejszyć.

Widzę, ze jest spora obrona :) To naturalne. Ja też się broniłem. Dopóki dwóch sprytnych gości na szkoleniu nie pokazało mi jak faktycznie należy podchodzić do testowania.
 

Też się bronisz. Spróbuj dostrzec negatywne strony TDD, nic nie jest złotym środkiem.

Najważniejsze jest zawsze pytanie Co testować?. Nie jest prawdą, że wszystko. Nie należy pisać testów do rzeczy trywialnych lub takich, których koszt jest niższy niż przygotowanie środowiska do testów. Inna kwestia, że akurat zawsze da się takie środowisko przygotować. Ale czasami można przyjąć, że "nie ma czasu/nie ma pieniędzy".
 

Tak jak mówisz nie wszystko trzeba testować. Tylko wyobraź sobie jakiś większy moduł \ warstwę 'pokrytą' testami jak sito? Co takie testy dadzą? że po wprowadzeniu w przyszłości zmian będziesz miał pewność, że 30% twojego modułu nadal działa, a co z resztą? Są projekty, które ładnie da się pokryć testami prawie w 100%, ale są też takie, które będą wyglądać jak sito i z moich obserwacji(amatorskiego programisty gier) projekty gamedev-owe zaliczają się do drugiej grupy i podejście 'test first' jest nieopłacalne. Może ktoś zajmujący się profesjonalnie gamedev-em ma inne doświadczenia.

Offline rm-f

  • Użytkownik
    • Tu trolluje

# Marzec 10, 2012, 21:30:30
Zaprezentuj nam po prostu na jakimś przykładzie, kawału z gry.

Offline siso

  • Użytkownik

# Marzec 10, 2012, 22:02:30
Kilka postów wcześniej napisałem, że dręczy nie pewien mały projekcik po godzinach. Jak tylko ujrzy światło dzienne, zaprezentuję.

Offline rm-f

  • Użytkownik
    • Tu trolluje

# Marzec 10, 2012, 22:13:58
Jak tylko ujrzy światło dzienne, zaprezentuję.
Wymigujesz się. :| Jeden przykład z życia.

Offline siso

  • Użytkownik

# Marzec 10, 2012, 22:18:42
Wymigujesz się. :| Jeden przykład z życia.
Skąd Ci wezmę? Muszę napisać, tak? :) To co zrobiłem do tej pory było jeszcze zanim poznałem TDD, więc się nadaje do refactoringu raczej.