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:
public class GameTableService : IGameTableService { public GameTable GetGameTable(int id) { return _gameTableRepository.Get(id); } }
Przykład repozytorium
Przykładowe repozytorium może wyglądać następująco.
public class GameTableRepository : IGameTableRepository { private readonly List<GameTable> _gameTables = GameTableGenerator.GameTables; public GameTable Get(int id) { return _gameTables.FirstOrDefault(x => x.Id == id); } }
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.
[TestMethod] public void GetGameTable() { //Arrange var gameTableService = new GameTableService(new GameTableRepository()); var newGameTableId = GameTableGenerator.gameTables.Max(x => x.Id) + 1; //method to get new GameTable object with with a specific id var newGameTable = GetTestGameTableWithId(newGameTableId); //Act gameTableService.CreateGameTable(newGameTable); var gameTable = _gameTableService.GetGameTable(newGameTableId); //Assert Assert.AreEqual(newGameTableId, gameTable.Id); }
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:
Mock<IGameTableRepository> gameTableRepositoryMock = new Mock<IGameTableRepository>();
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.
gameTableRepositoryMock.Setup(mock => mock.Get(It.IsAny<int>()));
Jeśli chcielibyśmy dodatkowo mieć pewność, że ta metoda zwróci odpowiedni obiekt, możemy dodać metodę Returns() ze zwracanym obiektem:
var testGameTable = new GameTable(); gameTableRepositoryMock.Setup(mock => mock.Get(It.IsAny<in>())).Returns(testGameTable);
Gdy zasymulowany obiekt repozytorium jest gotowy, można go wykorzystać przy tworzeniu obiektu serwisu:
var gameTableService = new GameTableService(gameTableRepositoryMock.Object);
-
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.
//Act var gameTable = gameTableService.GetGameTable(id);
-
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().
//Assert gameTableRepositoryMock.Verify(mock => mock.Get(It.Is<int>(x => x.Equals(id))), Times.Once());
Skoro przeszliśmy po kolei przez wszystkie etapy testu, to na koniec można przeanalizować cały test:
[TestMethod] public void GetGameTable() { //Arrange int id = 1; Mock<IGameTableRepository> gameTableRepositoryMock = new Mock<IGameTableRepository>(); gameTableRepositoryMock.Setup(mock =&amp;amp;amp;gt; mock.Get(It.IsAny<int>())); var gameTableService = new GameTableService(gameTableRepositoryMock.Object); //Act var gameTable = gameTableService.GetGameTable(id); //Assert gameTableRepositoryMock.Verify(mock => mock.Get(It.Is<int>(x => x.Equals(id))), Times.Once()); }
Dokumentację do biliboteki Moq możecie znaleźć tutaj.
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