To, że testy trzeba pisać, jest oczywiste. Czasami testy nie są jednak zbyt dobrze napisane. Warto więc na nie spojrzeć i je poprawić.
Przykład serwisu
Załóżmy, że mam metodę w serwisie, która chce na podstawie id zwrócić obiekt typu GameTable. W metodzie tej nie dzieje się nic poza wywołaniem odpowiedniej metody z repozytorium:
<br> public class GameTableService : IGameTableService<br> {<br> public GameTable GetGameTable(int id)<br> {<br> return _gameTableRepository.Get(id);<br> }<br> }<br>
Przykład repozytorium
Przykładowe repozytorium może wyglądać następująco.
<br> public class GameTableRepository : IGameTableRepository<br> {<br> private readonly List<GameTable> _gameTables = GameTableGenerator.GameTables;</p> <p> public GameTable Get(int id)<br> {<br> return _gameTables.FirstOrDefault(x => x.Id == id);<br> }<br> }<br>
Oczywiście docelowo pobiera ono dane z bazy danych, ale na ten moment wystarczy statyczna lista obiektów typu GameTable.
Przykład niezbyt dobrego testu
Poniżej przedstawiam przykładowy test, który w teorii ma na celu sprawdzić poprawność metody GetGameTable(int gameTableId) z serwisu.
<br> [TestMethod]<br> public void GetGameTable()<br> {<br> //Arrange<br> var gameTableService = new GameTableService(new GameTableRepository());<br> var newGameTableId = GameTableGenerator.gameTables.Max(x => x.Id) + 1;<br> //method to get new GameTable object with with a specific id<br> var newGameTable = GetTestGameTableWithId(newGameTableId);<br> //Act<br> gameTableService.CreateGameTable(newGameTable);<br> var gameTable = _gameTableService.GetGameTable(newGameTableId);<br> //Assert<br> Assert.AreEqual(newGameTableId, gameTable.Id);<br> }<br>
Co jest nie tak w tym teście? Wiele rzeczy, m.in.:
- tworzenie nowej instancji serwisu z wykorzystaniem prawdziwego repozytorium (czyli operowanie na prawdziwych danych),
- wywołanie nie tylko sprawdzanej metody z serwisu GetGameTable(), ale również metody CreateGameTable(),
- Assert sprawdza tak na prawdę działanie repozytorium – czy zwrócony przez repozytorium obiekt ma odpowiednie id,
- pomijam tymczasowe rozwiązanie pobierania newGameTableId.
Przykład dobrego testu (z wykorzystaniem Moq)
Teraz warto się zastanowić, co na prawdę chcemy testować. Jeśli jest to serwis, to cała logika z metody z repozytorium nie jest nam potrzebna. Chcemy się tylko dowiedzieć, czy metoda z repozytorium została wywołana (i ewentualnie, czy argument był prawidłowy). Przejdźmy więc po kolei po każdej części testu:
-
Arrange – definicja danych wejściowych
Możemy stworzyć symulowany obiekt repozytorium:
<br> Mock<IGameTableRepository> gameTableRepositoryMock = new Mock<IGameTableRepository>();<br>
I wykorzystać metodę Setup() z biblioteki Moq. Dzięki niej możemy określić, którą metodę chcemy zasymulować. W naszym przypadku będzie to metoda Get(int id) z BoardGameRepository.
<br> gameTableRepositoryMock.Setup(mock => mock.Get(It.IsAny<int>()));<br>
Jeśli chcielibyśmy dodatkowo mieć pewność, że ta metoda zwróci odpowiedni obiekt, możemy dodać metodę Returns() ze zwracanym obiektem:
<br> var testGameTable = new GameTable();<br> gameTableRepositoryMock.Setup(mock => mock.Get(It.IsAny<in>())).Returns(testGameTable);<br>
Gdy zasymulowany obiekt repozytorium jest gotowy, można go wykorzystać przy tworzeniu obiektu serwisu:
<br> var gameTableService = new GameTableService(gameTableRepositoryMock.Object);<br>
-
Act – działanie, które chcemy sprawdzić
Za każdym razem gdy wywołamy metodę GetGameTable(int gameTableId) serwisu, wywoła ona symulację metody Get() repozytorium.
<br> //Act<br> var gameTable = gameTableService.GetGameTable(id);<br>
-
Assert – sprawdzenie, czy wynik jest zgodny z oczekiwanym
Ok, to jak teraz sprawdzić, czy działa? Czy podczas wywołania testu została wywołana metoda Get() repozytorium z daną wartością parametru id? Do tego przyda się metoda Verify(), w której wskazujemy sprawdzaną metodę wraz z parametrami oraz możemy określić ile razy miała się wywołać. W naszym przypadku powinna wywołać się raz, stąd parametr Times.Once().
<br> //Assert<br> gameTableRepositoryMock.Verify(mock => mock.Get(It.Is<int>(x => x.Equals(id))), Times.Once());<br>
Skoro przeszliśmy po kolei przez wszystkie etapy testu, to na koniec można przeanalizować cały test:
<br> [TestMethod]<br> public void GetGameTable()<br> {<br> //Arrange<br> int id = 1;<br> Mock<IGameTableRepository> gameTableRepositoryMock = new Mock<IGameTableRepository>();<br> gameTableRepositoryMock.Setup(mock =&amp;amp;amp;amp;gt; mock.Get(It.IsAny<int>()));<br> var gameTableService = new GameTableService(gameTableRepositoryMock.Object);<br> //Act<br> var gameTable = gameTableService.GetGameTable(id);<br> //Assert<br> gameTableRepositoryMock.Verify(mock => mock.Get(It.Is<int>(x => x.Equals(id))), Times.Once());<br> }<br>
Dokumentację do biliboteki Moq możecie znaleźć tutaj.
Podoba Ci się to, co tworzę? Chcesz dostawać informacje o:
– wydarzeniach, które organizuję lub wspieram (np. konferencje, meetupy, webinary)
– inicjatywach, które organizuję lub wspieram (np. GeekWeekWro, DevAdventCalendar)
– moich prelekcjach, kursach i szkoleniach
– wyróżnionych artykułach z mojego bloga
0% SPAMu, 100% informacji! Krótko i na temat.
Moq wg mnie nie jest dobrą biblioteką, gdyż zaciemnia granice pomiędzy mock’ami i stub’ami. Chodzi o użycie słowa Mock. Poza tym same setupy są dość długie. Póki co najlepszym frameworkiem dla mnie jest NSubstitute, w którym składnia jest zwięzła i czytelna.
PolubieniePolubione przez 1 osoba