BoardGamesNook · C# · Programowanie

Testy – biblioteka Moq

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;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.

Jedna myśl na temat “Testy – biblioteka Moq

  1. 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.

    Polubione przez 1 osoba

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Google+

Komentujesz korzystając z konta Google+. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Connecting to %s