FluentValidation – cz. 3 testy i nullowalna properta

Kończąc zbiór wpisów (część 1, część 2) dotyczących FluentValidation czas na napisanie testów.

Przykładowa klasa walidatora

Dla przypomnienia, tak wygląda klasa BoardGameValidator:

public class BoardGameValidator : AbstractValidator<BoardGameViewModel>
{
	public BoardGameValidator()
	{
		RuleFor(boardGame => boardGame.MaxPlayers)
			.GreaterThan(0).WithMessage("Maksymalna liczba graczy musi być dodatnia!");
		RuleFor(boardGame => boardGame)
			.Must(boardGame => boardGame.MaxPlayers > boardGame.MinPlayers)
			.WithMessage("Maksymalna liczba graczy musi być większa minimalnej liczby graczy!");
		RuleFor(boardGame => boardGame.MaxTime)
		.GreaterThan(0).WithMessage("Maksymalny czas gry musi być dodatni!");
}
}

Jako dobrzy programiści, już na wstępie, zaraz przed zdefiniowaniem takiego walidatora, powinniśmy utworzyć testy sprawdzające jego poprawność. Ważne, żeby sprawdzić, czy dla każdego sprawdzanego warunku walidator zwraca prawidłowy status (informację o błędzie lub jego braku).

Testy  z parametrami

Żeby nie pisać kilka razy takich samych testów z różnymi danymi testowymi, skorzystam z parametrów. Instrukcja jak zrobić testy z parametrem z wykorzystaniem wbudowanej biblioteki MSTest jest dostępna pod linkiem. Trzeba również pamiętać o usunięciu referencji do pakietu Microsoft.VisualStudio.QualityTools.UnitTestFramework.

Testowanie prostych zasad

Na początek sprawdźmy, czy properta MaxPlayers jest sprawdzana prawidłowo. Testy powinny wyglądać tak:

[TestClass]
public class BoardGameValidationTest
{
	private readonly BoardGameValidator _boardGameValidator;
	public BoardGameValidationTest()
	{
		_boardGameValidator = new BoardGameValidator();
	}
	[DataTestMethod]
	[DataRow(0)]
	public void MaxPlayersThrowsError(int maxPlayers)
	{
		_boardGameValidator.ShouldHaveValidationErrorFor(boardGame => boardGame.MaxPlayers, maxPlayers);
	}
	[DataTestMethod]
	[DataRow(1)]
	public void MaxPlayersPasses(int maxPlayers)
	{
		_boardGameValidator.ShouldNotHaveValidationErrorFor(boardGame => boardGame.MaxPlayers, maxPlayers);
	}
}

Jak widać, mamy 2 metody testowe: pierwszą, która sprawdza, czy walidacja zwraca błąd, gdy dane są nieprawidłowe, oraz drugą, która sprawdza, czy walidacja działa poprawnie przy poprawnych danych. Co ważne, w każdym teście sprawdzamy tylko i wyłącznie daną propertę, a nie cały obiekt!

Sprawdzanie walidacji propercji MaxTime będzie wyglądało całkiem podobnie.

Testowanie złożonych zasad

Co jednak ze sprawdzaniem warunku MaxPlayers > MinPlayers? Musimy wtedy podać testowe wartości dla obydwu propert. Najlepiej do tego się nada zdefiniowanie obiektu z konkretnymi wartościami MaxPlayers i MinPlayers. Przykładowe testy wyglądałyby następująco:

[DataTestMethod]
[DataRow(1, 0)]
public void MaxPlayersWithMinPlayersThrowsError(int minPlayers, int maxPlayers)
{
	var boardGame = new BoardGameViewModel
	{
		MinPlayers = minPlayers,
		MaxPlayers = maxPlayers
	};
	_boardGameValidator.ShouldHaveValidationErrorFor(x => x.MaxPlayers, boardGame);
}
[DataTestMethod]
[DataRow(1, 1)]
[DataRow(1, 2)]
public void MaxPlayersWithMinPlayersPasses(int minPlayers, int maxPlayers)
{
	var boardGame = new BoardGameViewModel
	{
		MinPlayers = minPlayers,
		MaxPlayers = maxPlayers
	};
	_boardGameValidator.ShouldNotHaveValidationErrorFor(x => x.MaxPlayers, boardGame);
}

Dzięki napisaniu tych testów właśnie odkryłam buga z moim kodzie. Mam warunek taki, że minimalna liczba graczy musi być mniejsza od maksymalnej liczby graczy. Jednak może się okazać, że będzie istnieć gra, która będzie przeznaczona tylko dla danej liczby graczy. Mój walidator w tej chwili nie pozwoli na utworzenie gry z takimi parametrami. Muszę więc zrobić poprawkę walidatora i zaktualizować testowaną zasadę:

RuleFor(boardGame => boardGame)
	.Must(boardGame => boardGame.MaxPlayers >= boardGame.MinPlayers)
	.WithMessage("Maksymalna liczba graczy musi być większa lub równa minimalnej liczby graczy!");

Jak widać, pisanie testów się przydaje. Zaoszczędza mnóstwo czasu (przeznaczonego na ręczne testowanie wszystkich przypadków testowych) i przy okazji pozwala na sprawdzenie, czy testy się nie wysypują po kolejnych zmianach kodu.

Nullowalna properta

W jednym z kolejnych testów miałam problem z nullowalną propertą (w tym przypadku int? Place). Walidacja niestety nie zwracała mi błędu przy niepoprawnych danych. Okazało się, że musiałam inaczej zdefiniować zasadę walidatora:

  • nie działa:
public class GameResultValidator : AbstractValidator<GameResultViewModel>
{
	public GameResultValidator()
	{
		RuleFor(gameResult => gameResult)
			.Must(gameResult => gameResult.PlayersNumber >= gameResult.Place)
			.WithMessage("Maksymalna liczba graczy musi być większa lub równa zajętemu miejscu!");
	}
}
  • działa:
public class GameResultValidator : AbstractValidator<GameResultViewModel>
{
	public GameResultValidator()
	{
		RuleFor(gameResult => gameResult.PlayersNumber)
			.GreaterThanOrEqualTo(gameResult => gameResult.Place.Value)
			.When(gameResult => gameResult.Place.HasValue)
			.WithMessage("Maksymalna liczba graczy musi być większa lub równa zajętemu miejscu!");
	}
}

5 uwag do wpisu “FluentValidation – cz. 3 testy i nullowalna properta

Dodaj komentarz