Faire des tests élégants et efficaces avec NFluent

Je voulais écrire un article sur NFluent car c’est un Framework dont je suis particulièrement fan (car TDD friendly) et que j’utilise depuis plus de 4 ans car il permet d’écrire des tests plus « clean ». Généralement un test (unitaire, intégration, BDD,…) est composé de 3 parties: Arrange, Act, Assert (la règle des 3A). NFluent permet d’écrire les assertions des tests d’une manière plus « Fluent » et donc plus lisible.

Prenons cet exemple écrit sans utiliser NFluent (en utilisant des Assert):

Assert.Equal(2, userList.Count);

Avec NFluent on peut écrire la même instruction de cette manière :

Check.That(userList).HasSize(2);

Comme vous le voyez dans cet exemple l’utilisation de NFluent (ChecK.That(something).ExpectedBehavior()) est plus lisible que l’écriture précédente. En plus l’écriture NFluent est plus safe. En effet, dans le premier exemple, si l’objet userList est nul (et qu’on n’a pas fait de contrôle sur la non nullité en amont) alors le test sera Failed avec un NullReferenceExecption (NRE), ce qui n’est pas top pour un rapport d’erreur. Les NRE doivent tout le temps être évités que ce soit pour du code de test ou du code de PROD (car une NRE signifie un code mal écrit/géré). En revanche avec l’exemple de NFluent, si le userList est nul on aura un message explicite dans le rapport de test.

Par ailleurs, les messages d’erreur rémontés par NFluent en cas de test qui a échoué, sont toujours plus lisibles. Par exemple supposons que la liste userList contient un seul élément au lieu de 2, alors avec NFluent on aura le message d’erreur suivant :

Nous allons voir dans cet article plusieurs fonctionnalités de NFluent notamment la gestion des collections, la gestion des Exceptions, les opérateurs NOT et AND,…

Généralités sur les objets

Note : dans tous les exemples ci dessous, on fera un focus sur les assertions et non sur le code des méthodes testées.

Les égalités entre objets

Il arrive assez souvent lorsqu’on fait un test de comparer deux objets: le résultat de la méthode testée par rapport à l’objet attendu. Supposons par exemple qu’on veuille tester une méthode User GetUserFromId(int userId)

La manière classique de comparer les deux Users (celui généré par la méthode et celui attendu) est de faire un Override de la méthode Equals() et donc d’appeler la méthode Equals lors des assertions. Si on n’a pas la possibilité d’overrider la méthode Equals, NFluent dispose de pas mal de fonctionnalités (méthodes) pour comparer les objets. Regardons l’exemple ci dessous.

[Fact]
public void Should_Return_Expected_User()
{
	var expectedUser = new User
	{
		Id = 123,
		FirstName = "Pape",
		LastName = "DIENG"
	};
	var userResult = GetUserFromId(123);
	Check.That(userResult).HasFieldsWithSameValues(expectedUser);
}

Ici la méthode NFluent HasFieldsWithSameValues() permet justement de comparer les propriétés des deux objets, cela permet d’éviter de comparer les propriétés une par une.

Les types des objets

[Fact]
public void Should_Return_Correct_Instance()
{
	var adminUser = _userFactory.Create("Admin");
	Check.That(adminUser).IsInstanceOf<AdminUser>();
}

L’utilisation de la méthode IsInstanceOf() permet de faire des Check sur les types d’objet, ce qui est pratique pour les Factories par exemple.

Il y a aussi ces méthodes disponibles pour faire des Check plus lisibles/clean: IsInstanceOfType(), IsNotAnInstanceOfThese(), InheritsFrom(), IsAnInstanceOfOneOf(),…

Les Collections

L’une des notions les plus importantes en programmation C# (ou Java) ce sont les collections. NFluent dispose d’un ensemble de fonctionnalités permettant de manipuler des collections d’une manière plus efficace. Voici quelques exemples non exhaustifs.

[Fact]
public void Should_Contain_2_Elements_Of_Type_User()
{
	var userList = new List<User>
	{
		new User
		{
			Id = 123,
			FirstName = "Pape",
			LastName = "DIENG"
		},
		new User
		{
			Id = 456,
			FirstName = "DummyFirstName",
			LastName = "DummyLastName"
		}
	};
	Check.That(userList).Not.IsNullOrEmpty();
	Check.That(userList).HasSize(2);
	Check.That(userList).ContainsOnlyInstanceOfType(typeof(User));
}

Cet exemple très simple permet de tester que la liste est non vide et contient deux éléments de type User.

[Fact]
public void Should_Contains_Only_Users_With_LastName_DIENG()
{
	var userList = new List<User>
	{
		new User
		{
			Id = 123,
			FirstName = "Pape",
			LastName = "DIENG"
		},
		new User
		{
			Id = 456,
			FirstName = "DummyFirstName",
			LastName = "DIENG"
		}
	};
	Check.That(userList).Not.IsNullOrEmpty();
	Check.That(userList).ContainsNoNull();
	Check.That(userList).ContainsOnlyElementsThatMatch(
		x => x.LastName.Equals("DIENG"));
	 
}

Cet exemple permet d’utilser un lambda grâce à la méthode ContainsOnlyElementsThatMatch() en plus de pouvoir tester que la liste ne contient aucun élément nul ContainsNoNull().

Comme indiqué en introduction, un des avantages de NFluent est qu’en cas d’erreur sur un test, le message d’erreur est assez explicite. Par exemple, lorsqu’on reprend le test ci dessus en remplaçant le deuxième user par null, le test va donc échouer et l’erreur sera la suivante :

La librairie NFluent dispose de plusieurs autres méthodes sur les collections.

Les Exceptions

La gestion des exceptions est également très importante lorsqu’on fait de la programmation. NFluent dispose d’une librairie permettant de mieux manipuler les exceptions dans nos tests.

Supposons qu’on veut tester une méthode qui permet de calculer l’inverse d’un nombre entier (GetInverse()) et que cette méthode doit renvoyer une exception (avec un message « Input should not be zero ») lorsqu’on met zéro comme input. Alors on pourrait écrire notre test de cette manière

[Fact]
public void Should_Throw_Exception_With_Message()
{
	Check.ThatCode(() => _computationService.GetInverse(0))
		.Throws<ArgumentException>().WithMessage("Input should not be zero");
}

En utilisant WithMessage() et Throws() de NFluent on peut ainsi facilement écrire le test. On peut aller plus loin sur le Check des exceptions. Reprenons le même exemple mais supposons que la méthode GetInverse(0) au lieu de retourner un ArgumentException, retourne une exception personnalisée ComputationServiceException qui contient une propriété Origin (un enum ) on pourrait écrire le test de cette manière.

[Fact]
public void Should_Throw_Exception_With_Origin_Input_Invalid()
{
	Check.ThatCode(() => _computationService.GetInverse(0))
		.Throws<ComputationServiceException>()
		.WithProperty(x => x.Origin, Origin.InputInvalid);
}

Enfin si on veut exprimer le fait que la méthode ne doit pas retourner d’exception, on peut l’exprimer de cette manière

[Fact]
public void Should_Not_Throw_Exception_When_Input_Is_Valid()
{
	Check.ThatCode(() => _computationService.GetInverse(2))
		.DoesNotThrow();
}

Les opérateurs NOT et AND

Il arrive assez souvent de vouloir faire plusieurs plusieurs « Check » en même temps ou de vouloir faire la négation d’un « Check ». NFulent permet de le faire en utilisant les opérateurs NOT et AND. Regardons les exemples ci dessous (les mêmes exemples vus plus haut avec l’ajout de « NOT » et « AND« ).

[Fact]
public void Should_Throw_Exception_With_Message_And_Property()
{
	Check.ThatCode(() => _computationService.GetInverse(0))
		.Throws<ComputationServiceException>()
		.WithMessage("Input should not be zero")
		.And
		.WithProperty(x => x.Origin, Origin.InputInvalid);
}
[Fact]
public void Should_Contains_Only_Users_With_LastName_DIENG()
{
	var userList = new List<User>
	{
		new User
		{
			Id = 123,
			FirstName = "Pape",
			LastName = "DIENG"
		},
		new User
		{
			Id = 456,
			FirstName = "DummyFirstName",
			LastName = "DIENG"
		}
	};
	Check.That(userList)
		.Not.IsNullOrEmpty()
		.And
		.ContainsOnlyElementsThatMatch(
			x => x.LastName.Equals("DIENG"))
		.And
		.HasElementThatMatches(x => x.FirstName.Equals("Pape"))
		.And
		.HasElementThatMatches(x => x.FirstName.Equals("DummyFirstName"))
		.And
		.HasSize(2);
}

Pour aller plus loin

NFluent dispose aussi d’une librairie pour tester les Threads. Par exemple pour tester la durée d’éxécution d’une méthode on peut l’écrire comme ça

Check.ThatCode(() => _computationService.GetInverse(2))
	.LastsLessThan(60, TimeUnit.Milliseconds);

Ou encore pour tester les Task/Async, on peut utiliser

Check.ThatAsyncCode(() =>...)

Conclusion

NFluent est un framework TDD Friendly qui contient beaucoup de méthodes qui permettent de tester facilement les composants .Net (les collections, les exceptions, les Thread,…), il permet d’avoir des tests plus élégants, plus lisibles, plus clean et aussi il permet de générer des erreurs assez explicites lorsqu’un test échoue. Pour plus d’informations sur NFluent, je vous invite à visiter le site NFluent.

Une réflexion sur “Faire des tests élégants et efficaces avec NFluent

Répondre à Anonyme Annuler la réponse.