
\chapter{Tests unitaires et d'intégration}

\begin{quote}
    Tests are part of the system.
    You can think of tests as the outermost circle in the architecture.
    Nothing within in the system depends on the tests, and the tests always depend inward on the components of the system.

    -- Robert C. Martin, Clean Architecture
\end{quote}

Your tests are your first and best line of defense against software defects.
Your tests are more important than linting \& static analysis (which can only find a subclass of errors, not problems with your actual program logic).
Tests are as important as the implementation itself (all that matters is that the code meets the requirement -- how it's implemented doesn't matter at all unless it's implemented poorly).

Unit tests combine many features that make them your secret weapon to application success:

\begin{enumerate}
    \item
        Design aid: Writing tests first gives you a clearer perspective on the ideal API design.
    \item
        Feature documentation (for developers): Test descriptions enshrine in code every implemented feature requirement.
    \item
        Test your developer understanding: Does the developer understand the problem enough to articulate in code all critical component requirements?
    \item
        Quality Assurance: Manual QA is error prone.
        In my experience, it's impossible for a developer to remember all features that need testing after making a change to refactor, add new features, or remove features.
    \item
        Continuous Delivery Aid: Automated QA affords the opportunity to automatically prevent broken builds from being deployed to production.
\end{enumerate}

Unit tests don't need to be twisted or manipulated to serve all of those broad-ranging goals.
Rather, it is in the essential nature of a unit test to satisfy all of those needs.
These benefits are all side-effects of a well-written test suite with good coverage.

\begin{enumerate}
	\item
	    What component aspect are you testing?
	\item
	    What should the feature do? What specific behavior requirement are you testing?
\end{enumerate}

Traduit grossièrement depuis un article sur \url{https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d\#.kfyvxyb21\%3E\%60_}:

% TODO : Finir le verbatim ci-dessous : "ils sont.... ??????"
\begin{verbatim}
    Vos tests sont la première et la meilleure ligne de défense contre les défauts de programmation. Ils sont
\end{verbatim}

\begin{verbatim}
    Les tests unitaires combinent de nombreuses fonctionnalités, qui en fait une arme secrète au service d'un développement réussi:
\end{verbatim}

\begin{enumerate}
    \item
        Aide au design: écrire des tests avant d'écrire le code vous donnera une meilleure perspective sur le design à appliquer aux API.
    \item
        Documentation (pour les développeurs): chaque description d'un test
    \item
        Tester votre compréhension en tant que développeur:
    \item
        Assurance qualité: des tests, 5.
\end{enumerate}


\section{Complexité cyclomatique\index{McCabe}}

La \href{https://fr.wikipedia.org/wiki/Nombre_cyclomatique}{complexité cyclomatique} (ou complexité de McCabe) peut s'apparenter à mesure de difficulté de compréhension du code, en fonction du nombre d'embranchements trouvés dans une même section.
Quand le cycle d'exécution du code rencontre une condition, cette condition peut être évalue à VRAI ou à FAUX.
L'exécution du code dispose donc de deux embranchements, correspondant chacun à un résultat de cette condition.

Le code suivant \autoref{cyclomatic-simple-code} a une complexité cyclomatique 1; il s'agit du cas le plus simple que nous pouvons implémenter : l'exécution du code rentre dans la fonction (il y a un seul embranchement), et aucun bloc conditionnel n'est présent sur son chemin.
La complexité reste de 1.

\begin{listing}[!hbpt]
	\begin{minted}{Python}
	from datetime import date

	def print_current_date():
		print(date.today())
	\end{minted}
	\caption{Une version ultra-simple de l'affichage du jour de la semaine}
	\label{cyclomatic-simple-code}
\end{listing}

Si nous complexifions cette fonction en vérifiant (par exemple) le jour de la semaine, nous aurons notre embranchement initial (l'impression à l'écran de la date du jour), mais également un second embranchement qui vérifiera si cette date correspond à un lundi:

\begin{listing}[!h]
	\begin{minted}{Python}
	from datetime import date

	def print_current_date_if_monday():
		if date.today().weekday() == 0:
			print("Aujourd'hui, c'est lundi!")
		print(date.today())
	\end{minted}
	\caption{Ajout d'une fonctionnalité essentielle et totalement indispensable}
\end{listing}

La complexité cyclomatique d'un bloc est évaluée sur base du nombre d'embranchements possibles; par défaut, sa valeur est de 1.
Si nous rencontrons une condition, elle passera à 2, etc.
Cette complexité est liée à deux concepts:

\begin{itemize}
	\item
        \textbf{La lisibilité du code}: au plus la complexité cyclomatique sera élevée, au plus le code sera compliqué à comprendre en première instance. Il sera composé de plusieurs conditions, éventuellement imbriquées, il débordera probablement de la hauteur que votre écran sera capable d'afficher
	\item
        \textbf{Les tests unitaires}: pour nous assurer d'une couverture de code correcte, il sera nécessaire de couvrir tous les embranchements présentés. Connaître la complexité permet de savoir combien de tests devront être écrits pour assurer une couverture complète de tous les cas pouvant se présenter.
\end{itemize}


\section{Lisibilité du code}

Il est important de noter que refactoriser un bloc, par exemple en extrayant une méthode, n'améliore pas la complexité cyclomatique globale de l'application.
L'amélioration que nous visons ici est une amélioration \textbf{locale}, qui facilite la lecture d'un bloc spécifique, et pas d'un programme complet.

"Améliorons" notre code ci-dessous, pour lui ajouter la possibilité de gérer les autres jours de la semaine:

\begin{listing}[H]
	\begin{minted}{Python}
	from datetime import date

	def print_current_date():
		if date.today().weekday() == 0:
			print("Lundi")
		elif date.today().weekday() == 1:
			print("Mardi")
		elif date.today().weekday() == 2:
			print("Mercredi")
		elif date.today().weekday() == 3:
			print("Jeudi")
		elif date.today().weekday() == 4:
			print("Vendredi")
		elif date.today().weekday() == 5:
			print("Samedi")
		elif date.today().weekday() == 6:
			print("Dimanche")
		print(date.today())
	\end{minted}
	\caption{Un code un peu nul avec une complexité cyclomatique qui l'est tout autant}
	\label{Impression du jour de la semaine, version naïve}
\end{listing}

La complexité de ce code est évaluée à 8, même si la complexité effective ne sera que de 7.
Extraire une méthode à partir de ce bloc pourra réduire la complexité de la fonction \mintinline{python}{print_current_date} n'améliorera rien et ne fera que déplacer le problème.
Une solution serait de passer par un dictionnaire, de façon à ramener la complexité à 1:

\begin{listing}[H]
	\begin{minted}{python}
	from datetime import date

	def print_current_date():
		DAYS_OF_WEEK = {
			0: "Lundi",
			1: "Mardi",
			2: "Mercredi",
			3: "Jeudi",
			4: "Vendredi",
			5: "Samedi",
			6: "Dimanche"
		}

		print(DAYS_OF_WEEK.get(date.today().weekday()))
		print(date.today())
	\end{minted}
	\caption{La même version, avec une complexité réduite à 1}
\end{listing}

\section{Types de tests}

De manière générale, si nous nous rendons compte que les tests sont trop compliqués à écrire ou nous coûtent trop de temps, c’est sans doute que l’architecture de la solution n’est pas adaptée et que les composants sont couplés les uns aux autres.
Dans ces cas, il sera nécessaire de refactoriser le code, afin que chaque module puisse être testé
indépendamment des autres. \cite{clean_code}

Le plus important est de toujours corréler les phases de tests indépendantes du reste du travail (de développement, ici), en l’automatisant au plus près de sa source de création:

\begin{quote}
    Martin Fowler observes that, in general, "a ten minute build [and test process] is perfectly within reason\ldots
    [We first] do the compilation and run tests that are more localized unit tests with the database completely stubbed out. Such tests can run very fast, keeping within the ten minutes guideline.
    However any bugs that involve larger scale intercations, particularly those involving the real database, won’t be found.
    The second stage build runs a different suite of tests [acceptance tests] that do hit the real database and involve more end-to-end behavior.
    This suite may take a couple of hours to run.

    -- Robert C. Martin, Clean Architecture
\end{quote}

De manière plus générale, si nous nous rendons compte que les tests sont trop compliqués à écrire ou coûtent trop de temps à être mis en place, c'est sans doute que l'architecture de la solution n'est pas adaptée et que les composants sont couplés les uns aux autres.
Dans ces cas, il sera nécessaire de refactoriser le code, afin que chaque module puisse être testé indépendamment des autres.
Le plus important est de toujours corréler les phases de tests au reste du travail (de développement, ici), en les automatisant au plus près de leur source de création.

\subsection{Tests unitaires}

\begin{quote}
	The aim of a unit test is to show that a single part of the application
	does what programmer intends it to.
\end{quote}

Les tests unitaires ciblent typiquement une seule fonction, classe ou méthode, de manière isolée, en fournissant au développeur l’assurance que son code réalise ce qu’il en attend.
Pour plusieurs raisons (et notamment en raison de performances), les tests unitaires utilisent souvent des données stubbées - pour éviter d’appeler le "vrai" service

Le nombre de tests unitaires nécessaires à la couverture d'un bloc fonctionnel est au minimum égal à la complexité cyclomatique de ce bloc.
Une possibilité pour améliorer la maintenance du code est de faire baisser ce nombre, et de le conserver sous un certain seuil.
Certains recommandent de le garder sous une complexité de 10; d'autres de 5.

Idéalement, chaque fonction ou méthode doit être testée afin de bien en valider le fonctionnement, indépendamment du reste des composants.
Cela permet d'isoler chaque bloc de manière unitaire, et permet de ne pas rencontrer de régression lors  de l'ajout d'une nouvelle fonctionnalité ou de la modification d'une existante.
Il existe plusieurs types de tests (intégration, comportement, \ldots)

Avoir des tests, c'est bien.
S'assurer que tout est testé, c'est mieux.
C'est ici qu'il est utile d'avoir le pourcentage de code couvert par les différents tests, pour savoir ce qui peut être amélioré, le but du jeu consistant simplement à augmenter ou égaler le pourcentage de couverture de code existant avant chaque modification.
Gitlab permet de visualiser cette information de manière très propre, en l'affichant au niveau de chaque proposition d'intégration.
La couverture de code est une analyse qui donne un pourcentage lié à la quantité de code couvert par les tests. Attention qu'il ne s'agit pas de vérifier que le code est \textbf{bien} testé, mais juste de vérifier \textbf{quelle partie} du code est testée.
Le paquet \texttt{coverage} se charge d'évaluer le pourcentage de code couvert par les tests.

\subsection{Tests d'acceptance}

\begin{quote}
	The objective of acceptance tests is to prove that our application does
	what the customer meant it to.
\end{quote}

Les tests d’acceptance vérifient que l’application fonctionne comme convenu, mais à un plus haut niveau (fonctionnement correct d’une API, validation d’une chaîne d’actions effectuées par un humain, \ldots).

\subsection{Tests d'intégration}

Les tests d’intégration vérifient que l’application coopère correctement avec les systèmes
périphériques

