2013-08-27. 2019/11/13. 2019/11/24 ATTENTION: Nous migrons progressivement les tests d'acceptance de tests/behat/ vers tests/acceptance. Le contenu du présent répertoire sera migré au fur et à mesure des besoins vers le nouveau, sachant que dans ce dernier, nous avons préféré partir de zéro avec un Behat 3.(version 2.x pour ici) et que ce que nous chercherons à faire c'est d'intégrer les fixtures de données que nous avions déjà auparavant. ============= Qu'est ceci ? ============= Behat is an Acceptance Tool. Use Behat to translate user stories into testable scenarios and make sure that the code delivers what the User Story - and consequetly the User - wants. Proceed like this: 1. Write the User Story: "En tant que... Je voudrait que... Afin que..." 2. Ecrire les Critères d'Acceptance en s'assurant qu'ils sont SMART, autrement dit testables. (Hint: Many acceptance criteria are pretty obvious, so is saves time writing those down in advance. Do that with a small delegation of the team: The product-owner, a tester, and a developer. This session is also known as a “The three amigos” session). 3. Pour chaque critère écrire un ou plusieurs exemples. (Hint: 20mn pour chaque User Story) 4. Convertir les exemples en Scénarios en langage Gherkin en écrivant du “Given … When … Then …” (Hint: Write scenarios that express intent and avoid any implementation details). (Hint: Show the scenarios to the stakeholders as soon as possible). ========================================================= Spécifier les fonctionnalités de l'application avec Behat ========================================================= Behat, l'outil standard de spécifications exécutables sur PHP est intégré à notre framework CodeIniter maison (ce qui signifie que nous n'avons plus à faire l'initialisation traditionnelle "$ behat --init" (*)). Behat permet d'écrire des spécifications sous forme de fichiers \features\%somefeature%.feature (dans le langage Gherkin) et de les exécuter avec la commande "behat" pour voir si ces spécifications sont satisfaites. Pour rappel, Behat nous permet de lister les QUOIs (quelles fonctionnalités) et de définir l'architecture générale du code en écrivant les détails des step definitions: quelles classes et méthodes avec leurs signatures. Tandis que les tests unitaires nous permettent de vérifier les COMMENTs en décrivant en détails de nombreux cas possibles à vérifier. (comment les fonctionnalités sont-elles implémentées). I. QUICK-START ============== ATTENTION: /features/bootstrap/FeatureContext.php contient des %FeatureN%Context à personnaliser 1. Coder des spécifications dans un fichier features/%some_feature%.features 2. Compiler pour obtenir un squelette de step definitions (stepdefs): $ beh // Depuis le repertoire au dessus, copier et adapter beb.{cmd|sh}.default au besoin TROUBLESHOOTING: Si: Behat Unrecognized option "paths" under "testwork" C'est parce qu'on utilise un fichier behat.yml inadapté pour Behat 3.x. Il faut suivre le modèle de yml pour 3.x. Voir Configuration de Behat plus loin. Voir https://www.semicolonworld.com/question/51230/behat-ldquo-unrecognized-options-rdquo Copier-coller les stepdefs proposés dans un nouveau fichier features/bootstrap/%SomeFeature%Context.php et ajouter ce fichier à la liste des contextes à charger par FeatureContext.php. Recompiler au besoin beh pour vérifier qu'aucun stepdef n'est manquant. 3. Coder dans les stepdefs, ce qui revient à faire la conception de l'application: quelles classes, quelles méthodes, quelles signatures, etc. Compiler au fur et à mesure et pour chaque plantage signalé, créer les éléments manquants dans l'application (les modèles et autres classes dans application\models notamment). Ecrire le minimum de code pour s'assurer que les spécifications passent à la compilation. 4. Une fois que les specs sont satisfaites, passer dans les tests unitaires pour spécifier et coder des implémentations importantes. Recommencer avec une nouvelle feature. II. DETAILS =========== 1. Ecire les spécifications ------------------------ Les sprécifications se mettent dans des fichiers features/%som_feature%.feature. Nous adoptons la convention bas-de-casse et underscores pour les noms de fichier. Créer un fichier pour chaque feature. C'est fortement déconseillé par le créateur de Behat si on ne le fait pas en connaissance de cause, dans la mesure où on risque de définir de plusieurs façons contradictoires une même et unique situation, mais c'est l'unique façon pour nous de garder les tailles des fichiers et l'organisation des features gérables. Pour rappel, voici la structure type d'un feature avec 1 à N scenarios: Feature: [feature name] In order to [benefit] As a [role] I need to [feature desc] Background: Short description of the shared background Given [some context] And [more context] Scenario: Some description of the scenario Given [some context] And [more context] When [some event] And [second event occurs] Then [outcome] And [another outcome] But [another outcome] Scenario Outline: Some description of the scenario outline Given [some context with ] And [some context with ] When [some event with ] Then [outcome] But [another outcome] Examples: | parameter1 | parameter2 | | value1a | value2a | | value1b | value2b | Conseil 1: Ecire les features en francais pour que tout le monde comprenne mais en restant dans les mots-clés anglais afin qu'on n'ait pas à gérer le cas échéant un problème de langue ou de reconnaissance syntaxique par l'IDE. Conseil 2: Eviter les caractères étendus même si on écrit les features en français. Les caractères étendus sont traduits en hiéroglyphes dans les step defs proposés dans la ligne de commande (idem pour CMD que pour Windows Power Shell). Cela arrive même si on prend soin d'encoder la page en UTF8 préalablement. Conseil 3: Les apostrophes semblent son ok du moment qu'il n'y en a qu'un. Au dela, deux apostrophes sont interprétés comme délimiteurs de la chaîne qui se trouver en leur milieu. Catastrophique pour les noms de step defs générés. Donc à éviter tout simplement en reformulant télégraphiquement les phrases ou en le remplacant par des espaces. Conseil 4: Ne jamais utiliser des valeurs vides danns les tableaux d'Examples Les valeurs vides dans les tableaux de Scenario Outlines sont mal supportés. Si on met des valeurs vides les step définitions générés peuvent avoir : - Des noms incomplets, soit tronqués devant, soit tronqués derrière - Les arguments ne sont pas générés et au lieu de cela, les valeurs du tableau sont aléatoirement intégrées aux noms de certains step defs. - Les noms des stepdefs sont répétés et pour les distinguer, Behat ajoute des nombres incrémentés comme suffixés. Nous y voyons un inconvénient insoluble pour le moment: On ne peut pas traiter les cas où soit une valeur et manquante soit le résultat retourné est une valeur vide. Conseil 5: Ne pas confondre valeurs entre <...> et entre {"..." ou '...'}. Les <...> s'utilisent dans les Scenario Outlines, deviennent des arguments pour les méthodes des step définitions et sont remplacés par les valeurs données dans les tableaux d'exemples. Les "..." ou '..." sont ... (TODO) Conseil conceptuel: Décrire qu'est-ce qui doit se passer et non pas comment. On parle d'approche "déclarative" plutôt qu' "impérative". Si on n'est pas sûr, se poser la question "Est-ce que cette feature devra changer si nous changeons la façon de l'implémenter ?" 2. Compiler pour obtenir un squelette de step definitions ------------------------------------------------------ a. Compiler notre feature avec la commande beh.cmd située à la racine du site (ii). Behat va afficher une liste de méthodes vides, les step definitions, qu'il nous propose d'implémenter (iii). > beh b. Copier-coller les step defs proposées vers un nouveau fichier construit sur les bases du modèle SomeContext.php.template (iv): features\bootstrap\%SomeFeature%Context.php. Noter que nous adoptons le même nom que pour le fichier de feature, mais avec une convention différente: camel case et sans underscores. c. Ouvrir features\bootstrap\FeatureContext.php et ajouter le nouveau fichier à la liste des implémentations (subcontext) à charger: $this->useContext('%SomeFeature%Context', new %SomeFeature%Context($parameters)); Recompiler au besoin (beh) pour s'assurer qu'aucun stepdef n'est oublié et que l'implémentation est correctement chargée dans FeatureContext. 3. Coder dans les step definitions ------------------------------- Supprimer le code par défaut "throw new PendingException();" et commencer à coder la logique applicative. Pour signifier un échec (failure), utiliser plutôt les assertions de style PHPUnit car nous les avons rendus disponibles dans ce package. Ainsi, utiliser (sans le préfixe $this) les assertTrue(), assertEquals() et autres méthodes PHPUnit pour faire les véfifications. Ce que nous mettons dans les step definitions sont réellement la conception de notre application: les classes, les méthodes, les signatures, etc. Compiler au fur et à mesure et pour chaque plantage signalé, créer les éléments manquants dans l'application. Typiquement nos premiers fichiers seront dans application\models car c'est là le coeur de l'application. Accessoirement, nous pourrions être amenés à créer des codes de support dans les libraries et les helpers. Noter toutefois que si un problématique concerne directement le "domaine", nous préférons maintenir son code dans application\models, *même* si ce n'est pas directement un modèle CodeIgniter, parce que cela permet de regrouper en un seul endroit les codes qui encapsulent l'ensemble de la logique applicative. Ne doivent aller dans les libraries, helpers et le cas échéant third_party, que les codes qui sont réutilisables dans d'autres projets. Ecrire le minimum de code pour s'assurer que les spécifications passent à la compilation. 4. Passer dans les tests unitaires pour compléter les specs --------------------------------------------------------- Une fonctionnalité est "satisfaite" généralement parce que nous aurons créé du code bidon derrière qui donne les valeurs attendues par les specs Behat. Plus rarement, nous nous serons directement précipités dans le code pour faire l'implémentation réelle. Dans le premier cas, nous aurons à passer à l'implémentation réelle, définitive, du code. Dans le deuxième cas, nous aurons probablement codé une implémentation fragile qui ne répond qu'aux situations les plus simples/directes/courantes. C'est pourquoi dans les deux cas, il nous faut immédiatement, sans attendre, passer en mode test unitaire et écrire les codes (specs) nécessaires pour rendre notre fonctionnalité des plus solides. Exemple sommaire (à développer): - Behat nous aura permis de vérifier que la fonction Toto->Addition($a, $b) est bien disponible Nous pourrions avoir retourné des valeurs bidons mais attendus pour que le test Behat passe. - Mais tout de suite après, nous devrions écrire un ensemble de tests unitaires pous nous assurer que le calcul est réellement fait, que la fonction résite à la fourniture de valeurs arbitaires, que les conventions éventuelles sont correctement implémentées, etc. Tout cela ne PEUT PAS se définir dans Behat et doit se spécifier dans les tests unitaires. III. REPORTINGS UTILES ====================== Pour générer un rapport HTML lisible par les non-développeurs (pour le PO et les stakeholders)/ Remarquer la création du chemin features/reports que l'on pourrait déployer enlignne avec le build: $ behat --format="html" > features/reports/somereport.html Pour lister les step defintions existants de façon à les trier et détecter le cas échéant les redondances ou des incohérences (lorsque le projet grandit et les stepdefs en centaines ou milliers...) # Liste les step definitions seuls $ behat --definitions l # Liste les step definitions avec la méthode correspondante $ behat --definitions i III. CONFIGURATION DE BEHAT =========================== Divers pour information: 1. Ajout d'un fichier de configuration "behat.yml": Pour ce faire, il nous fallait personnaliser la configuration de Behat avec un fichier behat.yml. Ce fichier doit obligatoirement se trouver *au dessus* du répertoire \features. C'est la raison pour laquelle il se trouve à la racine du site. Son contenu par défaut est très simple étant donné le seul changement de chemin ci-dessus: # NEW syntax for Behat 3.x default: autoload: '': %paths.base%/features/bootstrap # au lieu de "bootstrap" suites: default: paths: - %paths.base%/features # OLD syntax for Behat 2.x # behat.yml default: paths: features: features bootstrap: %behat.paths.features%/bootstrap IV. FOOTNOTES ============= * 2019/11/14 Voir à quoir sert $ behat --config-reference (*) Initialion de Behat: ATTENTION: Nous n'avons plus à faire ceci dans cette distribution CodeIgniter maison puisque nous avons déjà fait cette initialisation au moment de la confection du package CI afin d'y inclure le support de Behat. Sinon, s'il faut le faire à d'autres occasions, depuis le répertoire [du projet] où l'on veut stocker les spécifications Behat: > behat --init Cela créera sur place un sous-répertoire features/ dans lequel nous sommes invités à mettre nos spécifications sous forme de fichiers *.features. Cela crééra aussi un fichier features/bootstrap/FeatureContext.php qui accueillera notre futur code pour satisfaire les spécifications. (ii) La commande est normalement "behat", mais le comportement par défaut de cette commande ne nous satisfait pas. D'une part parce qu'elle est trop bavarde, et d'autre part parce que nous voulons pouvoir discriminer par défaut entre specs à ignorer et specs à tester avec un tag "@disabled" de notre cru. D'où la commande "beh" qui est en fait constituée de ceci: > behat --format="progress" --tags ~@disabled Le format offert par "progress" est sobre car il s'agit essentiellement d'une suite de points montrant la progression, à l'image de l'affichage de phpunit. Cependant, nous bénéficions encore de bons message d'erreurs et de la proposition de squelettes de code (step definitions manquants). L'option "~@disabled" sur les tags nous permet de marquer avec "@disabled" les features que nous voulons délaisser momentanément. Cet article sur http://everzet.com/post/1461842003/behat-v03 explique mieux la syntaxe des tags que la documentation Behat (dans le marquage de la feature d'une part et dans la ligne de commande d'autre part) et nous la résumons ici: // Entête du feature ou du scenario: @tag1 @tag2 Feature: ... // Dans la ligne de commande: ... --tags @tag1,@tag2 # Exécute ceux qui ont @tag1 OU @tag2 (virgule) ... --tags @tag1&&@tag2 # Exécute ceux qui ont @tag1 ET @tag2 (&&) ... --tags ~@tag1 # Exécute tout sauf ceux qui ont @tag1 (~) ... --tags ~@tag1&&@tag2 # Exécute ceux qui ont @tag2 sauf ceux qui ont @tag1 parmi eux (iii) Si nous désirons ne compiler qu'un feature en particulier (par exemple pour vérifier ses stepdefs), nous avons le choix entre faire > behat features/%somefeature%.feature, et marquer les autres features avec le tag @disabled (voir note ci-dessus). Nous préférons cette denière méthode car le "beh.cmd" que nous avons implémenté ne prend pas encore en compte les arguments de ligne de commande et nous serions donc obligés de revenir à l'usage du "behat" par défaut. (iv) Voir si nécessaire: http://www.odr.lu/node/139 http://docs.behat.org/guides/4.context.html#using-subcontexts http://docs.behat.org/guides/4.context.html#creating-your-very-own-context-class http://blog.scur.pl/2012/06/subcontexts-behat-mink/