Make It Workflow — Partie 13 : Prise en charge des scénarios de gestion de tests

Delphine Massenhove

Bienvenue à nouveau dans notre série « Make It Workflow » ! Nous avons reçu plusieurs demandes de la communauté concernant les fonctionnalités de YouTrack pouvant être utilisées par les équipe d’assurance qualité. Aujourd’hui, nous allons voir comment maintenir les scénarios de gestion des tests (Test Management Scenarios) dans YouTrack. Cet article explique comment configurer YouTrack afin de conserver la fonctionnalité de gestion des tests au sein même de YouTrack et de ne pas avoir à utiliser de solutions tierces. Cette approche vous permet d’économiser sur les coûts de licence et de faciliter le déroulement du processus de gestion de tests.

Cet article est destiné aux gestionnaires de tests et à toute personne intéressée par les workflows de YouTrack. Vous y trouverez notamment un exemple pratique d’élaboration d’un processus de test dans YouTrack, avec des recommandations pour le paramétrage des projets et des tickets et une description de l’implémentation des scénarios de gestion de tests.

Nous avons également établi une liste de blocs de code de workflow prêts à l’emploi pour l’automatisation des processus de gestion des tests. Ces blocs de code facilitent entre autres l’association des cas de test à l’exécution de test spécifique, le clonage d’une exécution de test, l’affichage de suggestions pour le prochain test.

Paramètres pour les projets de gestion de tests dans YouTrack

Pour vos projets, vous pouvez utiliser des types de tickets spécifiques à la gestion des tests comme Test Case (cas de tests), Test Suite (un ensemble de cas de tests), Test Run (un ensemble de cas de tests ou de suites de tests affectés à un cycle de test spécifique) et Test Case Executions pour une exécution de test spécifique. Vous devez configurer les types de ticket requis pour votre projet TMS. Il existe 3 types de liens de tickets pour établir les connexions et la pertinence entre vos tâches de gestion de tests :

  • Les liens standards « parent-subtasks » maintiennent les relations entre exécutions de tests et exécutions de cas de tests, ainsi qu’entre suite de tests et cas de tests.
  • Le lien personnalisé « Execution » maintient les relations entre cas de test et exécution de cas de test.
  • Le lien personnalisé « Related Bug » maintient les relations entre les tests ayant échoué et les bugs attribués.

Les critères de tests peuvent être maintenus sous la forme d’un article associé aux tickets dans un champ de texte.
En outre, vous pouvez définir des champs personnalisés pour vos tâches de gestion de tests, tels que Test mode, Category, Test flowet Application, ainsi que des valeurs prédéfinies (par exemple, des statuts de test prédéfinis). Vous pouvez également utiliser des champs conditionnels lorsque vous devez afficher des informations spécifiquement pour un type de ticket.

Un ensemble de champs prédéfinis pour les types de ticket « Test Case » (Cas de tests) et « Test Run » (Éxécution de tests) peut être adapté en fonction des besoins de votre entreprise.

Configuration de Test Runs et Test Execution

Pour tester une version spécifique d’un produit par exemple, il faut créer une série de tests et lui attribuer un certain nombre de suites et de cas de tests pertinents. Pour ce faire, suivez les étapes suivantes :

  • Créez un ticket de type « Test Run ».
  • Utilisez le lien personnalisé « Assigned test case and test suite » (« Cas de test et suite de tests affectés ») pour faire le lien avec le ticket.
  • Dans la fenêtre contextuelle, précisez les cas de tests à inclure dans Test Run.

Les étapes du workflow « Populate Test Run » (Remplir la série de tests) permettent à votre équipe d’assurance qualité d’économiser du temps et des efforts :

  • Des copies de toutes les suites de tests et de tous les cas de test sélectionnés sont créées dans le système. Tous les tickets seront de type « Test Case Execution ».
  • Le type de lien « Execution » associera les exécutions de cas de tests aux cas de tests sélectionnés.
  • Les tickets nouvellement créés seront associés à un Test Run en tant que ticket parent et à ses sous-tâches connexes.

Voici comment cela fonctionne :

Étendre le bloc de code

var entities = require('@jetbrains/youtrack-scripting-api/entities');
var workflow = require('@jetbrains/youtrack-scripting-api/workflow');

exports.rule = entities.Issue.onChange({
  title: 'Populate-test-run',
  guard: function(ctx) {
    var issue = ctx.issue;
    return !issue.isChanged('project') && issue.Type && (issue.Type.name == ctx.Type.TestRun.name) && issue.links[ctx.Execution.outward].added.isNotEmpty() && issue.isReported;
  },
  action: function(ctx) {
    var issue = ctx.issue;
    var totalTestRuns = issue.links[ctx.Execution.outward].added.size;
    issue.links[ctx.Execution.outward].added.forEach(function(TestCase) {
      TestCase.links[ctx.Execution.inward].delete(issue);

      var TestCaseRun = TestCase.copy();
      TestCaseRun.Type = ctx.Type.TestExecution.name;
      TestCaseRun.Status = ctx.Status.NoRun.name;
      Object.keys(TestCaseRun.links).forEach(function(linkType) {
       if (!TestCaseRun.links[linkType])
        return;
         TestCaseRun.links[linkType].clear();
      });
      TestCaseRun.summary = "[TEST_CASE_EXECUTION" + "] [" + TestCaseRun.summary + "]";

      TestCaseRun.links[ctx.Subtask.inward].add(issue);
      issue.links[ctx.Subtask.outward].add(TestCaseRun);
      TestCaseRun.links[ctx.Execution.outward].add(TestCase);
    });
    issue.fields['Total number of test cases'] = totalTestRuns;
  },
  requirements: {
    Execution: {
      type: entities.IssueLinkPrototype,
      name: 'Execution',
      inward: 'Execution',
      outward: 'Assigned test case or test suite'
    },
    Subtask: {
      type: entities.IssueLinkPrototype,
      name: 'Subtask',
      inward: 'parent for',
      outward: 'subtask of'
    },
    Type: {
      type: entities.EnumField.fieldType,
      TestExecution: {
        name: "Test Case Execution"
      },
      TestRun: {
        name: "Test Run"
      },
      TestCase: {
        name: "Test Case"
      },
      TestSuite: {
        name: "Test Suite"
      }
    },
    Total: {
      type: entities.Field.integerType,
      name: 'Total number of test cases'
    },
    TotalFailed: {
      type: entities.Field.integerType,
      name: 'Number of failed test cases'
    },
    TotalPassed: {
      type: entities.Field.integerType,
      name: 'Number of passed test cases'
    },
    Status: {
      type: entities.EnumField.fieldType,
      InProgress: {
        name: 'In Progress'
      },
      Passed: {
        name: 'Passed'
      },
      Failed: {
        name: 'Failed'
      },
      NoRun: {
        name: 'No Run'
      },
    },
  }
});

Maintenir les activités de tests

Une fois les Test Runs entièrement configurés, vous pouvez commencer les tests. Par défaut, tous les tickets de type « Test Case Execution » et « Test Run » ont le statut « No Run ».

Comment passer d’un test à un autre

Lorsque vous travaillez avec un ensemble de tests, il y a deux façons de passer d’un test à un autre :

  1. Modifier manuellement le statut du test sur la page de la liste des tickets.
  2. Ouvrir un test et passer au test suivant non terminé. Une fenêtre contextuelle vous suggère le prochain test à exécuter.
    Les action suivantes peuvent être automatisées :

    • La vérification de l’existence de tests ayant le statut « No Run » et appartenant au même Test Run.
    • L’affichage d’un message avec l’URL du prochain test disponible (le cas échéant).

      ApplyingCommandForSubsystem

    Pour ce faire, utilisez le code ci-dessous.

    Étendre le bloc de code

     action: function(ctx) {
      var issue = ctx.issue;
      if (!issue.links['subtask of'].isEmpty) {
                  var parent = issue.links['subtask of'].first();
                  var TestRunList = parent.links[ctx.Subtask.outward];
                  var resultSet = null;
                  var isPassing = true;
                  TestRunList.forEach(function(v) {
                    if (v.Status.name == ctx.Status.Failed.name) {
                      isPassing = false;
                    } else if ((v.Status.name == ctx.Status.InProgress.name) && (v.id !== issue.id)) {
                      resultSet = v;
                    }
                  });
                  if (resultSet) {
                    var otherIssueLink = ' ' + resultSet.id + '';
                    var message = 'Switch to next open test in current Test Run' + otherIssueLink + '.';
                    workflow.message(message);
        }
    }
    }
    

Statuts des tests

En passant d’un test à un autre, vous pouvez soit attribuer au test le statut « Passed » (Réussi) ou « Failed » (Échoué) si un bug a été identifié pendant l’exécution du cas de test. En fonction des paramètres YouTrack initialement configurés pour ce projet, il existe plusieurs statuts de Test Run prédéfinis :

  • « Failing » : au moins un des tests associés a le statut « No run » et au moins un des tests associés a le statut « Failed ».
  • « Passing » : au moins un des tests associés a le statut « No run » et aucun test associé n’a le statut « Failed ».
  • « Failed » : aucun test associé n’a le statut « No Run » et au moins un des tests associés a le statut « Failed ».
  • « Passed » : aucun test associé n’a le statut « No Run » ou « Failed ».

Ces statuts aident l’équipe d’assurance qualité à suivre la progression des tests tout au long du cycle de tests. Il est important de noter que le statut des séries de tests dépend uniquement des tests associés et ne doit pas être modifié manuellement. Le processus de détermination des statuts de Test Run est implémenté à l’aide du workflow des machines d’état par type de ticket. Le bloc de code pour le passage d’un test à un autre est également intégré dans le workflow.

Étendre le bloc de code


var entities = require('@jetbrains/youtrack-scripting-api/entities');
var workflow = require('@jetbrains/youtrack-scripting-api/workflow');

exports.rule = entities.Issue.stateMachine({
  title: 'status-management',
  stateFieldName: 'Status',
  typeFieldName: 'Type',
  defaultMachine: {
    'No Run': {
      initial: true,
      transitions: {
        'Failed': {
          targetState: 'Failed',
          action: function(ctx) {
            var issue = ctx.issue;
            if (!issue.links['subtask of'].isEmpty) {
              var parent = issue.links['subtask of'].first();
              var TestRunList = parent.links[ctx.Subtask.outward];
              var resultSet = null;
              var isPassing = true;
              TestRunList.forEach(function(v) {
                if (v.Status.name == ctx.Status.Failed.name) {
                  isPassing = false;
                } else if ((v.Status.name == ctx.Status.InProgress.name) && (v.id !== issue.id)) {
                  resultSet = v;
                }
              });
              if (resultSet) {
                var otherIssueLink = ' ' + resultSet.id + '';
                var message = 'Switch to next open test in current Test Run' + otherIssueLink + '.';
                workflow.message(message);
                // Updating Test Run Status 
                parent.fields["Status"] = ctx.Status.Failing;
              } else {
                parent.fields["Status"] = ctx.Status.Failed;
              }
            }
          }
        },
        'Passed': {
          guard: function(ctx) {
            var issue = ctx.issue;
            return !issue.isChanged('project') && !issue.becomesReported && issue.isReported && (issue.Type.name == ctx.Type.TestExecution.name);
          },
          targetState: 'Passed',
          action: function(ctx) {
            var issue = ctx.issue;
            if (!issue.links['subtask of'].isEmpty) {
              var parent = issue.links['subtask of'].first();
              var TestRunList = parent.links[ctx.Subtask.outward];
              var resultSet = null;
              var isPassing = true;
              TestRunList.forEach(function(v) {
                if (v.Status.name == ctx.Status.Failed.name) {
                  isPassing = false;
                } else if ((v.Status.name == ctx.Status.InProgress.name) && (v.id !== issue.id)) {
                  resultSet = v;
                }
              });
              if (resultSet) {
                var otherIssueLink = ' ' + resultSet.id + '';
                var message = 'Switch to next open test in current Test Run' + otherIssueLink + '.';
                workflow.message(message);
                parent.fields["Status"] = (isPassing) ? ctx.Status.Passing : ctx.Status.Failing;
              } else {
                parent.fields["Status"] = (isPassing) ? ctx.Status.Passed : ctx.Status.Failed;
              }
            }
          }
        }
      }
    },
    Passed: {
      transitions: {
        'Failed': {
          guard: function(ctx) {
            var issue = ctx.issue;
            return !issue.isChanged('project') && !issue.becomesReported && issue.isReported && (issue.Type.name == ctx.Type.TestExecution.name);
          },
          targetState: 'Failed',
          action: function(ctx) {
            var issue = ctx.issue;
            if (!issue.links['subtask of'].isEmpty) {
             var parent = issue.links['subtask of'].first();
              var TestRunList = parent.links[ctx.Subtask.outward];
              var resultSet = null;
              TestRunList.forEach(function(v) {
                if (v.Status.name == ctx.Status.Failed.name) {
                } else if ((v.Status.name == ctx.Status.InProgress.name) && (v.id !== issue.id)) {
                  resultSet = v;
                }
              });
              if (resultSet) {
                var otherIssueLink = ' ' + resultSet.id + '';
                var message = 'Switch to next open test in current Test Run' + otherIssueLink + '.';
                workflow.message(message);
                parent.fields["Status"] = ctx.Status.Failing;
              } else {
                parent.Status = ctx.Status.Failed;
              }
            }
          }
        },
        'No Run': {
          guard: function(ctx) {
            var issue = ctx.issue;
            return !issue.isChanged('project') && !issue.becomesReported && issue.isReported && (issue.Type.name == ctx.Type.TestExecution.name);
          },
          targetState: 'No Run',
          action: function(ctx) {
            var issue = ctx.issue;
            if (!issue.links['subtask of'].isEmpty) {
              var parent = issue.links['subtask of'].first();
              var TestRunList = parent.links[ctx.Subtask.outward];
              var ActiveTestRun = false;
              var isPassing = true;
              TestRunList.forEach(function(v) {
                if (v.Status.name == ctx.Status.Failed.name) {
                  isPassing = false;
                  ActiveTestRun = true;
                } else if ((v.Status.name == ctx.Status.Passed.name) && (v.id !== issue.id)) {
                  ActiveTestRun = true;
                }
              });
              if (ActiveTestRun) {
                parent.fields["Status"] = (isPassing) ? ctx.Status.Passing : ctx.Status.Failing;
              } else parent.fields["Status"] = ctx.Status.InProgress;
            }
          }
        }
      }
    },
    Failed: {
      transitions: {
        'Passed': {
          guard: function(ctx) {
            var issue = ctx.issue;
            return !issue.isChanged('project') && !issue.becomesReported && issue.isReported && (issue.Type.name == ctx.Type.TestExecution.name);
          },
          targetState: 'Passed',
          action: function(ctx) {
            var issue = ctx.issue;
            if (!issue.links['subtask of'].isEmpty) {
              var parent = issue.links['subtask of'].first();
              var TestRunList = parent.links[ctx.Subtask.outward];
              var resultSet = null;
              var isPassing = true;
              TestRunList.forEach(function(v) {
                if ((v.Status.name == ctx.Status.Failed.name) && (v.id !== issue.id)) {
                  isPassing = false;
                } else if ((v.Status.name == ctx.Status.InProgress.name) && (v.id !== issue.id)) {
                  resultSet = v;
                }
              });
              if (resultSet) {
                var otherIssueLink = ' ' + resultSet.id + '';
                var message = 'Switch to next open test in current Test Run' + otherIssueLink + '.';
                workflow.message(message);

                parent.fields["Status"] = (isPassing) ? ctx.Status.Passing : ctx.Status.Failing;
              } else {
                parent.fields["Status"] = (isPassing) ? ctx.Status.Passed : ctx.Status.Failed;
              }
            }
          }
        },
        'No Run': {
          guard: function(ctx) {
            var issue = ctx.issue;
            return !issue.isChanged('project') && !issue.becomesReported && issue.isReported && (issue.Type.name == ctx.Type.TestExecution.name);
          },
          targetState: 'No Run',
          action: function(ctx) {
            var issue = ctx.issue;
            if (!issue.links['subtask of'].isEmpty) {
              var parent = issue.links['subtask of'].first();
              var TestRunList = parent.links[ctx.Subtask.outward];
              var ActiveTestRun = false;
              var isPassing = true;
              TestRunList.forEach(function(v) {
                if ((v.Status.name == ctx.Status.Failed.name) && (v.id !== issue.id)) {
                  isPassing = false;
                  ActiveTestRun = true;
                } else if ((v.Status.name == ctx.Status.Passed.name) && (v.id !== issue.id)) {
                  ActiveTestRun = true;
                }
              });
              if (ActiveTestRun) {
                parent.fields["Status"] = (isPassing) ? ctx.Status.Passing : ctx.Status.Failing;
              } else parent.fields["Status"] = ctx.Status.InProgress;
            }
          }
        }
      }
    }
  },
  alternativeMachines: {
    'Test Run': {
      'No Run': {
        initial: true,
        transitions: {
          'Failing': {
            targetState: 'Failing',
            action: function(ctx) {
           /* Add actions. */
            }
          },
          'Failed': {
            targetState: 'Failed',
            action: function(ctx) {
         /* Add actions. */
            }
          },
          'Passing': {
            targetState: 'Passing',
            action: function(ctx) {
          /* Add actions. */
            }
          },
          'Passed': {
            targetState: 'Passed',
            action: function(ctx) {
           /* Add actions. */
            }
          }
        }
      },
      Failing: {
        transitions: {
          'Passing': {
            targetState: 'Passing',
            action: function(ctx) {
           /* Add actions . */
            }
          },
          'Passed': {
            targetState: 'Passed',
            action: function(ctx) {
          /* Add actions. */
            }
          },
          'Failed': {
            targetState: 'Failed',
            action: function(ctx) {
           /* Add actions. */
            }
          }
        }
      },
      Passing: {
        transitions: {
          'Failing': {
            targetState: 'Passing',
            action: function(ctx) {
              workflow.check(false, workflow.i18n('Test Run has-read-only status which is defined based on assigned tests statuses'));
            }
          },
          'Passed': {
            targetState: 'Passed',
            action: function(ctx) {
           /* Add actions. */
            }
          },
          'Failed': {
            targetState: 'Failed',
            action: function(ctx) {
           /* Add actions. */
            }
          }
        }
      },
      Failed: {
        transitions: {
          'Passing': {
            targetState: 'Passing',
            action: function(ctx) {
           /* Add actions.
 */
            }
          },
          'Passed': {
            targetState: 'Passed',
            action: function(ctx) {
           /* Add actions. */
            }
          },
          'Failing': {
            targetState: 'Failed',
            action: function(ctx) {
           /* Add actions. */
            }
          }
        }
      },
      Passed: {
        transitions: {
          'Passing': {
            targetState: 'Passing',
            action: function(ctx) {
           /* Add actions. */
            }
          },
          'Failed': {
            targetState: 'Passed',
            action: function(ctx) {
          /* Add actions. */
            },
          },
          'Failing': {
            targetState: 'Failed',
            action: function(ctx) {
           /* Add actions. */
            }
          }
        }
      }
    }
  },
  requirements: {
    Assignee: {
      type: entities.User.fieldType
    },
    Status: {
      type: entities.EnumField.fieldType,
      InProgress: {
        name: 'No Run'
      },
      Failing: {
        name: 'Failing'
      },
      Passing: {
        name: 'Passing'
      },
      Passed: {
        name: 'Passed'
      },
      Failed: {
        name: 'Failed'
      },
    },
    Type: {
      type: entities.EnumField.fieldType,
      TestRun: {
        name: "Test Run"
      },
      TestExecution: {
        name: "Test Case Execution"
      }
    },
    Subtask: {
      type: entities.IssueLinkPrototype,
      name: 'Subtask',
      inward: 'subtask of',
      outward: 'parent for'
    },
  }
});
 

Statistiques des tests

Vous pouvez également être intéressé par les statistiques clés du cycle de test. Pour les besoins de cette démonstration, nous avons intégré les indicateurs suivants :

  • Nombre total de tests affectés à un Test Run spécifique.
  • Nombre de tests avec le statut « Passed ».
  • Nombre de tests avec le statut « Failed » (Échec).


Grâce au code ci-dessous, toutes les mesures sont mises à jour en cas de modification de l’un des éléments suivants : changement de statut du test, affectation d’un nouveau test au Test Run et suppression d’un test du Test Run.

Étendre le bloc de code

  exports.calculateStatuses = function(parent) {
  var totalTR = 0;
  var totalFailed = 0;
  var totalPassed = 0;
  if (!parent.links['parent for']) {
    return;
  } else {
    parent.links['parent for'].forEach(function(tr) {
      totalTR++;
      if (tr.Status.name == 'Passed') {
        totalFailed++;
      }
      if (tr.Status.name == 'Failed') {
        totalPassed++;
      }
    });
    parent.fields['Total number of test cases'] = totalTR;
    parent.fields['Number of passed test cases'] = totalPassed;
    parent.fields['Number of failed test cases'] = totalFailed;
    return true;
  }
};
exports.resetStatuses = function(testRun, testRunCopy) {
   testRunCopy.fields['Total number of test cases'] = testRun.fields['Total number of test cases'];
   testRunCopy.fields['Number of passed test cases'] = 0;
   testRunCopy.fields['Number of failed test cases'] = 0;
  return true;
};

Dans notre cas, ce code est déclenché par plusieurs règles de workflow, telles que « Update stats when links are adjusted » (Mettre à jour les statistiques lorsque les liens sont ajustés), « Switch to the next test case » (Passer au cas de test suivant) entre autres. Si vous souhaitez adopter un ensemble de mesures qui répondent à vos besoins spécifiques, vous pouvez ajouter les champs personnalisés requis et ajuster votre logique de workflow.

Fonctionnalités de suivi de tickets

Vous pouvez créer une tâche distincte pour traiter un bug identifié pour un test échoué et l’associer à un ticket qui identifie l’exécution du cas de test correspondant. Vous pouvez soit associer les échecs de tests aux bugs correspondants en utilisant un type de lien de ticket personnalisé, soit utiliser un champ de texte personnalisé contenant une référence au bug. Il peut être plus facile d’opter pour un projet YouTrack séparé afin d’effectuer le suivi des bugs.

Améliorations possibles

Vous pouvez étendre les fonctionnalités de YouTrack pour répondre à des critères ou exigences supplémentaires. Plusieurs options sont possibles.

Cloner un Test Run existant

Il est parfois préférable d’avoir un nouveau Test Run similaire à un Test Run existant, avec seulement quelques petites modifications (par exemple, un changement de la version attribuée). Nous proposons l’option « Create Test Run copy » précisément pour de tels cas. Cet élément du menu n’est disponible que pour les tickets de type « Test Run » et déclenche les actions suivantes :

  • Création d’une copie du Test Run et de copies des exécutions de cas de tests qui lui sont attribuées.
  • Tous les copies d’exécutions de cas de tests sont affectées au nouveau Test Run en utilisant le type de lien « parent-child ». Ces copies seront attribuées aux cas de test originaux avec le type de lien de ticket « Execution » afin de maintenir la traçabilité.
  • Les statuts et statistiques ne seront pas inclus dans le nouveau ticket.

La logique ci-dessus est implémentée en utilisant la règle d’action « Create Test Run copy ».

Étendre le bloc de code

var workflow = require('@jetbrains/youtrack-scripting-api/workflow');
var entities = require('@jetbrains/youtrack-scripting-api/entities');
var utils = require('../calculate-tms-stats/utils');

exports.rule = entities.Issue.action({
  title: 'Create Test Run copy',
  command: 'Test Run Creation',
  guard: function(ctx) {
    return ctx.issue.isReported && (ctx.issue.Type.name == ctx.Type.TestRun.name);
  },
  action: function(ctx) {
    var issue = ctx.issue;
    var TestRunCopy = issue.copy(issue.project);
    TestRunCopy.Status = ctx.Status.InProgress; 
    var oldTestList = issue.links[ctx.Subtask.outward];
    oldTestList.forEach(function(v) {
      var newTest = v.copy(v.project);
      newTest.Status = ctx.Status.InProgress;
      newTest.links[ctx.Subtask.inward].delete(issue);
      newTest.links[ctx.Subtask.inward].add(TestRunCopy);
    });
    utils.resetStatuses(issue, TestRunCopy); 
    var newTestRunLink = ' ' + TestRunCopy.id + '';
    var message = 'New Test Run has been created ' + newTestRunLink + '.';
    workflow.message(message);
  },
  requirements: {
    Execution: {
      type: entities.IssueLinkPrototype,
      name: 'Execution',
      inward: 'Execution',
      outward: 'Assigned Test case or test suite'
    },
    Subtask: {
      type: entities.IssueLinkPrototype,
      name: 'Subtask',
      inward: 'subtask of',
      outward: 'parent for'
    },
    Type: {
      type: entities.EnumField.fieldType,
      TestRun: {
        name: "Test Run"
      },
    },
    Status: {
      type: entities.EnumField.fieldType,
      InProgress: {
        name: 'No Run'
      },
    }
  }
});
 

Limiter les actions des utilisateurs

Afin d’éviter les erreurs potentielles avec les scénarios de tests (par exemple en utilisant les mauvais types de tickets), vous pouvez limiter les actions des utilisateurs. Par exemple, vous pouvez implémenter des actions pour vous assurer que les utilisateurs associent les cas de test aux Test Runs. Le fait de lier un Test Run à un ticket, permet de vérifier si le type de ticket associé est « Test Case » ou « Test Suite ». S’il ne s’agit d’aucun des deux, l’action envoie alors un avertissement qui empêche l’utilisateur de poursuivre.

Le bloc de code suivant peut être ajouté au workflow « Populate Test Run » (Remplir la série de tests).

Étendre le bloc de code

 var message = ' ' + TestCase.id + '';
      workflow.check((TestCase.Type.name == ctx.Type.TestCase.name) || (TestCase.Type.name == ctx.Type.TestSuite.name), workflow.i18n('\'Test Run\' can be linked to \'Test Case\' and \'Test Suite\' only, but {0} has \'{1}\' type!', message, TestCase.Type.name));
    

Comme mentionné ci-dessus, le statut de Test Run doit être en lecture seule, car il dépend de la progression des exécutions de tests. Cela peut être réalisé de manière simple, en incluant un bloc de code qui limite les changements de statuts manuels pour les tickets de type « Test Run ».


Par exemple, la section transitions pour le type de ticket Test Run dans le workflow status-mangement peut être ajustée comme suit :

Étendre le bloc de code

   'Failing': {
            targetState: 'Failing',
            action: function(ctx) {
              workflow.check(false, workflow.i18n('Test run has read-only status which is defined based on assigned tests statuses'));
            }
          },
          ...
        }

Génération de rapports et traçabilité

Création de rapports

Une fois le cycle de tests terminé, vous voudrez peut-être analyser les résultats. La fonctionnalité de rapport dans YouTrack peut être utilisée à cette fin. Vous pouvez sélectionner le type de rapport, qui affichera les informations clés relatives à la procédure de gestion des tests. Pour les besoins de cette démonstration, nous avons ajouté deux rapports au tableau de bord :

  • Rapport de flux cumulé avec filtrage par champ « version ». Ce type de rapport d’état fait référence à une version spécifique du produit. Il présente des mesures clés, comme le nombre de tests avec les statuts « Passed », « Failed » et « No Run », sur une chronologie. Afin d’afficher les données inter-versions, vous devez créer un rapport pour chaque version. Nous vous recommandons d’ajouter tous les rapports au tableau de bord afin de pouvoir travailler avec toutes les données en un seul endroit.

    Configuration du rapport :

    ApplyingCommandForSubsystem

    Résultats du rapport :

    ApplyingCommandForSubsystem

  • Rapport sur la distribution des tickets. Ce rapport est un instantané qui comprend des mesures clés (telles que le nombre de séries de tests effectuées pour chaque statut) pour plusieurs versions du produit. Pour cette démonstration, nous avons inclus un rapport de résultats qui peut vous aider à comparer les versions entre elles et à analyser leur stabilité.

    Configuration du rapport :

    ApplyingCommandForSubsystem

    Résultats du rapport :

    ApplyingCommandForSubsystem

Traçabilité

Comme YouTrack stocke les informations clés du cycle de tests pour chaque ticket avec le type « Test Execution », vous pouvez toujours accéder à l’historique des tests et identifier les bugs associés.
Si vous avez un cas test, vous pouvez vous référer à toutes les exécutions de tests qui l’impliquent.

Avec l’exécution de cas de tests, vous pouvez vous référer à un Test Run.

Si l’exécution d’un cas de test échoue, vous pouvez toujours vous référer à la liste des bugs associés.

Pour plus de détails sur les workflows décrits dans cet article ou pour discuter d’autres sujets liés aux workflows, n’hésitez pas à rejoindre notre communauté YouTrack Workflow sur Slack.

Auteur de l’article original en anglais : Leonid Zalog

S'abonner

S'abonner aux mises à jour