DéveloppeurWeb.Com
    DéveloppeurWeb.Com
    • Agile Zone
    • AI Zone
    • Cloud Zone
    • Database Zone
    • DevOps Zone
    • Integration Zone
    • Web Dev Zone
    DéveloppeurWeb.Com
    Home»Web Dev Zone»Développement piloté par les tests avec la bibliothèque de test oclif : deuxième partie
    Web Dev Zone

    Développement piloté par les tests avec la bibliothèque de test oclif : deuxième partie

    novembre 10, 2021
    Développement piloté par les tests avec la bibliothèque de test oclif : première partie
    Share
    Facebook Twitter Pinterest Reddit WhatsApp Email

    Dans la première partie de cette série sur la bibliothèque de tests oclif, nous avons utilisé une approche de développement piloté par les tests pour construire notre time-tracker CLI. Nous avons parlé du framework oclif, qui aide les développeurs à se passer de la configuration et du passe-partout afin qu’ils puissent écrire la viande de leurs applications CLI. Nous avons également parlé de @oclif/test et @oclif/fancy-test, qui s’occupent de la configuration et du démontage répétitifs afin que les développeurs puissent se concentrer sur l’écriture de leurs tests Mocha.

    Notre time-tracker l’application est une CLI multi-commandes. Nous avons déjà écrit des tests et implémenté notre première commande pour ajouter un nouveau projet à notre tracker. Ensuite, nous allons écrire des tests et implémenter notre commande « start timer ».

    Pour rappel, l’application finale est publiée sur GitHub comme référence au cas où vous vous heurterez à un barrage routier.

    Premier test de la commande Start Timer

    Maintenant que nous pouvons ajouter un nouveau projet à notre suivi du temps, nous devons pouvoir démarrer le chronomètre pour ce projet. L’utilisation de la commande ressemblerait à ceci :

    time-tracker start-timer project-one
    

    Puisque nous adoptons une approche TDD, nous allons commencer par écrire le test. Pour notre test de chemin heureux, « project-one » existe déjà, et nous pouvons simplement démarrer la minuterie pour cela.

    // PATH: test/commands/start-timer.test.js
    
    const {expect, test} = require('@oclif/test')
    const StartTimerCommand = require('../../src/commands/start-timer')
    const MemoryStorage = require('../../src/storage/memory')
    const {generateDb} = require('../test-helpers')
    
    const someDate = 1631943984467
    
    describe('start timer', () => {
      test
      .stdout()
      .stub(StartTimerCommand, 'storage', new MemoryStorage(generateDb('project-one')))
      .stub(Date, 'now', () => someDate)
      .command(['start-timer', 'project-one'])
      .it('should start a timer for "project-one"', async ctx => {
        expect(await StartTimerCommand.storage.load()).to.eql({
          activeProject: 'project-one',
          projects: {
            'project-one': {
              activeEntry: 0,
              entries: [
                {
                  startTime: new Date(someDate),
                  endTime: null,
                },
              ],
            },
          },
        })
        expect(ctx.stdout).to.contain('Started a new time entry on "project-one"')
      })
    })

    Il y a beaucoup de similitude entre ce test et le premier test de notre commande « ajouter un projet ». Une différence, cependant, est le supplément stub() appel. Puisque nous allons démarrer la minuterie avec new Date(Date.now()), notre code de test écrasera de manière préventive Date.now() rendre someDate. Bien que nous ne nous soucions pas de la valeur de someDate est, ce qui est important, c’est qu’il est fixé.

    Lorsque nous exécutons notre test, nous obtenons l’erreur suivante :

    Error: Cannot find module '../../src/commands/start-timer'
    

    Il est temps d’écrire du code d’implémentation !

    Commencer à implémenter la commande Start Time

    Nous devons créer un fichier pour notre start-timer commander. Nous dupliquons le add-project.js fichier et renommez-le en start-timer.js. Nous éliminons la plupart des run méthode, et nous renommons la classe de commande en StartTimerCommand.

    const {Command, flags} = require('@oclif/command')
    const FilesystemStorage = require('../storage/filesystem')
    
    class StartTimerCommand extends Command {
      async run() {
        const {args} = this.parse(StartTimerCommand)
        const db = await StartTimerCommand.storage.load()
    
        await StartTimerCommand.storage.save(db)
      }
    }
    
    StartTimerCommand.storage = new FilesystemStorage()
    
    StartTimerCommand.description = `Start a new timer for a project`
    
    StartTimerCommand.flags = {
      name: flags.string({char: 'n', description: 'name to print'}),
    }
    
    module.exports = StartTimerCommand

    Maintenant, lorsque nous exécutons à nouveau le test, nous voyons que le db n’a pas été mis à jour comme nous l’avions prévu.

    1) start timer
           should start a timer for "project-one":
    
          AssertionError: expected { Object (activeProject, projects) } to deeply equal { Object (activeProject, projects) }
          + expected - actual
    
           {
          -  "activeProject": [null]
          +  "activeProject": "project-one"
             "projects": {
               "project-one": {
          -      "activeEntry": [null]
          -      "entries": []
          +      "activeEntry": 0
          +      "entries": [
          +        {
          +          "endTime": [null]
          +          "startTime": [Date: 2021-09-18T05:46:24.467Z]
          +        }
          +      ]
               }
             }
           }
    
          at Context.<anonymous> (test/commands/start-timer.test.js:16:55)
          at async Object.run (node_modules/fancy-test/lib/base.js:44:29)
          at async Context.run (node_modules/fancy-test/lib/base.js:68:25)

    Pendant que nous y sommes, nous savons également que nous devrions enregistrer quelque chose pour dire à l’utilisateur ce qui vient de se passer. Mettons donc à jour la méthode run avec le code pour le faire.

    const {args} = this.parse(StartTimerCommand)
    const db = await StartTimerCommand.storage.load()
    
    if (db.projects && db.projects[args.projectName]) {
        db.activeProject = args.projectName
        // Set the active entry before we push so we can take advantage of the fact
        // that the current length is the index of the next insert
        db.projects[args.projectName].activeEntry = db.projects[args.projectName].entries.length
        db.projects[args.projectName].entries.push({startTime: new Date(Date.now()), endTime: null})
    }
    
    this.log(`Started a new time entry on "${args.projectName}"`)
    
    await StartTimerCommand.storage.save(db)
    

    En exécutant à nouveau le test, nous constatons que nos tests sont tous réussis !

    add project
        ✓ should add a new project
        ✓ should return an error if the project already exists (59ms)
    
    start timer
        ✓ should start a timer for "project-one"

    Triste chemin : démarrer une minuterie sur un projet inexistant

    Ensuite, nous devons informer l’utilisateur s’il tente de démarrer une minuterie sur un projet qui n’existe pas. Commençons par écrire un test pour cela.

    test
      .stdout()
      .stub(StartTimerCommand, 'storage', new MemoryStorage(generateDb('project-one')))
      .stub(Date, 'now', () => someDate)
      .command(['start-timer', 'project-does-not-exist'])
      .catch('Project "project-does-not-exist" does not exist')
      .it('should return an error if the user attempts to start a timer on a project that doesn't exist', async _ => {
        // Expect that the storage is unchanged
        expect(await StartTimerCommand.storage.load()).to.eql({
          activeProject: null,
          projects: {
            'project-one': {
              activeEntry: null,
              entries: [],
            },
          },
        })
      })

    Et, nous échouons à nouveau.

    1 failing
    
      1) start timer
           should return an error if the user attempts to start a timer on a project that doesn't exist:
         Error: expected error to be thrown
          at Object.run (node_modules/fancy-test/lib/catch.js:8:19)
          at Context.run (node_modules/fancy-test/lib/base.js:68:36)

    Écrivons du code pour corriger cette erreur. Nous ajoutons l’extrait de code suivant au début du run méthode, juste après avoir chargé le db du stockage.

    if (!db.projects?.[args.projectName]) {
        this.error(`Project "${args.projectName}" does not exist`)
    }

    On refait les tests.

    add project
        ✓ should add a new project (47ms)
        ✓ should return an error if the project already exists (75ms)
    
    start timer
        ✓ should start a timer for "project-one"
        ✓ should return an error if the user attempts to start a timer on a project that doesn't exist

    J’y suis arrivé! Bien sûr, il y a une autre chose que cette commande devrait faire. Imaginons que nous ayons déjà démarré une minuterie sur project-one et nous voulons passer rapidement la minuterie à project-two. Nous nous attendrions à ce que la minuterie en marche project-one s’arrêtera et une nouvelle minuterie s’allumera project-two va commencer.

    Arrêtez une minuterie, démarrez une autre

    Nous répétons notre cycle rouge-vert TDD en écrivant d’abord un test pour représenter la fonctionnalité manquante.

    test
      .stdout()
      .stub(StartTimerCommand, 'storage', new MemoryStorage({
        activeProject: 'project-one',
        projects: {
          'project-one': {
            activeEntry: 0,
            entries: [
              {
                startTime: new Date(someStartDate),
                endTime: null,
              },
            ],
          },
          'project-two': {
            activeEntry: null,
            entries: [],
          },
        },
      }))
      .stub(Date, 'now', () => someDate)
      .command(['start-timer', 'project-two'])
      .it('should end the running timer from another project before starting a timer on the requested one', async ctx => {
        // Expect that the storage is unchanged
        expect(await StartTimerCommand.storage.load()).to.eql({
          activeProject: 'project-two',
          projects: {
            'project-one': {
              activeEntry: null,
              entries: [
                {
                  startTime: new Date(someStartDate),
                  endTime: new Date(someDate),
                },
              ],
            },
            'project-two': {
              activeEntry: 0,
              entries: [
                {
                  startTime: new Date(someDate),
                  endTime: null,
                },
              ],
            },
          },
        })
    
        expect(ctx.stdout).to.contain('Started a new time entry on "project-two"')
      })

    Ce test nécessite un autre horodatage, que nous appelons someStartDate. Nous ajoutons que près du haut de notre start-timer.test.js déposer:

    const someStartDate = 1631936940178
    const someDate = 1631943984467

    Ce test est plus long que les autres tests, mais c’est parce que nous avions besoin d’un db initialisé dans MemoryStorage pour représenter ce cas de test. Vous pouvez voir que, initialement, nous avons une entrée avec un startTime et non endTime dans project-one. Dans l’assertion, vous remarquerez que le endTime dans project-one est renseigné, et il y a une nouvelle entrée active dans project-two avec un startTime et non endTime.

    Lorsque nous exécutons notre suite de tests, nous voyons l’erreur suivante :

    1) start timer
           should end the running timer from another project before starting a timer on the requested one:
    
          AssertionError: expected { Object (activeProject, projects) } to deeply equal { Object (activeProject, projects) }
          + expected - actual
    
           {
             "activeProject": "project-two"
             "projects": {
               "project-one": {
          -      "activeEntry": 0
          +      "activeEntry": [null]
                 "entries": [
                   {
          -          "endTime": [null]
          +          "endTime": [Date: 2021-09-18T05:46:24.467Z]
                     "startTime": [Date: 2021-09-18T03:49:00.178Z]
                   }
                 ]
               }
    
          at Context.<anonymous> (test/commands/start-timer.test.js:76:55)
          at async Object.run (node_modules/fancy-test/lib/base.js:44:29)
          at async Context.run (node_modules/fancy-test/lib/base.js:68:25)

    Cette erreur nous indique que notre CLI a correctement créé une nouvelle entrée dans project-two, mais il n’a pas d’abord mis fin à la minuterie sur project-one. Notre application n’a pas non plus changé le activeEntry de 0 à null dans project-one comme on s’y attendait.

    Corrigeons le code pour résoudre ce problème. Juste après avoir vérifié que le projet demandé…

    Share. Facebook Twitter Pinterest LinkedIn WhatsApp Reddit Email
    Add A Comment

    Leave A Reply Cancel Reply

    Catégories

    • Politique de cookies
    • Politique de confidentialité
    • CONTACT
    • Politique du DMCA
    • CONDITIONS D’UTILISATION
    • Avertissement
    © 2023 DéveloppeurWeb.Com.

    Type above and press Enter to search. Press Esc to cancel.