begin process at 2012 05 27 20:35:44
  Trouver un code source :
 
dans
 
Accueil > 

Code

 > 

Class et Objet ( POO )

 > TESTS UNITAIRES

TESTS UNITAIRES


 Information sur la source

Note :
9 / 10 - par 1 personne
9,00 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10
Catégorie :Class et Objet ( POO ) Classé sous :tests unitaires, framework, assertion, classe Niveau :Débutant Date de création :06/10/2011 Date de mise à jour :12/10/2011 10:51:13 Vu / téléchargé :2 476 / 101

Auteur : pierreSabatier

Ecrire un message privé
Commentaire sur cette source (14)
Ajouter un commentaire et/ou une note

 Description

Bonjour à tous,

Je présente aujourd'hui une version plus aboutie de ce code en ligne depuis peu. Il se compose de trois classes une interfaces et un index.php

Pourquoi ne pas utiliser PHPUnit ou SimpleTest ? Par goût de la simplicité.

La classe TestManager connait les deux autres classes.
Les classes de tests (que vous devez écrire !) héritant de TestUnitaire sont chargées via la méthode addClasse et lancées ensuite dans la méthode launch.
L'affichage comprend aujourd'hui une interface et une classe HtmlOutput.

Le tout étant soumis à vos appréciations. J'ai développé des choses en vitesse, pour savoir si je ne faisais pas fausse route.

A vous de jouer !

PS : La source ne présente que la classe TestManager. Ca vous donnera l'idée générale.

Source

  • <?php
  • final class TestManager {
  • const METHOD_PREFIX = 'test_';
  • const METHOD_INDENT = "\t";
  • private $tests = array();
  • /**
  • * Pile de logs
  • */
  • private $logs = array();
  • /**
  • * public function __construct()
  • */
  • public function __construct(){
  • assert_options(ASSERT_ACTIVE,1);
  • assert_options(ASSERT_WARNING,0);
  • assert_options(ASSERT_BAIL,0);
  • assert_options(ASSERT_QUIET_EVAL,1);
  • assert_options(ASSERT_CALLBACK,array($this,'hasFail'));
  • }
  • public function hasFail($file,$line,$code){
  • // Au dernier log (le [0] du tableau), j'ajoute le contexte de l'échec.
  • $this->logs[0]['fail'] = TRUE; //array('file' => $file, 'line' => $line, 'code' => $code);
  • }
  • public function log($m,$args){
  • $log = array(
  • 'method' => $m,
  • 'args' => $args,
  • 'fail' => FALSE,
  • );
  • // travail anté-historique (du plus récent au plus ancien)
  • // comme ça, les derniers tests (ceux qui nous préoccupent) occupent le haut du tableau
  • array_unshift($this->logs,$log);
  • }
  • /**
  • * vide les logs un par un en les affichant
  • */
  • public function drainLogs(TestOutput $obj){
  • while ($log = array_shift($this->logs)) {
  • $obj->show($log['method'],$log['args'],$log['fail']);
  • }
  • }
  • function addClasse($name){
  • $this->tests[] = $name;
  • }
  • function launch($out_class){
  • // instancier la sortie
  • $out = new $out_class();
  • // travail anté-historique
  • while ($current = array_shift($this->tests)) {
  • if (is_subclass_of($current,'TestUnitaire')) {
  • $obj = new $current($this);
  • $l = strlen(self::METHOD_PREFIX);
  • // annoncer la classe
  • $out->classBegin($current);
  • foreach (get_class_methods($obj) as $m) { if (substr($m, 0, $l) == self::METHOD_PREFIX) {
  • // annoncer la methode
  • $out->methodBegin($m);
  • // ... je les appelle dans un environnement clean
  • $obj->setUp();
  • $obj->$m();
  • $obj->tearDown();
  • // Pour ne pas surcharger la mémoire, à chaque fin de méthode, je dépile la pile des logs.
  • $this->drainLogs($out);
  • $out->methodEnd($m);
  • }}
  • $out->classEnd($current);
  • }
  • }
  • }
  • function generateClassTest($name, $output = 'php://output'){
  • $hdle = fopen($output, 'w');
  • fwrite($hdle, '<?php'. PHP_EOL . 'class ' . $name . 'Test extends TestUnitaire {' . PHP_EOL);
  • // self::write_method('setUp',$hdle);
  • self::write_method('tearDown',$hdle);
  • foreach(get_class_methods($name) as $m) {
  • self::write_method($m,$hdle);
  • }
  • fwrite($hdle, '}' . PHP_EOL);
  • fclose($hdle);
  • }
  • function write_method($method_name, $handle){
  • fwrite($handle, self::METHOD_INDENT . 'function ' . $method_name . '(){'. PHP_EOL . self::METHOD_INDENT . '}' . PHP_EOL);
  • }
  • }
<?php
final class TestManager {
	const METHOD_PREFIX = 'test_';
	const METHOD_INDENT = "\t";
	private $tests = array();
	/**
	 * Pile de logs
	 */
	private $logs = array();
	/**
	 * public function __construct()
	 */
	public function __construct(){
		assert_options(ASSERT_ACTIVE,1);
		assert_options(ASSERT_WARNING,0);
		assert_options(ASSERT_BAIL,0);
		assert_options(ASSERT_QUIET_EVAL,1);
		assert_options(ASSERT_CALLBACK,array($this,'hasFail'));
	}
	public function hasFail($file,$line,$code){
		// Au dernier log (le [0] du tableau), j'ajoute le contexte de l'échec.
		$this->logs[0]['fail'] = TRUE; //array('file' => $file, 'line' => $line, 'code' => $code);
	}
	public function log($m,$args){
		$log = array(
			'method'	=> $m,
			'args'		=> $args,
			'fail'		=> FALSE,
		);
		// travail anté-historique (du plus récent au plus ancien)
		// comme ça, les derniers tests (ceux qui nous préoccupent) occupent le haut du tableau
		array_unshift($this->logs,$log);
	}
	/**
	 * vide les logs un par un en les affichant
	 */
	public function drainLogs(TestOutput $obj){
		while ($log = array_shift($this->logs)) {
			$obj->show($log['method'],$log['args'],$log['fail']);
		}
	}
	function addClasse($name){
		$this->tests[] = $name;
	}
	function launch($out_class){
		// instancier la sortie
		$out = new $out_class();
		// travail anté-historique
		while ($current = array_shift($this->tests)) {
			if (is_subclass_of($current,'TestUnitaire')) {
				$obj = new $current($this);
				$l = strlen(self::METHOD_PREFIX);
				// annoncer la classe
				$out->classBegin($current);
				foreach (get_class_methods($obj) as $m) { if (substr($m, 0, $l) == self::METHOD_PREFIX) {
					// annoncer la methode
					$out->methodBegin($m);
					// ... je les appelle dans un environnement clean
					$obj->setUp();
					$obj->$m();
					$obj->tearDown();
					// Pour ne pas surcharger la mémoire, à chaque fin de méthode, je dépile la pile des logs.
					$this->drainLogs($out);
					$out->methodEnd($m);
				}}
				$out->classEnd($current);
			}
		}
	}
	function generateClassTest($name, $output = 'php://output'){
		$hdle = fopen($output, 'w');
		fwrite($hdle, '<?php'. PHP_EOL . 'class ' . $name . 'Test extends TestUnitaire {' . PHP_EOL);
		// self::write_method('setUp',$hdle);
		self::write_method('tearDown',$hdle);
		foreach(get_class_methods($name) as $m) {
			self::write_method($m,$hdle);
		}
		fwrite($hdle, '}' . PHP_EOL);
		fclose($hdle);
	}
	function write_method($method_name, $handle){
		fwrite($handle, self::METHOD_INDENT . 'function ' . $method_name . '(){'. PHP_EOL . self::METHOD_INDENT . '}' . PHP_EOL);
	}
}

 Conclusion

J'attends vos regards externes pour faire avancer ce code.

 Fichier Zip

Les Membres Club peuvent télécharger directement un fichier contenu dans le zip sans télécharger le zip en entier !

Télécharger le zip


 Historique

10 octobre 2011 16:05:02 :
Les incohérences les plus notables ont été corrigés (les propriété $oInstance et $_config) Il n'y a toujours pas de singleton, le "if" dans le constructeur de la classe mère reste suffisant.
11 octobre 2011 09:27:43 :
Ajout de la méthode finale launch() de la classe mère TestUnitaire. Le zip n'est pas mis à jour (faute de temps.)
12 octobre 2011 10:51:13 :
Remaniement complet en réaction aux commentaires de Akénathon.

 Sources du même auteur

[CRON] INSERT ON DUPLICATE KEY UPDATE
COMPTEUR DE VISITES SUR FICHIER

 Sources de la même categorie

Source avec Zip GÉNÉRATION AUTOMATIQUE DE FICHIER .CLASS.PHP EN FONCTION D'U... par ig3
CLASSE D'OBJET DE CRYPTAGE ET DÉCRYPTAGE DE CHAINES DE CARAC... par 8Tnerolf8
Source avec Zip MY.DEVIANTART API par inwebo
CLASSE DE GESTION DE "VARIABLES GLOBALES D'ENVIRONNEMENT" par pifou25
Source avec Zip COLLECTION.CLASS.MIN.PHP par thunderhunter

 Sources en rapport avec celle ci

Source avec une capture CLASSE CONSTRUCTEUR DE TABLEAU HTML EN PHP (REVU ET CORRIGÉ) par demonstorm
CLASSE MYSQL UTILISANT LES FONCTIONS PDO par Vince66
Source avec Zip Source avec une capture GÉNÉRATION DE FORMULAIRES HTML PERSONNALISÉS. par beejeridou
Source avec Zip Source avec une capture DBOC - V3.1 [AJAX][PHP5] par Morphinof
CLASSE FRANCOPHONE D'EXPLOITATION MYSQL par Gwinyam

Commentaires et avis

Commentaire de pierreSabatier le 07/10/2011 18:53:32

Bonsoir tout le monde,

La source a été vue plus de 180 fois. Quelqu'un voudrait-il formuler un petit commentaire ?

Commentaire de aKheNathOn le 10/10/2011 14:50:47

Bonjour Pierre,

Un source très intéressant, les asserts en PHP sont assez peu utilisés donc c'est très instructif d'en voire un exemple.

Le code est très simple et j'aime ton approche KISS et justifiant le fait de ne pas passer par un framework de type PHPUnit.

Je commence par un point "obscur" sur le :

$this->_return[$this->iNbError][$config] = $$config; la liste des clés étant limitée par la signature de la fonction je ne vois pas trop l'intérêt de cette boucle.

Un simple code aurait pû suffire :

$this->_return[] = array(
  'line' => $line,
  'code' => $code
);

--> Pour faire un peu d'optimisations :) <--

Dans la classe Debug :

- Les variables iNbError, et _config n'ont pas besoin d'exister
A la place de iNbError, tu peux utiliser : sizeof($this->_return)

- De la manière dont tu as écrit ta classe Debug n'est pas un singleton, donc soit supprimer oInstance soit en faire un vrai : cf partie PHP 5 : http://fr.wikipedia.org/wiki/Singleton_%28patron_de_conception%29

Dans la classe TestUnitaire :

- J'aurais plutôt mis l'instanciation des asserts dans les DEBUG

- Pour permettre d'instancier les asserts à partir des DEBUG j'aurais éventuellement loggué dans Debug toutes les assertions, ainsi j'aurais également eu le log de tout ce qui est OK.

Sur le principe, il me manque un élément, c'est le contexte d'implémentation des tests unitaires : je présume qu'actuellement on doit le fait en bloc dans le construct. Au passage dans la classe TestUnitaire ton __construct devrait être final public pour éviter les effets de bords de l'héritage.

* Y'à t'il moyen d'avoir un cloisonnement des contextes avec plusieurs fonctions ...
* Si dans une exécution on veut exécuter plusieurs classes de tests à la fois ...

Si tu règle ces petits points je pense que ta classe à vraiment un bon potentiel car c'est pas vraiment intéressant de déployer, configurer et customiser PHPUnit, alors que ton code serait beaucoup plus facilement adaptable.

Bonne continuation,
akh

Commentaire de pierreSabatier le 10/10/2011 16:16:29

Akhenaton, j'ai supprimé le côté brouillon de mon source.

En suivant tes indications, je développe un nouveau code. Je pense à 4 classes (ces deux-là, une de logs complet et une d'affichage).

Dans le construct de la classe mère "testUnitaire", je pourrais écrire quelque chose comme :
// Pour chacune de mes méthodes ...
foreach (get_class_methods($this) as $m) {
  // ... qu'il faut appeler par CONVENTION
  if (substr($m, 0, 5) == 'test_') {
    // ... je les appelle dans un environnement clean
    $this->setUp();
    $this->$m();
    $this->tearDown();
  }
}

Est-ce que je vois juste ?

Commentaire de aKheNathOn le 10/10/2011 17:33:30

C'est très bien, pour les setup et teardown - ça permet de se créer un contexte de tests ISO et mieux cloisonnés - important pour éviter les effets de bords de certains tests - petit bémol quand tu sort du scope du $this le cloisonnement n'est plus géré ($_REQUEST, $_SESSION, $GLOBALS ... appels vers des variables statiques) - mais ce type d'effet de bord est très minime.

Parcontre je ne mettrait pas les appels (setUp, test_..., tearDown) dans l'instanciation de la classe de test mais je ferais une classe TestManager à part, qui elle lencerait par exemple les tests sur une classe, ou sur un ensemble de classes. L'objet devrait à mon sens être reconstruit à chaque execution de test.

La classe de log c'est bien la class Débug que tu as actuellement, elle devrait effectivement être en mode singleton et juste stocker les infos.

La classe TestManager s'occuperait de dumper les résultats en fin de test par exemple. Pour la structure de débug, faudrait rajouter un niveau de log sur la classe en cours d'execution vu que c'est multi-classes.

J'utiliserais les classes de Reflection dans le TestManager pour gérer les annotations de documentation, histoire d'associer des commentaires aux fonctions testées et de les affichers dans les résultats.

NB : Un truc utile dans le TestManager est de pouvoir générer un fichier de test unitaire en lui passant en argument un nom de classe.

Si tu peux faire le tout en moins de 1000 lignes de codes, tu tiens un framework de tests unitaires complet et surtout facile d'utilisation.

:) bon courrage

Commentaire de jpaul078 le 11/10/2011 06:48:21

Bonjour,

L'idée des tests unitaires m'intéresse énormément.
Ce code fait appel à des notions que je n'ai pas l'habitude d'utiliser en php.
De ce fait, cela m'intéresse encore plus.
Mais j'ai fait un test sans succès et avant de pouvoir le faire fonctionner, j'ai peur qu'il ne me faille investir pas mal de temps pour éclaircir les notions qui m'échappent.
J'ai peu de temps de temps en ce moment et je pense qu'un petit exemple d'utilisation m'aiderais beaucoup.

Dans tout les cas merci de partager ton travail.

Cordialement,
Jean-Paul

Commentaire de pierreSabatier le 11/10/2011 09:25:40

Salut JPAUL,

Voilà comment aujourd'hui, ce "framework" de test fonctionne. Ta classe de test hérite de TestUnitaire. Les méthodes de tests doivent commencer par "test_".

<?php class LambdaTest extends TestUnitaire {

function test_alpha(){}
function test_beta(){}
}

$obj = new LambdaTest();
$obj->launch();

Ps : j'ai rajouté la méthode launch() dans TestUnitaire. Elle contient :
foreach (get_class_methods($this) as $m) {
  // ... qu'il faut appeler par CONVENTION
  if (substr($m, 0, 5) == 'test_') {
    // ... je les appelle dans un environnement clean
    $this->setUp();
    $this->$m();
    $this->tearDown();
  }
}

Voilà la version alpha de ce "framework". Les modifs vont arriver quand je peux.

Commentaire de pierreSabatier le 12/10/2011 18:06:25

Bon, voilà un remaniement complet, qui ne sera probablement pas du goût de tous. Toutes vos remarques sont les bienvenues :-)

Commentaire de pierreSabatier le 17/10/2011 14:01:56

Toujours pas de nouveau commentaire ?

Commentaire de aKheNathOn le 17/10/2011 17:54:28 9/10

C'est du joli travail, j'adhère vraiment de plus en plus à ton mini-framework !

Quelques petites choses supplémentaires histoire de l'améliorer :

Dans la classe TestUnitaire, il te manque une fonction fail($message) qui permet de forcer le log à afficher un statut de fail sur le test en cours. C'est utilisé si on sort d'un simple assert :

if ($cond1 != $val && ($cond2 > $max || $cond2 < $min) fail("valeur aux limites);

D'ailleurs tu peux aussi lors de la génération des classes la rajouter (dans TestManager --> write_method : dans le corp de la méthode rajoutes :
$this->fail('Not implemented yet');

Dans le TestManager toujours, dans la méthode launch, fais un try catch sur l'exécution du test car les erreurs peuvent faire arrêter le test sinon, à logguer aussi dans le statut d'exécution.

Il ne manque plus grand chose à ta librairie, juste le packaging : un nom de projet, de la phpdoc sur les classes, un repository SVN et un site web (sourceforge est pas mal pour ce genre de choses). Si tu le mets sous GIT (https://github.com/) je vais peut-être en faire un fork :)

En tout cas merci de partager ton source, je vais surement l'utiliser :)

Bonne prog et tiens-nous au courant,
akh

Commentaire de pierreSabatier le 19/10/2011 11:35:42

Merci Akh, c'est grâce à toi que j'améliore ce source.

En fin de semaine, je déménage. Mais je ne vais pas oublier de packager ce framework. Il faudra un peu plus de patience, voilà tout.

Commentaire de jpaul078 le 19/10/2011 22:41:29

Bonjour ou plutôt bonsoir,

Je suis désolé mais je dois passer à côté de quelque chose : même avec tes explications, je n'arrive pas à comprendre.
Faut dire que je n'ai pas trop de temps et que je n'ai pas investi en regarder ton code...
Je me suis fait un répertoire exemple avec la classe à tester suivante (fichier=class_verifadr.php)
----------------- début code -----------------
<?php
class verifadr
{
function longueur_nom($adr)
{
return strlen(substr($adr,0,strpos($adr, '@')));
}
function longueur_dom($adr)
{
return strlen(substr($adr,strpos($adr, '@')+1));
}
}
?>
-----------------  fin  code -----------------
j'ai téléchargé le zip et j'ai copié le dossier test dans mon dossier exemple.

Maintenant, il faut que je crée mon fichier de tests (fichier=test_verifadr.php) et c'est là que je me demande ce qu'il faut faire...
J'imagine qu'il faut que je charge ma classe et la classe de tests unitaires donc je suis arrivé à un truc comme :
----------------- début code -----------------
<?php
require('test/testUnitaire.php');
require('class_verifadr.php');


class verifadrTest extends TestUnitaire
{

function test_alpha()
{
$mon_adr=new verifadr();
if ($mon_adr->longueur_nom('toto@truc.fr') == 4)
{
return true;
}
else
{
return false;
}
}
function test_beta()
{
$mon_adr=new verifadr();
if ($mon_adr->longueur_dom('toto@truc.fr') == 7)
{
return true;
}
else
{
return false;
}
}
}

$obj = new verifadrTest();
$obj->launch();
?>
-----------------  fin  code -----------------
quand je charge le fichier test_verifadr.php dans mon navigateur j'ai :
1) un warning :
Warning: Cannot modify header information
2) ça affiche :
Début des tests
3) ensuite j'ai une erreur :
Fatal error: Maximum function nesting level of '100' reached, aborting! in /home/jpaul/workspace/tests/testUnitaire/exemple/test/testUnitaire.php on line 47
4) un tableau de 99 lignes auquel je ne comprends pas grand chose voici la ligne de titre et la première et la dernière lignes :
# Time Memory Function Location
1 0.0004 337560 {main}( ) ../test_verifadr.php:0
99 0.0039 443628 TestUnitaire->__call( ) ../testUnitaire.php:0
5) une ligne qui semble être le résultat :
49 test(s) lancé(s). OK

Peux-tu me dire ce que j'ai fait de travers ?

Cordialement,
Jean-Paul OLIVIER

Commentaire de pierreSabatier le 19/10/2011 23:04:28

Oui, je peux te le dire ;-)

Vérifie ta classe mère "TestUnitaire",car si j'ai bien compris ton erreur, la méthode launch() ne doit pas figurer. Or, il faut, dans l'état actuel de ton code, qu'elle fasse quelque chose comme :

final public function launch(){
foreach (get_class_methods($this) as $m) {
  // ... qu'il faut appeler par CONVENTION
  if (substr($m, 0, 5) == 'test_') {
    // ... je les appelle dans un environnement clean
    $this->setUp();
    $this->$m();
    $this->tearDown();
  }
}
}

Dans le cas de ton erreur, __call est appelé récursivement... Je prends bonne note de ce bug et vais le corriger avec ce test dans la fonction __call
if (method_exists($this,$method)){
call_user_func_array(array($this, $method), $args);
}

Merci pour ta remontée d'erreur ! Tiens-moi au courant pour ton code.

PS : une nouvelle mouture va voir le jour. Elle sera (rétro-)compatible avec tes classes ;-)

Commentaire de jpaul078 le 20/10/2011 06:57:14

Bonjour,

Dans la première réponse que tu m'avais faite, j'avais bien vu que tu faisais allusion à cette fonction launch mais je pensais que tu l'avais intégrée à ton fichier de livraison et je l'ai donc retéléchargé à l'aide du lien 'Télécharger le zip'...
Comment fait-on pour télécharger ta dernière version ?

Donc je viens de la rajouter (par copier coller de la fonction que tu as donnée dans ta réponse d'hier soir) juste après la fonction __destruct() dans le fichier test/testUnitaires.php et ça ne change pas grand chose.
L'autre souci, c'est que si je change un des tests pour qu'il soit fau par exemple :
$mon_adr->longueur_dom('toto@truc.fr') == 5
au lieu de :
$mon_adr->longueur_dom('toto@truc.fr') == 7

La seule différence (qui ne saute pas aux yeux) c'est la dernière ligne :
48 test(s) lancé(s). OK
au lieu de :
49 test(s) lancé(s). OK

J'ai encore dû merder quelque part...

En tous cas, merci de partager ce travail :)
Jean-Paul

Commentaire de pierreSabatier le 25/10/2011 03:47:06

Suite aux sollicitations publiques et privees, je vais faire mon possible pour ameliorer le packaging de mon code.
Sortie possible dans deux semaines (je n ai pas d ordi, utilise un qwerty pourri dans un web bar, etc).

Votre tres oblige ;-)

 Ajouter un commentaire


Discussions en rapport avec ce code source dans le forum

probleme de classe en php [ par Elkaire ] Donc voila mon probleme!!dans une premiere page je rensegne les insances de mon objet. Une fois renseigné je peux réutiliser les instances cet objet d Parametre de ma classe connexion et connexion/deconnexion [ par sebos63 ] Bonjour, 1. J'ai une classe de connexion et je souhaiterais savoir où il est judicieux de spécifier les paramètres d'une connexion. (Dans mon construc template.inc [ par joedalton ] bonjourVoila, je possede un fichier template.inc qui me permet de définir ma classe template, mais le probleme c'est que ce fichier est valable pour l Inversion de matrice [ par VanSama ] Bonjours, pour les besoins d'analyse d'une image, je recherche un algorithme de d'inversion d'une matrice NxN.J'ai lue beaucoup de chose sur le site d projet de développement : une framework [ par kiminox ] Bonjour à tous,Voilà, je suis à la recherche de développeurs ayant du temps libre pour m'aider à développer une framework en php avec MySQL.Profil rec Quand on parle de Classe [ par Tomcube ] C'est une question très vaste,On parle de plus en plus de programmation objet. Qui dit programmation objet dit classe. D'ailleurs, c'est fou le nombre problème de classe [ par guixyz ] Hello! Voila je suis en train d'apprendre a me servir des classes en php. Et j'en ai fait une pour gérer mes formulaires Donc j'ai ma classe, une fcti Pb avec classe en php, Affichage [ par matou82 ] Je voudrais savoir comment surcharger l'affichage d' echo ou print pour une classePour avoir:$obj = new Maclass;echo $obj;Cela affiche : "Object" Je s Classe et panier [ par romalafrite ] http://www.phpcs.com/code.aspx?ID=12931Quelqu'un peut me dire comment récupérer le contenu du panier une fois que la sélection a été faite ?visit inte fatal error sur chargement de class [ par fabrice_pi ] salut à tous,j'utilise une classe PHP pour faire mes taleaux en html. depuis peu j'ai l'erreur suivante :Fatal error: Cannot instantiate non-existent


Nos sponsors


Sondage...

Comparez les prix

CalendriCode

Mai 2012
LMMJVSD
 123456
78910111213
14151617181920
21222324252627
28293031   

Consulter la suite du CalendriCode

Photothèque

A découvrir



 
Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel (EBArtSoft), Merci à Vincent pour ses précieux conseils.
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés

Google Coop CodeS-SourceS Google Coop CodeS-SourceS
Temps d'éxécution de la page : 0,640 sec (3)

Nous contacter | Annoncer sur CodeS-SourceS | Mentions légales