2009-04-30 13 views
10

Błąkałem się, czy możliwe jest wyśmiewanie obiektu Game w celu przetestowania mojego składnika DrawableGameComponent?XNA kpić z obiektu Game lub oddzielając swoją grę

Wiem, że szydercze frameworki potrzebują interfejsu do działania, ale muszę wyśmiać rzeczywisty obiekt Game.

edytuj: Oto link do odpowiedniej dyskusji na forach społeczności XNA. Każda pomoc?

+0

Powodzenia. W SlimDX analizujemy przejście hurtowe na interfejsy do obsługi tego przypadku użycia. – Promit

+0

@Promit, Interfejsy nie są srebrną kulą ... w niektórych przypadkach klasa abstrakcyjna to właściwy wybór. Chociaż interfejsy są w pewnych sytuacjach użyteczne i poprawne, nie zawsze jest to właściwe narzędzie :-) –

+0

@Joel Martinez - jeśli chodzi o kpiny/fałszowanie, kiedy interfejsy nie są odpowiednim narzędziem? –

Odpowiedz

14

Jest kilka dobrych postów na tym forum na temat testów jednostkowych. Oto moje osobiste podejście do testów jednostkowych w XNA:

  • Zignoruj ​​Draw() metoda
  • Isolate skomplikowane zachowanie w swoich własnych metod klasy
  • przetestować trudne rzeczy, nie pocą się odpoczynek

Oto przykład testu, który potwierdza, że ​​moja metoda Update przenosi Entry na właściwą odległość między wywołaniami Update(). (Używam NUnit.) Wyciąłem kilka linii z różnymi wektorami ruchu, ale wpadłeś na pomysł: nie powinieneś potrzebować Game, aby prowadzić testy.

[TestFixture] 
public class EntityTest { 
    [Test] 
    public void testMovement() { 
     float speed = 1.0f; // units per second 
     float updateDuration = 1.0f; // seconds 
     Vector2 moveVector = new Vector2(0f, 1f); 
     Vector2 originalPosition = new Vector2(8f, 12f); 

     Entity entity = new Entity("testGuy"); 
     entity.NextStep = moveVector; 
     entity.Position = originalPosition; 
     entity.Speed = speed; 

     /*** Look ma, no Game! ***/ 
     entity.Update(updateDuration); 

     Vector2 moveVectorDirection = moveVector; 
     moveVectorDirection.Normalize(); 
     Vector2 expected = originalPosition + 
      (speed * updateDuration * moveVectorDirection); 

     float epsilon = 0.0001f; // using == on floats: bad idea 
     Assert.Less(Math.Abs(expected.X - entity.Position.X), epsilon); 
     Assert.Less(Math.Abs(expected.Y - entity.Position.Y), epsilon); 
    } 
} 

Edycja: Niektóre inne notatki z uwagami:

My Entity Class: Wybrałem zawinąć wszystko moja gra obiektów w centralnej klasy podmiotu, który wygląda mniej więcej tak:

public class Entity { 
    public Vector2 Position { get; set; } 
    public Drawable Drawable { get; set; } 

    public void Update(double seconds) { 
     // Entity Update logic... 
     if (Drawable != null) { 
      Drawable.Update(seconds); 
     } 
    } 

    public void LoadContent(/* I forget the args */) { 
     // Entity LoadContent logic... 
     if (Drawable != null) { 
      Drawable.LoadContent(seconds); 
     } 
    } 
} 

Daje mi to dużą elastyczność w tworzeniu podklas Entity (AIEntity, NonInteractiveEntity ...), które prawdopodobnie zastępują Update(). Pozwala mi to również na podklasę Drawable swobodnie, bez piekielnych podklas n^2, takich jak AnimatedSpriteAIEntity,i AnimatedSpriteNoninteractiveEntity. Zamiast tego, można to zrobić:

Entity torch = new NonInteractiveEntity(); 
torch.Drawable = new AnimatedSpriteDrawable("Animations\litTorch"); 
SomeGameScreen.AddEntity(torch); 

// let's say you can load an enemy AI script like this 
Entity enemy = new AIEntity("AIScritps\hostile"); 
enemy.Drawable = new AnimatedSpriteDrawable("Animations\ogre"); 
SomeGameScreen.AddEntity(enemy); 

Moja rozciągliwej klasa: Mam klasy abstrakcyjnej, z których wszystkie moje narysowane obiekty są pochodną. Wybrałem klasę abstrakcyjną, ponieważ niektóre zachowania będą udostępniane. Byłoby zupełnie do przyjęcia, aby zdefiniować to jako interface, jeśli nie jest to prawdą w twoim kodzie.

public abstract class Drawable { 
    // my game is 2d, so I use a Point to draw... 
    public Point Coordinates { get; set; } 
    // But I usually store my game state in a Vector2, 
    // so I need a convenient way to convert. If this 
    // were an interface, I'd have to write this code everywhere 
    public void SetPosition(Vector2 value) { 
     Coordinates = new Point((int)value.X, (int)value.Y); 
    } 

    // This is overridden by subclasses like AnimatedSprite and ParticleEffect 
    public abstract void Draw(SpriteBatch spriteBatch, Rectangle visibleArea); 
} 

Podklasy definiują własną logikę rysowania. W przykładzie zbiornika, można zrobić kilka rzeczy:

  • Dodaj nowy podmiot dla każdego pocisku
  • Dodać do klasy TankEntity który określa listy, a zastępuje Draw() iteracyjne nad Bullets (które określają metoda wyciągania własnych)
  • Zrób ListDrawable

Oto przykład implementacja ListDrawable, ignorując kwestię jak zarządzać samą listę.

public class ListDrawable : Drawable { 
    private List<Drawable> Children; 
    // ... 
    public override void Draw(SpriteBatch spriteBatch, Rectangle visibleArea) { 
     if (Children == null) { 
      return; 
     } 

     foreach (Drawable child in children) { 
      child.Draw(spriteBatch, visibleArea); 
     } 
    } 
} 
+0

Czy możesz pokazać mi swój interfejs "Entity"? Co to jest? Składnik? Niestandardowa klasa? – drozzy

+0

Jest to klasa niestandardowa - każdy obiekt gry renderowany używa go. Uniknąłem GameComponents na wszystko oprócz mojego edytora wejściowego i http://creators.xna.com/en-US/samples/gamestatemanagement ScreenManager. Definiuje metody takie jak Update (podwójny czas), który przesuwa prędkość * czasu w kierunku NextStep. – ojrac

+0

Aby obsłużyć wiele rodzajów rysunków (ikonki, animowane ikonki, cząstki), mój element * zawiera * Drawable - klasę abstrakcyjną, która obsługuje wywołania funkcji Draw(). – ojrac

2

Możesz użyć narzędzia o nazwie TypeMock, które, jak sądzę, nie wymaga interfejsu. Inną, najczęściej stosowaną metodą jest stworzenie nowej klasy, która dziedziczy po Game, a także implementuje utworzony interfejs, który pasuje do obiektu Game. Możesz następnie kodować przeciwko temu interfejsowi i przekazywać w swoim "niestandardowym" obiekcie gry.

public class MyGameObject : Game, IGame 
{ 
    //you can leave this empty since you are inheriting from Game.  
} 

public IGame 
{ 
    public GameComponentCollection Components { get; set; } 
    public ContentManager Content { get; set; } 
    //etc... 
} 

Jest to nieco żmudne, ale pozwala uzyskać pełną sprawność.

+0

Czy istnieje sposób, w jaki mogę dziedziczyć z jednej rzeczy zamiast dwóch? Co mi też daje dziedziczenie z IGame? Wygląda na to, że nie odziedziczenie po nim niczego nie zmienia. Może nie jestem do końca jasny, przepraszam. – drozzy

+0

to nie działa, ponieważ konstruktor do drawablegamecomponent potrzebuje instancji Game, a nie IGame ;-) –

3

Struktury takie jak MOQ i Rhino Mocks nie wymagają interfejsu. Mogą też kpić z każdej niezamkniętej i/lub abstrakcyjnej klasy. Gra jest klasą abstrakcyjną, więc nie powinieneś mieć problemu z kpieniem z niej :-)

Należy zwrócić uwagę, że przynajmniej z tymi dwiema ramami, aby określić jakiekolwiek oczekiwania co do metod lub właściwości, muszą one być wirtualne lub abstrakcyjny. Powodem tego jest to, że wyśmiewana instancja, którą generuje, musi być w stanie nadpisać. Wspomniany przez IAmCodeMonkey typemock, jak sądzę, ma sposób obejścia tego, ale nie sądzę, że typemock jest wolny, podczas gdy dwa wspomniane są.

Tak na marginesie, można również sprawdzić projekt kopalni, które mogą pomóc w tworzeniu testów jednostkowych do gier XNA bez konieczności dokonać mocks: http://scurvytest.codeplex.com/

+0

Dzięki za odpowiedź. Przepraszam, ale nie mam zbyt dużo czasu na debugowanie innej struktury. Wygląda jednak interesująco. – drozzy

+0

Kusi mnie, aby dać temu szansę. Obawia się tylko innego czynnika niepewności (nowego w XNA, nowego w TDD). Czy ktoś ma jakieś XP? –

+0

@boris callens: Użyłem dużo Rhino Mocks. Pracuje bardzo dobrze. – Skurmedel

3

Nie trzeba go wyśmiewać. Dlaczego nie zrobić fałszywej gry?

Odziedzicz z gry i nadpisaj metody, których chcesz używać w testach, aby zwrócić wartości w puszkach lub obliczenia skrótów dla dowolnych metod/właściwości, których potrzebujesz. Następnie przekaż fałszywy do swoich testów.

Przed kpiącymi ramami ludzie rzucali własne makiety/kody/podróbki - może to nie jest tak szybkie i łatwe, ale wciąż można.

+0

Podoba mi się. W jakiś sposób wir tych ram zaparował mi drogę. Spróbuję i wrócę tu z wynikami! – drozzy

+0

Brzmi świetnie. Powodzenia! Jestem podekscytowany wiedząc, jak to się skończyło, ponieważ mam gotowy projekt z 1/4 mojego własnego siedzenia z tyłu palnika. –

+0

Po rozpatrzeniu tego więcej, niestety nie mogę wymyślić łatwy sposób robienia tego. Chyba że sfałszuję całą masę metod. Nie jestem pewien, jakie skutki będą wywoływać. W każdym razie dzięki. – drozzy

0

Na początek coś takiego, chciałbym uderzyć w XNA WinForms Sample. Używając tej próbki jako modelu, wydaje się, że jednym ze sposobów wizualizacji komponentów w WinForm jest utworzenie dla niego kontrolki w tym samym stylu co SpinningTriangleControl z próbki. Pokazuje to, jak renderować kod XNA bez instancji gry. Naprawdę gra nie jest ważna, liczy się to, co ona dla ciebie robi. Tak więc, aby utworzyć projekt biblioteki, który ma logikę Load/Draw komponentu w klasie i innych projektach, utwórz klasę Control i klasę Component, które są owijkami dla kodu biblioteki w ich odpowiednich środowiskach. W ten sposób kod, który testujesz, nie jest duplikowany i nie musisz się martwić pisaniem kodu, który zawsze będzie możliwy do zrealizowania w dwóch różnych scenariuszach.

+0

Przepraszam, ale bez czytania tego przykładu, twoja odpowiedź nie ma większego sensu. Sprawdzę to później w domu. – drozzy

+0

Ideą idei jest utrzymanie logiki renderowania w oddzielnej klasie i napisanie 2 klas, które są w zasadzie powłokami dla tej logiki renderowania w ich odpowiednich środowiskach - GameComponent i WinForms Control. Więc tak naprawdę nie piszesz komponentu, który podrabia instancję gry, piszesz Kontrolę, która nigdy nie używa elementów i dzieli implementację pomiędzy dwiema klasami prezentacji. – Jeremy

1

Będę świnka z powrotem na swoim stanowisku, jeśli nie przeszkadza, ponieważ mine wydaje się być mniej aktywny i już umieścić swoje rep na linii;)

Jak czytam w twoich postach (zarówno tutaj & XNAForum) Myślę, że to zarówno ramy, które mogą być bardziej przystępne i mój (nasz) projekt, który nie jest bezbłędny.

Ramy można zaprojektować tak, aby były łatwiejsze do przedłużenia. Ciężko mi jest uwierzyć w główny argument Shawn z perf hit on interfaces. Aby mnie poprzeć, his colleague mówi, że perfekcyjne trafienie można łatwo uniknąć.
Należy zauważyć, że struktura ma już interfejs IUpdatowalny i łatwo edytowalny.Dlaczego nie pójść na całość?

Z drugiej strony uważam również, że rzeczywiście mój (i twój) wzór nie jest bezbłędny. Tam, gdzie nie polegam na obiekcie Game, bardzo zależę od obiektu GraphicsDevice. Spojrzę na to, jak mogę to obejść. Sprawi, że kod będzie bardziej skomplikowany, ale myślę, że rzeczywiście mogę zerwać te zależności.

+0

True dat. Na przykład jednym z wyzwań, jakie mam, jest przekazanie partii sprite z konstruktora lub utworzenie z klasy. Przekazywanie go przez konstruktor powoduje, że kod jest bardziej oddzielony od siebie, ale muszę wtedy przyjąć założenia dotyczące wywoływania funkcji sprite_batch.begin() i end(). – drozzy

+0

Jeps, miał to samo. Przekazuję go teraz konstruktorowi, ponieważ nie chcę, aby moja klasa wiedziała, jak to wygląda. Myślę, że dzięki temu są przenośne do innych gier. –

+0

Także begin() - i end() - w każdej klasie, którą mogę sobie wyobrazić, może spowodować zauważalne spowolnienie. –