Подтвердить что ты не робот

Имя пользовательского тестового метода в отчетах TestNG

Я работаю над проектом, где мне нужно программно использовать TestNG (используя поставщиков данных). Все хорошо, за исключением того, что в отчете мы получаем имя метода @Test, который является общим для многих случаев. Нам хотелось бы получить значимое имя в отчете.

Я занимался этим исследованием и нашел 3 способа, но, к сожалению, все для меня не работают.

1) Внедрение ITest

Я нашел здесь здесь и здесь

Я устанавливаю имя, которое я хочу, как только я введу метод @Test (для всех трех способов, которые я пытался, именно так я устанавливаю имя). Это имя возвращается из getTestName(). Я заметил, что getTestName() вызывается до и после моего @Test. Первоначально он возвращает null (для обработки NullPointerException я возвращаю "вместо" ), а затем возвращает правильное значение. Но я не вижу, как это отражается в отчете

Изменить. Также попытался установить имя из @BeforeMethod, как было предложено artdanil

2 и 3

Оба основаны на решениях, приведенных в второй ссылке выше

Переопределяя setName в XmlSuite, я получаю

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

Переопределяя toString(), я вижу их в журналах (с моими комментариями), но никаких обновлений в отчете

[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**

Изменить: повторил все 3 с помощью hardcoding значение, а не устанавливал его при вводе моего тестового метода. Но те же результаты

4b9b3361

Ответ 1

У меня была та же проблема, и я обнаружил, что это помогает установить поле, хранящее имя тестового случая в методе, аннотированное с помощью @BeforeMethod, используя собственную инъекцию TestNG для предоставления имени метода и параметров тестирования. Имя теста берется из параметров теста, предоставленных DataProvider. Если ваш тестовый метод не имеет параметров, просто сообщите имя метода.

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

РЕДАКТИРОВАТЬ: Основываясь на комментариях ниже, я понял, что образец из отчета будет полезен.

Извлечь из отчета из приведенного выше кода:

<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:[email protected]]"
            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>

Обратите внимание, что значение, возвращаемое из метода getTestName(), находится в атрибуте test-instance-name, а не в атрибуте name.

Ответ 2

У меня возникла аналогичная проблема. Сначала я реализовал уже упомянутую стратегию ITest. И это часть решения, но не полностью.

TestNG по какой-то причине при создании разных отчетов вызывает вызов getName() при тестировании при построении отчета. Это нормально, если вы не используете поставщика данных для создания разных прогонов и задаете уникальное имя для каждого запуска с помощью стратегии ITest. Если вы используете поставщик данных для создания нескольких прогонов одного и того же теста и хотите, чтобы каждый запуск имел уникальное имя, возникает проблема. Поскольку стратегия ITest оставляет имя для теста как имя, заданное последним прогоном.

Поэтому мне пришлось реализовать очень обычное getName(). Предположения SOme (в моем конкретном случае):

  • Выполняются только три отчета: TestHTMLReporter, EmailableReporter, XMLSuiteResultWriter.
  • Когда когда-либо получить имя не вызывается в результате одного из предполагаемых репортеров, то вернуть текущее заданное имя в порядке.
  • Когда репортер работает, он вызывает свои вызовы getName() в порядке и только 1 раз для каждого запуска.
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;
}

При настройке имени для запуска (я сделал это в методе @BeforeMethod (alwaysRun = true), который я определил после стратегии ITest). Я добавил имя в имена тестов ArrayList. Но тогда html-отчет был неправильным. Большинство других отчетов извлекают информацию по порядку, например XMLSuiteResultWriter, но TestHTMLReporter получает имя, сначала получая все имена для неудачных тестов, а затем имена для прохождения тестов. Поэтому мне пришлось реализовать дополнительные ArrayLists: testNamesFailed и testNamesPassed и добавить им имена тестов, когда тест закончен на основе того, прошли ли они или нет.

И я буду свободно признавать, что это очень хак и очень хрупкий. В идеале TestNG добавляет тесты в коллекцию во время работы и получает имя из этой коллекции вместо исходного теста. Если у вас есть TestNG для запуска других отчетов, вам нужно будет выяснить, в каком порядке они запрашивают имена и что такое уникальная строка для поиска в трассировке стека потоков.

- Изменить 1

В качестве альтернативы используйте стратегию ITest и шаблон factory (@factory аннотации).

TestNG Использование @ factory и @DataProvider

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

Это требует незначительных изменений. Это включает создание конструктора с теми же параметрами, что и исходный метод тестирования. Метод испытания теперь не имеет параметров. Вы можете установить имя в новом конструкторе и просто вернуть его в методе getTestName. Обязательно удалите спецификацию поставщика данных из метода тестирования.

Ответ 3

Если вы хотите изменить имя в отчете HTML, это будет полный взлом. Вот как я это сделал:

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

Ответ 4

Попробуйте реализовать интерфейс org.testng.ITest, для которого требуется метод getTestName(). Таким образом, отчет правильно обрабатывает возвращаемое значение.