Учим Gradle менять потоки в TestNG

Привет! Давно не постил какой-то технической годноты. С появлением ребенка найти время на написание заметок становится сложно. В этой заметке я расскажу, как я решал проблему параметризации TestNG xml и Gradle.

В чем состоит проблема?

Скажем, у меня есть стандартный TestNG xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="smoke.tests" thread-count="3" parallel="classes">
    <listeners>
        <listener class-name="com.tcoe.testrail.TestListener"/>
        <listener class-name="com.jira.client.JiraTestListener"/>
    </listeners>
    <test name="Smoke tests">
        <groups>
            <run>
                <include name="smoke"/>
            </run>
        </groups>

        <packages>
            <package name="com.demo.tests.*"/>
        </packages>
    </test>
</suite>

Вот в этом xml файле я хочу менять значение переменной thread-count, чтобы потом иметь возможность запускать тесты в CI с переменным количеством потоков.

Выглядит как простая задача. В мавене, к примеру, все работает прям из коробки.

В Gradle чуваки почему-то не удосужились сделать хорошо, потому приходится городить костыли, которые я покажу ниже.

Значит, если мы берем xml, то в build.gradle файле нужно написать:

test {
    useTestNG() {
        suites 'src/test/resources/smoke.xml'
    }
}

Ок, а как теперь менять переменную в файле?

Тупое, но рабочее решение:

Создаем файл темплейт smoke_template.xml

В этом файле прописываем такую штуку:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="smoke.tests" thread-count="${thread.count}" parallel="classes">
    <listeners>
        <listener class-name="com.tcoe.testrail.TestListener"/>
        <listener class-name="com.jira.client.JiraTestListener"/>
    </listeners>
    <test name="Smoke tests">
        <groups>
            <run>
                <include name="smoke"/>
            </run>
        </groups>

        <packages>
            <package name="com.demo.tests.*"/>
        </packages>
    </test>
</suite>

В нашем build.gradle создаем две таски:

task generateXml << {
    def threadNum = System.getProperty("threadCount", "2")

    def text = file(Paths.get('src/test/resources', "smoke_template.xml"))
            .text
            .replace('${thread.count}', threadNum)

    def file = file(Paths.get('src/test/resources', "smoke_ready.xml"))
    file.text = text

    print("Use " + file.text)
}

task smokeTest(type: Test, dependsOn: generateXml) {
    useTestNG() {
        suites 'src/test/resources/smoke_ready.xml'
    }
}

Здесь мы просто считали файл smoke_template.xml, реплейснули плейсхолдер и записали в новый файл, который потом используем для запуска тестов.

Элегантное решение:

Внутри блока useTestNG() можно использовать XmlBuilder():

task smokeTest(type: Test){
    useTestNG(){
        def threadCount = System.getProperty("threadCount", "2")

        suiteXmlBuilder().suite(name: 'smoke.tests', parallel: 'classes', 'thread-count': threadCount) {
            test (name : 'Smoke tests') {
                groups {
                     run{
                       'include' (name:'smoke')
                     }
                }

                packages {
                    'package' (name: 'com.demo.tests.*')
                }
            }
        }
    }
}

В этом подходе мы используем силу Gradle build файла и мощь Groovy. Все елементы здесь являются кодом, поэтому мы можем легко параметризовать любой параметр.

На этом все, теперь, когда вы будете решать подобную задачу, у вас будет пример решения.

P/S: Если вы давно читаете блог и контент вам нравится, вы можете поддержать выход новых заметок, став патроном https://www.patreon.com/automation_remarks