Столкновение ID при использовании testrail в качестве репортера для тестовых тестов - PullRequest
0 голосов
/ 16 января 2019

При публикации результатов теста из testgn на тестовую шину я сталкиваюсь с проблемой управления уникальными идентификаторами тестовых примеров.

Я изначально сохранил их в методе test, поэтому каждый метод сопоставлялся с тестовым примером testrail. Это работало, пока я не начал параметризовать методы испытаний.

Теперь предположим, что я хочу запустить один тест на селен в четырех разных браузерах и сохранить результаты отдельно. Я не могу сохранить идентификатор случая в методе теста, поскольку четыре разных результата будут возвращаться к одному и тому же тесту.

Вместо этого я решил попробовать добавить идентификатор дела в качестве параметра в XML-файл. Это работает, но только если у вас есть один метод test для каждого класса, в противном случае testXXX () и testYYY () получают одинаковый идентификатор для каждой записи в xml. Так что там тоже не повезло.

Я пытаюсь найти способ хранения идентификаторов наблюдений для каждой версии каждого тестового прогона без структурных жертв (таких как отказ от параметров или написание только одного метода теста для класса).

Пример набора ниже

<suite name="UL" parallel="tests" thread-count="1" verbose="10">
    <parameter name="env" value="REDACTED"/>
    <parameter name="recordTests" value="1"/>
    <listeners>
    </listeners>
    <test name="UL Tests firefox">
        <classes>
            <class name="tests.selenium_tests.ULTests">
                <parameter name="browser" value="firefox"/>
                <parameter name="case_id" value="1111"/>
            </class>
        </classes>
    </test>
    <test name="UL Tests chrome">
        <classes>
            <class name="tests.selenium_tests.ULTests">
                <parameter name="browser" value="chrome"/>
                <parameter name="case_id" value="1112"/>
            </class>
        </classes>
    </test>
    <test name="UL Tests safari">
        <classes>
            <class name="tests.selenium_tests.ULTests">
                <parameter name="browser" value="bs_safari"/>
            </class>
        </classes>
    </test>
    <test name="UL Tests edge">
        <classes>
            <class name="tests.selenium_tests.ULTests">
                <parameter name="browser" value="bs_edge"/>
            </class>
        </classes>
    </test>
</suite>

1 Ответ

0 голосов
/ 17 января 2019

Все зависит от того, как вы визуализируете свой TestCase ID в системе TCMS.

Если контрольный пример представляет собой тест на основе данных, то подход должен быть немного другим.

Если тестовый пример представляет собой обычный тест, то, я думаю, у вас уже есть работающее решение.

Вот один из способов сделать это. Я использую TestNG 7.0.0-beta3 (последняя выпущенная на сегодня версия)

Предположение:

  • Тестовый случай в TCMS представляет "n" итераций фактического теста и считается проходом тогда и только тогда, когда все итерации пройдены, в противном случае - это сбой.

Шаги, которым необходимо следовать:

  1. Сначала вы создаете пользовательскую аннотацию, которая фиксирует идентификатор TCMS (Testcase Management System) для конкретного теста.
  2. Вы аннотируете свои методы @Test, используя пользовательскую аннотацию, чтобы связать ее с конкретным тестовым набором TCMS.
  3. Теперь вы создаете пользовательский прослушиватель, который гарантирует, что он может различать обычные тесты и тесты, управляемые данными, и соответственно публиковать результаты. Для тестов, управляемых данными, им нужно будет отслеживать все итерации, которые до сих пор выполнялись, а затем вычислять общие результаты.

То же самое подробно описано в моем блоге здесь .

Вот пример, который показывает все это в действии:

Пользовательская аннотация выглядит следующим образом:

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({METHOD, TYPE})
public @interface Tcms {
  String id() default "";
}

Слушатель выглядит следующим образом:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;

public class TestRailReporter implements IInvokedMethodListener {
  private Map<String, Boolean> resultTracker = new ConcurrentHashMap<>();

  @Override
  public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
    String key = testResult.getInstanceName() + "." + method.getTestMethod().getMethodName();
    resultTracker.putIfAbsent(key, Boolean.TRUE);
  }

  @Override
  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
    Tcms tcms =
        method.getTestMethod().getConstructorOrMethod().getMethod().getAnnotation(Tcms.class);
    // Only report those tests to TestRail wherein our annotation is found.
    if (tcms == null) {
      return;
    }
    if (method.getTestMethod().isDataDriven()) {
      // For data driven tests we need a different logic
      String key = testResult.getInstanceName() + "." + method.getTestMethod().getMethodName();
      if (method.getTestMethod().hasMoreInvocation()) {
        Boolean result = resultTracker.get(key);
        result = result && (testResult.getStatus() == ITestResult.SUCCESS);
        resultTracker.put(key, result);
        return;
      }
      postResultsToTestRail(tcms, resultTracker.get(key));
    } else {
      postResultsToTestRail(tcms, testResult.getStatus() == ITestResult.SUCCESS);
    }
  }

  private void postResultsToTestRail(Tcms tcms, boolean pass) {
    String testCaseId = tcms.id();
    // Write logic here that takes care of posting results to the TCMS system
    System.err.println("Test case Id [" + testCaseId + "] passed ? " + pass);
  }
}

Пример теста:

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestRailReporter.class)
public class SampleTestCase {

  @Test
  @Tcms(id = "TESTRAIL-1")
  public void testMethod() {
    Assert.assertTrue(true);
  }

  @Test(dataProvider = "dp")
  @Tcms(id = "TESTRAIL-2")
  public void dataDrivenTestWithSomeFailures(int i) {
    if (i % 2 == 0) {
      Assert.fail("simulating a failure");
    }
  }

  @Test(dataProvider = "dp")
  @Tcms(id = "TESTRAIL-3")
  public void dataDrivenTestWithNoFailures(int i) {
    Assert.assertTrue(i >= 0);
  }

  @DataProvider(name = "dp")
  public Object[][] getData() {
    return new Object[][] {{1}, {2}, {3}};
  }
}

Выход:

Test case Id [TESTRAIL-3] passed ? true
Test case Id [TESTRAIL-2] passed ? false


java.lang.AssertionError: simulating a failure

    at org.testng.Assert.fail(Assert.java:97)
    at com.rationaleemotions.stackoverflow.qn54224337.SampleTestCase.dataDrivenTestWithSomeFailures(SampleTestCase.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:131)
    at org.testng.internal.TestInvoker.invokeMethod(TestInvoker.java:570)
    at org.testng.internal.TestInvoker.invokeTestMethod(TestInvoker.java:170)
    at org.testng.internal.MethodRunner.runInSequence(MethodRunner.java:46)
    at org.testng.internal.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:790)
    at org.testng.internal.TestInvoker.invokeTestMethods(TestInvoker.java:143)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:128)
    at org.testng.TestRunner.privateRun(TestRunner.java:763)
    at org.testng.TestRunner.run(TestRunner.java:594)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:398)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:392)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:355)
    at org.testng.SuiteRunner.run(SuiteRunner.java:304)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:96)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1146)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1067)
    at org.testng.TestNG.runSuites(TestNG.java:997)
    at org.testng.TestNG.run(TestNG.java:965)
    at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:73)
    at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:123)

Test case Id [TESTRAIL-1] passed ? true

===============================================
Default Suite
Total tests run: 7, Passes: 6, Failures: 1, Skips: 0
===============================================

Редактировать: Основываясь на комментариях от OP, вот другой способ сделать это.

Подход 2

Используемая аннотация:

import static java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({METHOD})
public @interface Tcms {
  String id() default "";
}

Аргумент, который будет передан методу тестирования поставщиком данных для тестов, управляемых данными, будет выглядеть следующим образом:

import java.lang.annotation.Annotation;

public class TestData implements Tcms {

  private String tcmsId;
  private String data;

  public TestData(String tcmsId, String data) {
    this.tcmsId = tcmsId;
    this.data = data;
  }

  @Override
  public String id() {
    return tcmsId;
  }

  public String getData() {
    return data;
  }

  @Override
  public Class<? extends Annotation> annotationType() {
    return Tcms.class;
  }

  @Override
  public String toString() {
    return getData();
  }
}

Слушатель выглядит следующим образом:

import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;

public class TestRailReporter2 implements IInvokedMethodListener {

  @Override
  public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
    if (method.getTestMethod().isDataDriven()) {
      //Data driven tests need to be handled differently
      Object[] parameters = testResult.getParameters();
      if (parameters.length != 1) {
        //If theres more than one parameter, then dont do anything.
        return;
      }
      Object parameter = parameters[0];
      if (!(parameter instanceof Tcms)) {
        //If the parameter doesnt implement our interface dont do anything
        return;
      }
      postResultsToTestRail(
          (Tcms) parameter, testResult.getStatus() == ITestResult.SUCCESS, parameter.toString());
    } else {
      Tcms tcms =
          method.getTestMethod().getConstructorOrMethod().getMethod().getAnnotation(Tcms.class);
      if (tcms == null) {
        return;
      }
      postResultsToTestRail(tcms, testResult.getStatus() == ITestResult.SUCCESS);
    }
  }

  private void postResultsToTestRail(Tcms tcms, boolean pass) {
    String testCaseId = tcms.id();
    // Write logic here that takes care of posting results to the TCMS system
    System.err.println("Test case Id [" + testCaseId + "] passed ? " + pass);
  }

  private void postResultsToTestRail(Tcms tcms, boolean pass, String param) {
    String id = tcms.id();
    // Write logic here that takes care of posting results to the TCMS system
    System.err.println("Test case Id [" + id + "] with parameter [" + param + "] passed ? " + pass);
  }
}

Тестовый класс выглядит следующим образом:

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

@Listeners(TestRailReporter2.class)
public class AnotherSampleTestCase {

  @Test
  @Tcms(id = "TESTRAIL-1")
  public void simpleTestMethod() {
    Assert.assertTrue(true);
  }

  @Test(dataProvider = "dp")
  public void dataDrivenTestMethod(TestData data) {
    Assert.assertFalse(data.getData().trim().isEmpty());
  }

  @DataProvider(name = "dp")
  public Object[][] getData() {
    return new Object[][] {
      {new TestData("TESTRAIL-2", "Jack")},
      {new TestData("TESTRAIL-3", "")},
      {new TestData("TESTRAIL-4", "Daniels")}
    };
  }
}

Вот результат выполнения:

Test case Id [TESTRAIL-2] with parameter [Jack] passed ? true
Test case Id [TESTRAIL-3] with parameter [] passed ? false

java.lang.AssertionError: did not expect to find [false] but found [true]

    at org.testng.Assert.fail(Assert.java:97)
    at org.testng.Assert.failNotEquals(Assert.java:969)
    at org.testng.Assert.assertFalse(Assert.java:65)
    at org.testng.Assert.assertFalse(Assert.java:75)
    at com.rationaleemotions.stackoverflow.qn54224337.AnotherSampleTestCase.dataDrivenTestMethod(AnotherSampleTestCase.java:19)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:131)
    at org.testng.internal.TestInvoker.invokeMethod(TestInvoker.java:570)
    at org.testng.internal.TestInvoker.invokeTestMethod(TestInvoker.java:170)
    at org.testng.internal.MethodRunner.runInSequence(MethodRunner.java:46)
    at org.testng.internal.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:790)
    at org.testng.internal.TestInvoker.invokeTestMethods(TestInvoker.java:143)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:128)
    at org.testng.TestRunner.privateRun(TestRunner.java:763)
    at org.testng.TestRunner.run(TestRunner.java:594)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:398)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:392)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:355)
    at org.testng.SuiteRunner.run(SuiteRunner.java:304)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:96)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1146)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1067)
    at org.testng.TestNG.runSuites(TestNG.java:997)
    at org.testng.TestNG.run(TestNG.java:965)
    at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:73)
    at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:123)

Test case Id [TESTRAIL-4] with parameter [Daniels] passed ? true
Test case Id [TESTRAIL-1] passed ? true

===============================================
Default Suite
Total tests run: 4, Passes: 3, Failures: 1, Skips: 0
===============================================


Process finished with exit code 0
...