Features

Optimieren Sie Ihren Workflow — Teil 13: Unterstützung von Testverwaltungsszenarien

Read this post in other languages:

Willkommen zurück zu unserer Reihe „Optimieren Sie Ihren Workflow“! Wir wurden immer wieder von der Community aufgefordert, die YouTrack-Funktionen für QA-Teams zu thematisieren. Heute möchten wir der Frage nachgehen, wie ein Testmanagement-Szenario (TMS) in YouTrack verwaltet werden kann. In diesem Artikel zeigen wir Ihnen, wie Sie YouTrack so einrichten können, dass die Testmanagement-Funktionalität innerhalb von YouTrack verwaltet werden kann, ganz ohne Lösungen von Drittanbietern. Mit diesem Ansatz sparen Sie Lizenzkosten und vereinfachen den Testverwaltungsprozess.

Dieser Artikel richtet sich einerseits an Testverantwortliche – und andererseits an alle, die an YouTrack-Workflows interessiert sind. Wir zeigen Ihnen ein praktisches Beispiel zum Einrichten eines Testprozesses in YouTrack, mit Empfehlungen für Projekt- und Ticketeinstellungen, einer Beschreibung zum Implementieren von Testmanagement-Szenarien und weiteren Informationen.

Wir haben auch eine Reihe von einsatzfertigen Workflow-Codeblöcken zusammengestellt, mit denen Sie Testmanagementprozesse automatisieren können. Diese Codeblöcke erleichtern verschiedene Aufgaben: Testfälle einem bestimmten Testlauf zuordnen, Testläufe klonen, Systemvorschläge für den nächsten Test anzeigen usw.

Einstellungen für Testmanagement-Projekte in YouTrack

In der Regel ist es sinnvoll, Testmanagement-spezifische Tickettypen zu verwenden: Test Case (Testfall), Test Suite (Testsuite; eine Sammlung von Testfällen), Test Run (Testlauf; eine Reihe von Testfällen oder Testsuiten, die einem bestimmten Testzyklus zugewiesen sind) oder Test Case Execution (Testfallausführung; einem bestimmten Testlauf zugeordnet). Sie sollten die Tickettypen entsprechend den Anforderungen Ihres TMS-Projekts konfigurieren. Es gibt drei Arten von Ticket-Verknüpfungen, um Verbindungen zwischen Ihren Testmanagement-Aufgaben herzustellen und die Relevanz zu kennzeichnen:

  • Der Standard-Linktyp zwischen übergeordneten Aufgaben und Teilaufgaben ist für Beziehungen zwischen Testläufen und Testfallausführungen sowie Testsuiten und Testfällen geeignet.
  • Mit dem benutzerdefinierten Linktyp Execution (Ausführung) werden Beziehungen zwischen Testfällen und Testfallausführungen verwaltet.
  • Der benutzerdefinierte Linktyp Related Bug (zugehöriger Fehler) verwaltet Beziehungen zwischen fehlgeschlagenen Tests und zugeordneten Fehlern.

Die Testanforderungen können als Artikel über ein Textfeld mit den Tickets verknüpft werden.
Darüber hinaus können Sie für Ihre Testmanagement-Aufgaben benutzerdefinierte Felder einrichten, zum Beispiel Testmodus, Kategorie, Testablauf und Anwendung. Sie können für diese Felder vordefinierte Werte festlegen (z. B. einen vordefinierten Teststatus). Mit bedingten Feldern können Sie Informationen speziell für einen Tickettyp anzeigen.

Die Tickettypen „Test Case“ (Testfall) und „Test Run“ (Testlauf) verfügen über verschiedene vordefinierte Felder, die Sie an Ihre Anforderungen anpassen können.

Einrichten von Testläufen und Testausführungen

Wenn wir beispielsweise eine bestimmte Produktversion testen möchten, müssen wir einen Testlauf erstellen und diesem relevante Testsuiten und Testfälle zuweisen. Dazu gehen Sie folgendermaßen vor:

  • Erstellen Sie ein Ticket mit dem Tickettyp „Test Run“ (Testlauf).
  • Use the ‘Assigned test case and test suite’ custom link type to link to the issue.
  • Geben Sie im Popup-Fenster die Testfälle an, die Sie in den Testlauf aufnehmen möchten.

Der Workflow „Populate Test Run“ (Testlauf füllen) erspart Ihrem QS-Team Zeit und Mühe:

  • Von allen ausgewählten Testsuiten und Testfällen werden Kopien im System erstellt. Alle Tickets werden als Tickettyp „Test Case Execution“ (Testfallausführung) angelegt.
  • Der Linktyp „Execution“ (Ausführung) verbindet Testfallausführungen mit ausgewählten Testfällen.
  • Neu erstellte Tickets werden mit dem Testlauf als übergeordnetem Ticket sowie mit den zugehörigen Teilaufgaben verknüpft.

So funktioniert es im Detail:

Codeblock ausklappen

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'
      },
    },
  }
});

Pflege der Testaktivitäten

Sobald die Testläufe vollständig eingerichtet sind, kann mit dem Testen begonnen werden. Standardmäßig haben alle Tickets mit den Typen „Test Case Execution“ (Testfallausführung) und „Test Run“ (Testlauf) den Status „No Run“ (Keine Ausführung).

Zwischen Tests wechseln

Wenn Sie mit mehreren Tests arbeiten, haben Sie zwei Möglichkeiten, um von einem Test zum anderen zu wechseln:

  1. Ändern Sie in der Ticketliste den Teststatus manuell.
  2. Öffnen Sie einen Test und wechseln Sie zum nächsten nicht abgeschlossenen Test. When you want to switch tests in this way, a helpful pop-up appears that suggests the next test to run.
    The following actions can be automated:

    • Überprüfen, ob es Tests gibt, die den Status „No Run“ haben und demselben Testlauf angehören.
    • Anzeigen einer Nachricht mit der URL des nächsten verfügbaren Tests (falls vorhanden).

      ApplyingCommandForSubsystem

    Dies kann mit dem folgenden Code implementiert werden.

    Codeblock ausklappen

     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);
        }
    }
    }
    

Teststatus

Beim Wechsel von einem Test zu einem anderen können Sie entweder angeben, dass der Test bestanden wurde („Passed“), oder Sie können den Teststatus in „Failed“ ändern (falls während der Ausführung des Testfalls ein Fehler aufgetreten ist). Gemäß den YouTrack-Einstellungen, die wir ursprünglich für dieses Projekt konfiguriert haben, gibt es mehrere vordefinierte Statuswerte für einen Testlauf:

  • Failing: Mindestens einer der zugeordneten Tests hat den Status „No run“, und mindestens einer der zugeordneten Tests hat den Status „Failed“.
  • Passing: Mindestens einer der zugeordneten Tests hat den Status „No run“, und es gibt keine zugeordneten Tests mit dem Status „Failed“.
  • Failed: Es gibt keine zugeordneten Tests mit dem Status „No Run“, und mindestens einer der zugeordneten Tests hat den Status „Failed“.
  • Passed: Es gibt keine zugeordneten Tests mit dem Status „No Run“ oder „Failed“.

Mit diesen Statuswerten kann Ihr QA-Team den Testfortschritt während des gesamten Testzyklus überwachen. Zu beachten ist dabei, dass der Testlaufstatus nur von den zugehörigen Tests abhängt und nicht manuell geändert werden sollte. Die Bestimmung des Testlaufstatus wird mithilfe des Workflows Zustandsautomat nach Tickettyp implementiert. Der Codeblock für Testwechsel ist ebenfalls in den Workflow integriert.

Codeblock ausklappen


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'
    },
  }
});
 

Teststatistiken

Manchmal möchte man sich die statistischen Daten zu einem Testzyklus ansehen. In diesem Beispiel haben wir die folgenden Kennzahlen implementiert:

  • Gesamtzahl der Tests, die einem bestimmten Testlauf zugeordnet sind.
  • Anzahl der erfolgreichen Tests („Passed“).
  • Anzahl der fehlgeschlagenen Tests („Failed“).


Der folgende Code sorgt dafür, dass diese Kennzahlen bei den folgenden Änderungen aktualisiert werden: Teststatus-Änderungen, Zuordnung eines neuen Tests zum Testlauf und Entfernen eines Tests aus dem Testlauf.

Codeblock ausklappen

  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;
};

In unserem Fall wird dieser Code in mehreren Workflow-Regeln ausgelöst, z. B. „Update stats when links are adjusted“ (Statistiken aktualisieren, wenn Links angepasst werden), „Switch to the next test case “ (Zum nächsten Testfall wechseln) und andere. Wenn Sie eigene Metriken verwenden möchten, die Ihren spezifischen Anforderungen entsprechen, können Sie die erforderlichen benutzerdefinierten Felder hinzufügen und Ihre Workflow-Logik anpassen.

Issue-Tracker-Funktionen

Bei einem fehlgeschlagenen Test wollen Sie vielleicht eine separate Aufgabe erstellen und diese mit einem Ticket verknüpfen, das die zugehörige Testfallausführung identifiziert. Um einen fehlgeschlagenen Test mit den zugehörigen Bugs zu verknüpfen, können Sie entweder einen benutzerdefinierten Ticket-Linktyp verwenden oder in einem benutzerdefinierten Textfeld einen Verweis auf den Bug hinterlegen. Es ist möglicherweise einfacher, für das Bug-Tracking ein separates YouTrack-Projekt anzulegen.

Mögliche Verbesserungen

Sie können die Funktionalität von YouTrack erweitern, um besondere Geschäftsanforderungen zu erfüllen. Hier sind einige Möglichkeiten.

Vorhandenen Testlauf klonen

Manchmal kann es sehr hilfreich sein, einen vorhandenen Testlauf mit einigen kleinen Änderungen (z. B. Änderung der zugewiesenen Version) erneut auszuführen. Der Menüpunkt „Create Test Run copy“ (Testlaufkopie erstellen) ist genau für solche Fälle gedacht. Dieser Menüpunkt ist nur verfügbar, wenn das Ticket den Typ „Test Run“ (Testlauf) hat, und er löst die folgenden Aktionen aus:

  • Eine Kopie des Testlaufs sowie Kopien der diesem zugewiesenen Testfallausführungen erstellen.
  • All newly created test case execution copies are assigned to the new test run using the ‘parent-child’ issue link type. Darüber hinaus werden diese Kopien den ursprünglichen Testfällen mit dem Linktyp „Execution“ (Ausführung) zugewiesen (um die Rückverfolgbarkeit zu gewährleisten).
  • All statuses and statistics will be discarded for the newly created issue.

Die obige Logik wird mithilfe der Aktionsregel „Create Test Run copy“ (Testlaufkopie erstellen) implementiert.

Codeblock ausklappen

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'
      },
    }
  }
});
 

Benutzeraktionen einschränken

Um mögliche Fehler bei der Arbeit mit Testszenarien (z. B. Verwendung des falschen Tickettyps) zu vermeiden, können Sie die Aktionen der Benutzer einschränken. Sie können beispielsweise Aktionen implementieren, um sicherzustellen, dass Benutzer Testfälle mit Testläufen verknüpfen. When the user links a test run to an issue, this action will check whether the linked issue type is ‘Test Case’ or ‘Test Suite.’ Ist dies nicht der Fall, löst die Aktion eine Warnung aus, die den Benutzer daran hindert, fortzufahren.

Der folgende Codeblock kann zum Workflow „Populate Test Run“ (Testlauf füllen) hinzugefügt werden.

Codeblock ausklappen

 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));
    

Wie oben erwähnt sollte der Testlaufstatus nur gelesen und nicht geändert werden, da er vom Ergebnis der Testausführungen abhängt. Dies kann auf einfache Weise sichergestellt werden, indem ein Codeblock eingefügt wird, der bei Tickets vom Typ „Testlauf“ manuelle Statusänderungen verhindert.


Beispielsweise kann der Abschnitt „transitions“ des Tickettyps „Test Run“ im Workflow „status-management“ wie folgt angepasst werden:

Codeblock ausklappen

   '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'));
            }
          },
          ...
        }

Berichte und Rückverfolgung

Berichte

Wenn ein Testzyklus abgeschlossen ist, möchten Sie möglicherweise die Ergebnisse analysieren. Die YouTrack-Berichtsfunktion leistet Ihnen dabei hervorragende Dienste. You can select the report type, which will display key information related to the test management procedure. Für diese Demonstration fügen wir dem Dashboard zwei Berichte hinzu:

  • Kumulativer-Fluss-Bericht, gefiltert nach dem Feld „Version“. Dieser Statusbericht bezieht sich auf eine bestimmte Produktversion. Er bildet auf einer Zeitachse wichtige Werte wie die Anzahl der Tests mit den verschiedenen Statuswerten – Passed, Failed und No Run – ab. Um Daten für mehrere Versionen anzuzeigen, erstellen Sie einen Bericht für jede Version. Es kann zweckmäßig sein, alle Berichte zum Dashboard hinzuzufügen, um alle Daten an einem Ort zu haben.

    Einrichten des Berichts:

    ApplyingCommandForSubsystem

    Ergebnisse des Berichts:

    ApplyingCommandForSubsystem

  • Ticketverteilungsbericht. Dieser Bericht ist eine Momentaufnahme, die wichtige Kennzahlen (z. B. die Anzahl der Testläufe pro Status) für mehrere Produktversionen enthält. Anhand der Berichtsergebnisse in diesem Beispiel können Sie mehrere Versionen miteinander vergleichen und die Versionsstabilität analysieren.

    Einrichten des Berichts:

    ApplyingCommandForSubsystem

    Ergebnisse des Berichts:

    ApplyingCommandForSubsystem

Rückverfolgung

Da YouTrack für jedes Ticket vom Typ „Test Execution“ wichtige Informationen zum Testzyklus speichert, können Sie jederzeit auf den Testverlauf zugreifen und relevante Fehler identifizieren.
Wenn Sie einen Testfall haben, können Sie auf alle Testausführungen zugreifen, die sich auf diesen Testfall beziehen.

Von der Testfallausführung ausgehend können Sie auf den Testlauf zugreifen.

Bei einer fehlgeschlagenen Testfallausführung haben Sie immer Zugriff auf die Liste der zugehörigen Fehler.

Sie können jederzeit die Quellen für alle in diesem Artikel beschriebenen Workflows aus unserem Repo herunterladen. Wenn Sie sich über andere Workflows austauschen möchten, laden wir Sie herzlich ein, sich unserer YouTrack-Workflow-Community in Slack anzuschließen.