web-dev-qa-db-fra.com

Nom de méthode de test personnalisé dans les rapports TestNG

Je travaille sur un projet où j'ai besoin d'appeler TestNG par programmation (en utilisant des fournisseurs de données). Les choses vont bien sauf que dans le rapport, nous obtenons le nom de la méthode @Test, qui est générique pour gérer de nombreux cas. Ce que nous aimerions, c'est obtenir un nom significatif dans le rapport.

Je faisais des recherches à ce sujet et j'ai trouvé 3 façons, mais malheureusement, toutes échouent pour moi.

1) Implémentez ITest

J'ai trouvé à ce sujet ici et ici

Je mets le nom que je veux dès que j'entre dans la méthode @Test (Pour les 3 méthodes que j'ai essayées, c'est ainsi que je mets le nom). Ce nom est renvoyé par getTestName (). Ce que j'ai observé, c'est que getTestName () est appelé avant et après mon @Test. Initialement, il renvoie null (pour la gestion de NullPointerException, je renvoie "" au lieu de null) et plus tard, il renvoie la valeur correcte. Mais je ne vois pas cela se refléter dans le rapport

Modifier : également essayé de définir le nom de @ BeforeMethod comme suggéré par artdanil

2 et 3

Les deux sont basés sur des solutions données dans le deuxième lien ci-dessus

En remplaçant setName dans XmlSuite, je reçois

Exception in thread "main" Java.lang.AssertionError: l should not be null
        at org.testng.ClassMethodMap.removeAndCheckIfLast(ClassMethodMap.Java:58)
        at org.testng.internal.TestMethodWorker.invokeAfterClassMethods(TestMethodWorker.Java:208)
        at org.testng.internal.TestMethodWorker.run(TestMethodWorker.Java:114)
        at org.testng.TestRunner.privateRun(TestRunner.Java:767)
        ...

En remplaçant toString (), je les vois dans les journaux (avec mes commentaires) mais aucune mise à jour dans le rapport

[2013-03-05 14:53:22,174] (Main.Java:30) - calling execute 
    [2013-03-05 14:53:22,346] GenericFunctionTest.<init>(GenericFunctionTest.Java:52) - inside constructor
    [2013-03-05 14:53:22,372] GenericFunctionTest.toString(GenericFunctionTest.Java:276) - returning **//this followed by 3 invocations before arriving at @Test method**
    [2013-03-05 14:53:22,410] GenericFunctionTest.toString(GenericFunctionTest.Java:276) - returning 
    [2013-03-05 14:53:22,416] GenericFunctionTest.toString(GenericFunctionTest.Java:276) - returning 
    [2013-03-05 14:53:22,455] GenericFunctionTest.toString(GenericFunctionTest.Java:276) - returning 
    [2013-03-05 14:53:22,892] GenericFunctionTest.<init>(GenericFunctionTest.Java:52) - inside constructor 
    [2013-03-05 14:53:23,178] GenericFunctionTest.toString(GenericFunctionTest.Java:276) - returning **//again blank as i havent set it yet**
    [2013-03-05 14:53:23,182] GenericFunctionTest.getResult(GenericFunctionTest.Java:69) - inside with test case:TestCase{signature=Signature{...}}**//I am setting it immedietely after this**
    [2013-03-05 14:53:23,293] GenericFunctionTest.toString(GenericFunctionTest.Java:276) - returning MyMethodName **//What i want**
    [2013-03-05 14:53:23,299] GenericFunctionTest.toString(GenericFunctionTest.Java:276) - returning MyMethodName **// again**

Edit: réessayé tous les 3 en codant en dur une valeur plutôt que de la définir à l'entrée de ma méthode de test. Mais mêmes résultats

24
rajesh

J'ai eu le même problème et j'ai trouvé qu'il était utile de définir le champ stockant le nom du scénario de test dans la méthode annotée avec @BeforeMethod, En utilisant injection native de TestNG pour fournir le nom de la méthode et les paramètres de test . Le nom du test est tiré des paramètres de test fournis par DataProvider. Si votre méthode de test n'a pas de paramètres, indiquez simplement le nom de la méthode.

//oversimplified for demontration purposes
public class TestParameters {
    private String testName = null;
    private String testDescription = null;

    public TestParameters(String name,
                          String description) {
        this.testName = name;
        this.testDescription = description;
    }

    public String getTestName() {
        return testName;
    }
    public String getTestDescription() {
        return testDescription;
    }
}

public class SampleTest implements ITest {
    // Has to be set to prevent NullPointerException from reporters
    protected String mTestCaseName = "";

    @DataProvider(name="BasicDataProvider")
    public Object[][] getTestData() {
        Object[][] data = new Object[][] {
                { new TestParameters("TestCase1", "Sample test 1")},
                { new TestParameters("TestCase2", "Sample test 2")},
                { new TestParameters("TestCase3", "Sample test 3")},
                { new TestParameters("TestCase4", "Sample test 4")},
                { new TestParameters("TestCase5", "Sample test 5") }
        };
        return data;
    }

    @BeforeMethod(alwaysRun = true)
    public void testData(Method method, Object[] testData) {
        String testCase = "";
        if (testData != null && testData.length > 0) {
            TestParameters testParams = null;
            //Check if test method has actually received required parameters
            for (Object testParameter : testData) {
                if (testParameter instanceof TestParameters) {
                    testParams = (TestParameters)testParameter;
                    break;
                }
            }
            if (testParams != null) {
                testCase = testParams.getTestName();
            }
        }
        this.mTestCaseName = String.format("%s(%s)", method.getName(), testCase);
    }

    @Override
    public String getTestName() {
        return this.mTestCaseName;
    }

    @Test(dataProvider="BasicDataProvider")
    public void testSample1(TestParameters testParams){
        //test code here
    }

    @Test(dataProvider="BasicDataProvider")
    public void testSample2(TestParameters testParams){
        //test code here
    }

    @Test
    public void testSample3(){
        //test code here
    }
}

EDIT: Sur la base des commentaires ci-dessous, j'ai réalisé qu'un échantillon du rapport serait utile.

Extrait du rapport à partir du code en cours d'exécution ci-dessus:

<testng-results skipped="0" failed="0" total="5" passed="5">
  <suite name="SampleTests" duration-ms="2818" started-at="<some-time>" finished-at="<some-time>">
    <test name="Test1" duration-ms="2818" started-at="<some-time>" finished-at="<some-time>">
        <test-method 
            status="PASS" 
            signature="testSample1(org.example.test.TestParameters)[pri:0, instance:org.example.test.TimeTest@c9d92c]"
            test-instance-name="testSample1(TestCase5)"
            name="testSample1" 
            duration-ms="1014"
            started-at="<some-time-before>" 
            data-provider="BasicDataProvider" 
            finished-at="<some-time-later>" >
            <!-- excluded for demonstration purposes -->
        </test-method>
        <!-- the rest of test results excluded for brevity -->
    </test>
  </suite>
</testng-result>

Notez que la valeur renvoyée par la méthode getTestName() se trouve dans l'attribut test-instance-name Et non dans l'attribut name.

15
artdanil

Veuillez trouver le code suivant pour définir le nom personnalisé du cas de test dans les rapports TestNG.

Les fonctionnalités suivantes sont disponibles dans ce code.

  • Exécution dynamique sur le même cas de test en plusieurs temps
  • Définir un nom de scénario de test personnalisé pour les rapports
  • Définir l'exécution parallèle de l'exécution de plusieurs cas de test

    import Java.lang.reflect.Field;
    import org.testng.ITest;
    import org.testng.ITestResult;
    import org.testng.Reporter;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.Factory;
    import org.testng.annotations.Test;
    import org.testng.internal.BaseTestMethod;
    import com.test.data.ServiceProcessData;
    
    public class ServiceTest implements ITest {
    
    protected ServiceProcessData serviceProcessData;
    protected String testCaseName = "";
    
    @Test
    public void executeServiceTest() {
        System.out.println(this.serviceProcessData.toString());
    }
    
    @Factory(dataProvider = "processDataList")
    public RiskServiceTest(ServiceProcessData serviceProcessData) {
        this.serviceProcessData = serviceProcessData;
    }
    
    @DataProvider(name = "processDataList", parallel = true)
    public static Object[] getProcessDataList() {
    
        Object[] serviceProcessDataList = new Object[0];
        //Set data in serviceProcessDataList
        return serviceProcessDataList;
    }
    
    @Override
    public String getTestName() {
    
        this.testCaseName = "User custom testcase name";
    
        return this.testCaseName;
    }
    
    @AfterMethod(alwaysRun = true)
    public void setResultTestName(ITestResult result) {
        try {
            BaseTestMethod baseTestMethod = (BaseTestMethod) result.getMethod();
            Field f = baseTestMethod.getClass().getSuperclass().getDeclaredField("m_methodName");
            f.setAccessible(true);
            f.set(baseTestMethod, this.testCaseName);
        } catch (Exception e) {
            ErrorMessageHelper.getInstance().setErrorMessage(e);
            Reporter.log("Exception : " + e.getMessage());
        }
    }}
    

    Merci

3
Radadiya Nikunj

Si vous souhaitez modifier le nom dans le rapport HTML, ce sera un hack total. Voici comment je l'ai fait:

public class NinjaTest {
...
...
@AfterMethod (alwaysRun = true)
public void afterMethod(ITestResult result, Method method) {
    try {
        //I have XML test suites organized in directories. 
        String xmlFile = result.getTestContext().getCurrentXmlTest().getSuite().getFileName();
        String suiteName = xmlFile.substring(xmlFile.lastIndexOf("\\") + 1, xmlFile.lastIndexOf(".xml"));
        String pathToFile = xmlFile.substring(0, xmlFile.lastIndexOf("\\") );
        String directory = pathToFile.substring(pathToFile.lastIndexOf("\\") + 1);
        String testMethodName = String.format("%s/%s - %s", directory, suiteName, method.getName());

        //Total hack to change display name in HTML report  \(^o^)/ 
        Field methodName = org.testng.internal.BaseTestMethod.class.getDeclaredField("m_methodName");
        methodName.setAccessible(true);
        methodName.set(result.getMethod(), testMethodName);
    } catch (Exception e) {
        // Eh....  ¯\_(ツ)_/¯
        e.printStackTrace();
    }
}
...
...
3

J'ai rencontré un problème similaire. J'ai d'abord mis en œuvre la stratégie ITest déjà mentionnée. Et cela fait partie de la solution, mais pas complètement.

TestNG, pour une raison quelconque, lors de la génération de différents rapports, appelle getName () sur le test lors de la génération du rapport. C'est très bien si vous n'utilisez pas de fournisseur de données pour générer des exécutions différentes et définir un nom unique pour chaque exécution en utilisant la stratégie ITest. Si vous utilisez un fournisseur de données pour générer plusieurs exécutions du même test et souhaitez que chaque exécution ait un nom unique, il y a un problème. Comme la stratégie ITest laisse le nom du test comme le nom défini lors de la dernière exécution.

J'ai donc dû implémenter un getName () très personnalisé. Quelques hypothèses (dans mon cas particulier):

  1. Seuls trois rapports sont exécutés: TestHTMLReporter, EmailableReporter, XMLSuiteResultWriter.
  2. Chaque fois que get name n'est pas appelé en raison de l'un des déclarants supposés, le retour du nom actuellement défini est correct.
  3. Lorsqu'un reporter est en cours d'exécution, il effectue ses appels getName () dans l'ordre et une seule fois pour chaque exécution.
public String getTestName()
{
    String name = testName;
    StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();//.toString();
    if(calledFrom(stackTrace, "XMLSuiteResultWriter"))
    {
        name = testNames.size()>0?testNames.get(xmlNameIndex<testNames.size()?xmlNameIndex:0):"undefined";
        xmlNameIndex++;
        if(xmlNameIndex>=testNames.size())
            xmlNameIndex = 0;
    }
    else if(calledFrom(stackTrace, "EmailableReporter"))
    {
        name = testNames.size()>0?testNames.get(emailNameIndex<testNames.size()?emailNameIndex:0):"undefined";
        emailNameIndex++;
        if(emailNameIndex>=testNames.size())
            emailNameIndex = 0;
    }
    if(calledFrom(stackTrace, "TestHTMLReporter"))
    {
        if(testNames.size()<0)
        {
            name = "undefined";
        }
        else
        {
            if(htmlNameIndex < testNamesFailed.size())
            {
                name = testNamesFailed.get(htmlNameIndex);
            }
            else
            {
                int htmlPassedIndex = htmlNameIndex - testNamesFailed.size();
                if(htmlPassedIndex < testNamesPassed.size())
                {
                    name = testNamesPassed.get(htmlPassedIndex);
                }
                else
                {
                    name = "undefined";
                }
            }
        }
        htmlNameIndex++;
        if(htmlNameIndex>=testNames.size())
            htmlNameIndex = 0;
    }
    return name;
}

private boolean calledFrom(StackTraceElement[] stackTrace, String checkForMethod)
{
    boolean calledFrom = false;
    for(StackTraceElement element : stackTrace)
    {
        String stack = element.toString();
        if(stack.contains(checkForMethod))
            calledFrom = true;
    }
    return calledFrom;
}

Lors de la définition du nom de l'exécution (je l'ai fait dans la méthode @BeforeMethod (alwaysRun = true) que j'ai définie en suivant la stratégie ITest), j'ai ajouté le nom à un ArrayList testNames. Mais le rapport html n'était pas correct. La plupart des autres rapports récupèrent les informations dans l'ordre, comme XMLSuiteResultWriter, mais TestHTMLReporter obtient le nom en obtenant d'abord tous les noms des tests ayant échoué, puis les noms des tests réussis. J'ai donc dû implémenter des ArrayLists supplémentaires: testNamesFailed et testNamesPassed et leur ajouter les noms de test une fois le test terminé en fonction de leur réussite ou non.

Et je vais admettre librement que c'est vraiment un hack et très fragile. Idéalement, TestNG ajoute les tests à une collection lors de son exécution et obtient le nom de cette collection au lieu du test d'origine. Si vous avez TestNG pour exécuter d'autres rapports, vous devrez déterminer l'ordre dans lequel ils demandent les noms et quelle chaîne suffisamment unique pour rechercher dans la trace de la pile de threads.

--Edit 1

Vous pouvez également utiliser la stratégie ITest et le modèle d'usine (annotations @factory).

TestNG utilisant @Factory et @DataProvider

http://beust.com/weblog/2004/09/27/testngs-factory/

Cela nécessite quelques modifications mineures. Cela inclut la création d'un constructeur avec les mêmes paramètres que la méthode de test d'origine. La méthode de test n'a désormais plus de paramètres. Vous pouvez définir le nom dans le nouveau constructeur et le renvoyer simplement dans la méthode getTestName. Assurez-vous de supprimer la spécification du fournisseur de données de la méthode de test.

3
pilotg2

Essayez d'implémenter l'interface org.testng.ITest qui nécessite une méthode getTestName (). De cette façon, la génération de rapports gère correctement la valeur renvoyée.

1
Stanislav Fedii