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

Offline rm-f

  • Użytkownik
    • Tu trolluje

# Marzec 10, 2012, 22:38:09
Skąd Ci wezmę? Muszę napisać, tak? :)
Tak.

Offline Mr. Spam

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

Offline lmmilewski

  • Użytkownik
    • Łukasz Milewski - devblog

# Marzec 11, 2012, 04:44:42
Cytuj
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... :)

Pewnie masz rację, że pseudokod to za mało. Jaki przykład/projekt wg Ciebie byłby wystarczający, żeby pokazać jak pisać testy, żeby to nie było bolesne (dla tych, którzy są przekonani, że chcą to robić, ale nie wiedzą jak)?

Dla jasności - ja nie będę nikogo przekonywał. Szczególnie, że sam nie jestem "entuzjastą". Czasami wykorzystuję TDD i jak narazie w tych przypadkach się sprawdzało (czyt. zaoszczędziłem czas).

Offline Xender

  • Użytkownik

# Marzec 11, 2012, 08:52:05
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.
Może więc przekaż nam tą wiedzę, żebyśmy mogli się przekonać?

Offline Kos

  • Użytkownik
    • kos.gd

# Marzec 11, 2012, 11:10:51
@up Siso nie przekazał, bo potrzebuje jeszcze drugiego sprytnego gościa do kompletu. :)

Wiesz, Extreme Teaching, zawsze w parach.

@lmmilewski:
Cytuj
Jaki przykład/projekt wg Ciebie byłby wystarczający, żeby pokazać jak pisać testy, żeby to nie było bolesne (dla tych, którzy są przekonani, że chcą to robić, ale nie wiedzą jak)?

Uch, jakikolwiek, który sam napiszę i mi się spodoba :).

Offline siso

  • Użytkownik

# Marzec 11, 2012, 15:15:03
Może więc przekaż nam tą wiedzę, żebyśmy mogli się przekonać?
Mam wrażenie, że robię to o d swojego pierwszego postu o TDD ;)

@up Siso nie przekazał, bo potrzebuje jeszcze drugiego sprytnego gościa do kompletu. :)

Wiesz, Extreme Teaching, zawsze w parach.
LOL, to dobre :)
Swoją drogą w parach jest łatwiej tededować.

Wytededuję jakiś kawałek prostego gameplaya (niestety, w pojedynkę) jak tylko ogarnę się z bieżącą robótką.
Peace! ;)

Offline dikamilo

  • Użytkownik
    • blog

# Marzec 11, 2012, 20:20:41
Cytuj
jak wyglądają unit testy dla GUI?
Testy wyglądają tak samo :) Chodzi o to żeby testować zachowanie obiektów. Weźmy na przykład przycisk w UI, co on ma robić ? Jeżeli użytkownik kliknie w obszarze gdzie znajduje się ten przycisk to ma się wywołać callback na funkcję obsługi tego przycisku - więc napiszmy test.

Pseudo kod, składnia typowa dla google test i google mock:
TEST(ButtonTest, callListenerOnClick) {
  ClickListenerMock listener;
  Button button;
  button.register(&listener);

  // oczekuje że metoda onClick zostanie wywołana raz
  EXPECT_CALL(listener, onClick()).Times(1);

  button.onMouseClick(10, 20);
}
Robię tutaj mock na listenera aby było prościej - równie dobrze mogę dziedziczyć po klasie listenera i w metodzie onClick ustawić jakieś pole bool na true a potem sprawdzać ASSERT_TRUE(true, listener.clicked); - mockiem jest dużo prościej w tym wypadku.

Jak zaimplementować kod aby test przeszedł ? Dodać listenera jako pole prywatne, zaimplementować metodę register, zaimplementować onMouseClick NAJPROŚCIEJ JAK SIĘ DA - czyli wywołujesz listener.onClick za każdym razem. Potem piszesz drugi test w którym oczekujesz że onClick się nie wywoła jeżeli kliknięcie jest po za przyciskiem. I tak dalej i tak dalej... Ogólnie chodzi o to aby robić małe kroczki.

Inny przykłady ? Ustawiam na przycisku teksturę i oczekuję że wyświetli się z teksturą (odpowednie funkcje renderera zostaną wywołane w metodzie render itp.).

Nie jestem żadnym specjalistą, sam próbuje używać TDD w swoich projektach - ostatnio piszę mały framework do gier 2D tą techniką, za dużo jeszcze nie ma więc nie podam jakiś większych przykładów itp.

Prosty przykład klasy zajmującej się obliczaniem delty (metody tej klasy wywoływane są w głównej pętli):

Klasa sama w sobie: http://pastebin.com/vqmQd1JP
Testy jednostkowe: http://pastebin.com/nPsEvrHq
Mock: http://pastebin.com/8Vv6sxbX

W C++ problem jest taki że sporo bibliotek, API np. openGL to są funkcje globalne których testowanie jest trudne a czasami nie możliwe. I teraz aby testować aplikację która używa np. opengl musimy zapakować wszystkie te funkcje do jednej klasy - interfejsu i używać dependency injection w naszych klasach. Chyba że napiszemy renderer którego nie będziemy testować a w testach będziemy używać mock.

TDD vs gamedev ?
http://gamesfromwithin.com/category/test-driven-development
szczególnie 3 cześć "Stepping Through the Looking Glass: Test-Driven Game Development"
« Ostatnia zmiana: Marzec 11, 2012, 20:40:20 wysłana przez dikamilo »

Offline Xender

  • Użytkownik

# Marzec 11, 2012, 23:27:50
Klasa do liczenia dt? No bez jaj, ile masz tych pętli głównych że musiałeś wyodrębnić to do osobnej klasy? Rozumiem wrapper na timer ale to jest... dziwne...

Poza tym to zły przykład. Na tak prostym kodzie widać od razu czy działa dobrze czy nie - zalicza się do tych przypadków niewymagających testów. Można prosić o jakiś sensowny przykład?

Offline gotji

  • Użytkownik

# Marzec 12, 2012, 10:03:52
@dikamilo To co pokazałeś jest prawdziwe i wszystko ładnie pięknie tylko że kod który pokazujesz jest 'powszechny', który równie dobrze może pochodzić ze zwykłej aplikacji okienkowej np. WinFormsy. Problemy się pojawiają kiedy dotykasz trochę niższego poziomu jak assety i praca na nich, małe optymalizacje itp.. Pokrycie takich rzeczy testami jest możliwe, ale zmusza to programistę do trzymania kodu jako 'obiektową kobyłę', która jest bardzo kosztowna w utrzymaniu. Z doświadczenia wiem, że główny koszt testu to nie jego napisanie lecz jego utrzymanie.

Offline siso

  • Użytkownik

# Marzec 12, 2012, 17:15:12
@gotji
Pokaż, jeśli możesz, taki problematyczny kawałek pracujący blisko assetów. Być może uda nam się coś wspólnie rozkminić.

Offline gotji

  • Użytkownik

# Marzec 12, 2012, 19:52:43
Swego czasu zaczełem nowy projekt w XNA i założenie było takie, że lecę całość podejściem DDD + BDD. Pomyślałem, że koro w pracy się tak super sprawdza to czemu tu ma być inaczej. Frustracja pojawiła się przy implementacji ContentProcessor-a, Importer-a, Writer-a i Reader-a. Pierwsze podejścia to masa wrapper-ów, dzięki którym zamiast czystego kodu miałem 'obiektową papkę'. Koniec końców wywaliłem wszystko bo po prostu tego nie dało się sensownie przetestować.

Po jakimś czasie trafiła do mnie informacja, że używanie event-ów jest mocno niewskazane z uwagi na to że generują masę śmieci i GC na Xboxie sobie z tym nie radzi(radzi ale spadek wydajności jest przerażający). Są na to obejścia ale dosyć uciążliwe. W tym momencie zostałem pozbawiony wspaniałego narzędzia do decoupling-u. Efekt: rzeźbienie kodu. Miałem masę kodu po to tylko żebym mógł używać BDD. Każda zmiana w kodzie skutkowała poprawianiem testów. Doszedłem do wniosku, że nie ma sensu testować na siłę części aplikacji skro 'środowisko', w którym pracuje, tak mocno się przed tym broni. No to wtedy zaczęło się testowani tylko niektórych elementów. Skutek taki, że miałem kod pokryty testami na oko w 30-50%.

Offline siso

  • Użytkownik

# Marzec 13, 2012, 11:31:32
Fakt, kiedy testy się rozjadą, trudno nad nimi ponownie zapanować. Z samego opisu wynika, że model obiektowy może być nie najlepszy do tego, co chciałeś osiągnąć. Albo niechcący za bardzo go rozdmuchałeś. Dodatkowo rozwijałeś go z dala od testów. Skutek - bałagan w testach i idący za tym ich ciężar.

Mały hint: nie musiało tak być. Unit testy można także pisać do nieobiektowego kodu ;)

Ponowię prośbę o pokazanie jakiegoś fragmentu, o ile oczywiście możesz.

Offline gotji

  • Użytkownik

# Marzec 13, 2012, 12:02:47
Ponowię prośbę o pokazanie jakiegoś fragmentu, o ile oczywiście możesz.

Sprawdź w necie dowolną pełną implementację rozszerzeń do XNA Content Pipline(importer, processor, writer, reader) zobaczysz jak to wygląda, nie będą wklejał paruset linii kodu.

Dodatkowo rozwijałeś go z dala od testów.

Skąd ten wniosek? Było wprost przeciwnie. DDD + BDD to bardzo dobre uzupełniające się połączenie.

Mały hint: nie musiało tak być. Unit testy można także pisać do nieobiektowego kodu ;)

Bez modelu obiektowego ciężko jest uzyskać 'decoupling' na sensownym poziomie, żeby możliwe było TDD(i pochodne wersje). TDD wymaga tego aby projekt miał odpowiednią formę inaczej użycie jest praktycznie niemożliwe bądź jest męką. Nie piszemy tu o samych UT tylko o podejściu TDD.

Celem mojej wypowiedzi jest podzielenie się doświadczeniem na temat TDD + amatorski gamedev. W żadnym wypadku nie mówię: "niech nikt tak nie robi!". Z twoich wypowiedzi wynika, że nie pisałeś takiego projektu, a mimo wszystko próbujesz mi wmówić, że robiłem błędy projektowe.

Offline siso

  • Użytkownik

# Marzec 13, 2012, 13:12:11
Nie, nie próbuję Ci wmówić, że robiłeś błędy projektowe. Nie mogę, bo nie widziałem tego kodu. Ale co do jednego powinniśmy mieć tu jasność: TDD to, jak sama nazwa wskazuje, development sterowany testami. Jeśli możesz napisać do czegoś unit test, to znaczy, że możesz tam praktykować TDD. OOP nie jest jedynym słusznym narzędziem. Ale nie jest też prawdą, że bez OOP nie da się uzyskać SoC, SRP, sensownego podziału na warstwy, jeśli trzeba, czy nawet i polimorfizmu.

Nie trzeba jednak obiektowo pisać, żeby testować jednostkowo. Wiem, bo robiłem tak w C/C++. Tylko trzeba dążyć do jak najmniejszej (zdroworozsądkowo) liczby zależności. Czyli wszechobecne globale zdecydowanie nie pasują - ot, taki przykład :) Kod musi się dać dzielić na w miarę niezależne jednostki. Jeśli tak jest, można tededować :)

Inna sprawa, że ten Content Pipeline wygląda biednie pod względem testfriendlności, ale to już rozmowa na jakieś dłuższe piwo chyba ;)

Offline gotji

  • Użytkownik

# Marzec 13, 2012, 13:33:39
Jeśli możesz napisać do czegoś unit test, to znaczy, że możesz tam praktykować TDD.

Właśnie tutaj tkwi chyba różnica naszych poglądów. Dla mnie TDD(i pochodne) to pokrycie konkretnej integralnej części kodu praktycznie w 100% testami. Postrzegam to jako sposób na projektowanie i jednoczesne implementowanie pomniejszych elementów modułu \ warstwy. Jeżeli część modułu \ warstwy nie może zostać przetestowana(nie można dla niej napisać testów) to podejściem TDD ten moduł nie może powstać. Dlatego stosuje się różnego rodzaju framework-i oraz wzorce aby 100% testowalność otrzymać i praktycznie każdy projekt pisany podejściem TDD ma narzut z tym związany.

Nie mówię żeby w ogóle nie pisać UT bo  UT powinno się pisać, ale UT z podejściem 'test first' to nie jest równoznaczne z TDD.

Offline siso

  • Użytkownik

  • +1
# Marzec 13, 2012, 15:12:14
Jeśli jest różnica poglądów, warto ją wyprostować :)

TDD ma bardzo dobrze zdefiniowany cykl pracy: test -> implementacja -> refactoring. Odstępstwa od tego powodują, że już nie pracujesz w TDD. Kluczem jest tu driven, bo to testy mają sterować kształtem kodu, a nie kod kształtem testów.

To pierwsza sprawa. Druga to pokrycie kodu. Nieprawdą jest, że jest wymagane 100%. Nie musisz pokrywać całego kodu, wystarczy że pokryjesz całe API jednostki, czyli  jej publiczny interfejs. Nikt nie wymaga, byś pisał testy dla niepublicznych części. Zresztą, jeśli coś nie jest publiczne, nie bardzo jest jak do tego się dobrać. Lub nie powinno być łatwo, przynajmniej. I nie należy pisać testów do jakichś trywializmów, jak np. akcesory (pytanie wręcz  czy akcesory są zawsze potrzebne?).
Za to stuprocentowo powinny być pokryte wymagania. Może się zdarzyć tak, że jedna metoda (implementacja jakiegoś algorytmu) ma kilka warunków brzegowych. Wtedy możesz mieć do niej i 20 testów, a do pozostałych metod w klasie/module/czymkolwiek po jednej. I będzie dobrze.