@ ... FormationLangageCmarsQuatre ... [MODIFIER] [diff] [ LisTe* | AcTu* ]

FormationLangageCmarsQuatre

Formation langage C Toulouse mars 2004

Voir aussi LangageCppMinUnit pour un exemple en C++ avec le même environnement de tests.


Quelques mots sur le Pilotage par les Tests Unitaires

Le pilotage par les tests suppose des séquences très courtes :

  1. écriture de quelques tests, ce qui impose la conception correspondante du module
  2. compilation des tests (pas "link": la fonction testée n'existe pas toujours à ce niveau)
  3. écriture du code lui-même, uniquement pour faire passer les tests
  4. passage des tests: mise ou point et validation.

Ce cycle devrait prendre moins d'une heure et est repris N fois dans la journée. Au fur et à mesure, les tests deviennent des tests de non-regression et très rapidement le développeur dispose d'une batterie conséquente de tests automatisés. L'autre avantage est le feedback rapide, l'un des principes majeurs de l'Extreme Programming.

Voir aussi: PilotageParLesTestsEtConception et PilotageParLesTestsEtMiseAuPoint

-tc-


Cette formation de mars 2004 a plusieurs objectifs :


25.3.2004 Nous attaquons dans le vif du sujet : création de premiers modules et mise en oeuvre des macros mu_assert et mu_test_run pour les tests unitaires...

Un TP proposé est le calcul de l'impot, sujet d'actualité... Les tranches par rapport au revenu sont les suivantes :

Le système de test proposé repose sur 3 niveaux, un peu comme xUnit mais en beaucoup plus simple:

  1. le cas de test, sous forme de fonction testX()
  2. la suite de test, sous forme de fonction all_tests() qui enchaine les différents cas de test
  3. le test runner qui est simplement le "main" du programme de test, qui en quelques lignes lance la/les suite(s) de test concernées et teste leur résultat.

La macro mu_assert(message, test) reçoit 2 paramètres:

Voici les sources à l'issue de cette journée (le plus simple si vous lisez un peu le C est de consulter directement ces sources pour comprendre le fonctionnement des tests unitaires) :

Note: SourceTestImpot définit plusieurs tests. C'est le résultat à la fin de la journée. Nous avons commencé par écrire uniquement les 2 premiers tests (impot à 0), puis la fonction qui permettait de faire passer ces 2 tests. Ensuite nous avons ajouté les tests 1 à 1 soit tranche par tranche.

Une modification consiste à afficher un "." à chaque "assert" pour visualiser l'avancement des tests:

define mu_assert(message, test) do {printf("."); fflush(stdout); if (!(test)) return message; } while (0)

Les sources dans une version plus évoluée (voir aussi plus bas):

tc


2ème jour

Nous travaillons sur les allocations mémoire et les tableaux. L'appli doit gérer des réductions d'impot. Voici le nouveau test unitaire:

static char * testImpotReduction() {

	nouvelleReduction(500);
	nouvelleReduction(500);
	mu_assert("nombre de reductions incorrect", nombreReduction() == 2);
	mu_assert("total des reductions incorrect", totalReduction() == 1000);
	return 0;

}

Il s'agit de verifier la bonne gestion des reductions avant de voir ce que donne le nouveau calcul qui doit en tenir compte. La conception de cette nouvelle spéc fait apparaitre une fonction: nouvelleReduction(). Afin de tester cette fonction, nous en créons 2 autres : nombreReduction() et totalReduction(). Ces 2 fonctions ne seront peut-être pas utilisées dans l'appli elle-même mais l'objectif est la testabilité.

Le code associé dans impot.c est ensuite ajouté:

static int reduction[100]; static int nreduction = 0;

// le wiki reconnait les crochets, ils sont remplacés par des chevrons.

void nouvelleReduction(int montant) {

	reduction <nreduction> = montant;
	nreduction ++;

}

int nombreReduction(void) {

	return 	nreduction;

}

int totalReduction(void) {

	int i = 0, cumul = 0;
		for (i = 0; i < nreduction; i++)
		cumul += reduction <i>;
	return cumul;

}


Ajout du calcul de l'impot avec reduction.

Le nouveau test est:

static char * testImpotReduction() {

	nouvelleReduction(500);
	nouvelleReduction(500);
	mu_assert("nombre de reductions incorrect", nombreReduction() == 2);
	mu_assert("total des reductions incorrect", totalReduction() == 1000);

// Test de l'impot: 25000 avec 500 + 500 de réduction: 1000 de la 1ere tranche,

// la 2eme tranche est de 5000 -500 -500 soit 4000 à 20% --> 800,

// total impot: 1000 + 800

	mu_assert("Impot avec reduction incorrect", calculImpot(25000) == 1800);
	return 0;

}

La suite de tests est alors:

static char * all_tests() {

	mu_run_test(testImpot0);
	mu_run_test(testImpot10000);
	mu_run_test(testImpot15000);
	mu_run_test(testImpot25000);
	mu_run_test(testImpot60000);
	mu_run_test(testImpot100000);
	mu_run_test(testImpotBoucle);
	mu_run_test(testImpotTrancheSup);
	mu_run_test(testImpotReduction);
	return 0;

}

Et le nouveau calcul dans impot.c devient:

int calculImpot(int revenu) {

	int impot = 0;
	int revenu_apres_reduction = 0;
	revenu_apres_reduction = revenu - totalReduction();
	if (revenu_apres_reduction <= 10000)
		impot = 0;
	if ((revenu_apres_reduction >10000) && (revenu_apres_reduction) <= 20000)
		impot = (revenu_apres_reduction-10000) * 0.1;
	if ((revenu_apres_reduction >20000) && (revenu_apres_reduction <= 60000))
		impot = 1000 + (revenu_apres_reduction-20000) * 0.2;
	if (revenu_apres_reduction >60000)
		impot = 1000 + 8000  +  ((revenu_apres_reduction-60000) * 30 /100 ) ;

//printf("revenu: %d\t %10d\n", revenu, impot);

	return impot;

}


Les Pointeurs

Il est temps de s'interesser aux pointeurs. Une première implémentation consiste à créer une fonction qui renvoie les infos "reduction" sous forme de paramètre de retour. Dans un premier temps, le test est :

	{	// test de la fonction avec pointeurs
		int n = 0, reduc=0;
		infosReduction(&n, &reduc);
		mu_assert("Fonction avec pointeurs : incorrect", (n == 2) && (reduc == 1000));
	}

Ce test est inséré dans la fonction: testImpotReduction().

La fonction infosReduction() est ensuite développée:

void infosReduction(int *nombre, int *reductions) {

	ETOILE nombre = 	nreduction;
	ETOILE reductions = totalReduction();

}


Le switch

Nouveaux cas de tests sur des types de calculs de réduction (là encore, les trois tests ne sont pas écrits simultanément, le premier est écrit, le code correspondant est ensuite développé etc).

static char * testCalculReduction() {

	mu_assert("calcul reductions type 1 incorrect", calculReduction(1, 1000) == 100);
	mu_assert("calcul reductions type 2 incorrect", calculReduction(2, 1000) == 200);
	mu_assert("calcul reductions type 3 incorrect", calculReduction(3, 1000) == 2000);
	return 0;

}

Et le code de la fonction est:

int calculReduction(int type_reduction, int montant) {

	int reduction = 0;
	switch(type_reduction)
	{
		case 1:
			reduction = montant / 10;
			break;
		case 2:
			reduction = montant / 5; // 20%
			break;
		case 3:
			reduction = montant * 2;
			break;
		default:
			fprintf(stderr,
				"int calculReduction(int type_reduction, int montant) Erreur type");
	}
	return reduction;

}

L'étape suivante consiste à traiter les cas d'erreur, toujours sous forme de test d'abord: par ex invoquer la fonction avec type reduc = 4 etc.


29.3.2004 Les structures.

Création d'un type structuré pour gérer les réductions, ce qui impose une mise à jour des prototypes de fonctions du module.

Une fonction Init() est ensuite créée pour pouvoir enchainer des tests sans effet de bord et être sûr de démarrer d'un état connu.

const int MAX = 5;

static char * testImpotReductionDifferentesTranches() {

	int i = 0;
	struct s_reduction tab<MAX> = {
					{ 1, 5000},
					{ 2, 2500},
					{ 2, 2500},
					{ 2, 2500},
					{ 3, 250}
				};
	Init();
	for (i = 0; i<MAX; i++)
		nouvelleReduction(&tab<i>);
	{
		int n = 0, total=0;
		infosReduction(&n, &total);
		mu_assert("Infos reductions incorrect", (n == MAX) &&  (total == 2500) );
	}
	mu_assert("Impot avec reduction T0 incorrect", calculImpot(500) == 0);
	mu_assert("Impot avec reduction T1 incorrect", calculImpot(18000) == 550);
	// 50000 avec 2500 reduc --> 47500 soit 1000 de T1 + 20% de 27500 soit
	mu_assert("Impot avec reduction T2 incorrect", calculImpot(50000) == 6500);
	return 0;

}


Exercice de style : liste chainée

ImpotListeChainee

---

4eme Jour

Chaînes de caractères

ImpotChainesDeCaractere


Dernier jour

Base de Données et tests unitaires

Nous utilisons pour terminer une base de données. Ce qui se traduit par deux aspects:

ImpotAvecBaseDeDonnees

@