如何使用Gradle重新运行失败的JUnit测试类?

时间:2018-02-15 20:40:37

标签: gradle junit

灵感来自this neat TestNG taskthis SO question我以为我会快速为Gradle重新运行失败的JUnit测试做点什么。

但是经过一段时间的搜索后,我找不到任何类似的东西。

我想出了以下内容,它似乎运行良好,并为我项目中<testTaskName>Rerun类型的每个任务添加了Test任务。

import static groovy.io.FileType.FILES

import java.nio.file.Files
import java.nio.file.Paths

// And add a task for each test task to rerun just the failing tests
subprojects {
    afterEvaluate { subproject ->
        // Need to store tasks in static temp collection, else new tasks will be picked up by live collection leading to StackOverflow 
        def testTasks = subproject.tasks.withType(Test)
        testTasks.each { testTask ->
            task "${testTask.name}Rerun"(type: Test) {
                group = 'Verification'
                description = "Re-run ONLY the failing tests from the previous run of ${testTask.name}."

                // Depend on anything the existing test task depended on
                dependsOn testTask.dependsOn 

                // Copy runtime setup from existing test task
                testClassesDirs = testTask.testClassesDirs
                classpath = testTask.classpath

                // Check the output directory for failing tests
                File textXMLDir = subproject.file(testTask.reports.junitXml.destination)
                logger.info("Scanning: $textXMLDir for failed tests.")

                // Find all failed classes
                Set<String> allFailedClasses = [] as Set
                if (textXMLDir.exists()) {
                    textXMLDir.eachFileRecurse(FILES) { f ->
                        // See: http://marxsoftware.blogspot.com/2015/02/determining-file-types-in-java.html
                        String fileType
                        try {
                            fileType = Files.probeContentType(f.toPath())
                        } catch (IOException e) {
                            logger.debug("Exception when probing content type of: $f.")
                            logger.debug(e)

                            // Couldn't determine this to be an XML file.  That's fine, skip this one.
                            return
                        }

                        logger.debug("Filetype of: $f is $fileType.") 

                        if (['text/xml', 'application/xml'].contains(fileType)) {
                            logger.debug("Found testsuite file: $f.")

                            def testSuite = new XmlSlurper().parse(f)
                            def failedTestCases = testSuite.testcase.findAll { testCase ->
                                testCase.children().find { it.name() == 'failure' }
                            }

                            if (!failedTestCases.isEmpty()) {
                                logger.info("Found failures in file: $f.")
                                failedTestCases.each { failedTestCase -> 
                                    def className = failedTestCase['@classname']
                                    logger.info("Failure: $className")
                                    allFailedClasses << className.toString()
                                }
                            }
                        }
                    }
                }

                if (!allFailedClasses.isEmpty()) {
                    // Re-run all tests in any class with any failures
                    allFailedClasses.each { c ->
                        def testPath = c.replaceAll('\\.', '/') + '.class'
                        include testPath
                    }

                    doFirst {
                        logger.warn('Re-running the following tests:')
                        allFailedClasses.each { c ->
                            logger.warn(c)
                        }
                    }
                }

                outputs.upToDateWhen { false } // Always attempt to re-run failing tests
                // Only re-run if there were any failing tests, else just print warning
                onlyIf { 
                    def shouldRun = !allFailedClasses.isEmpty() 
                    if (!shouldRun) {
                        logger.warn("No failed tests found for previous run of task: ${subproject.path}:${testTask.name}.")
                    }

                    return shouldRun
                }
            }
        }
    }
}

Gradle还有更简单的方法吗?有没有办法让JUnit以某种方式输出一个统一的失败列表,所以我不必啜饮XML报告?

我正在使用JUnit 4.12和Gradle 4.5。

2 个答案:

答案 0 :(得分:6)

这是一种方法。完整文件将在最后列出,并且可用here

第一部分是为每个失败的测试写一个小文件(称为failures):

test {
    // `failures` is defined elsewhere, see below
    afterTest { desc, result -> 
        if ("FAILURE" == result.resultType as String) {
            failures.withWriterAppend { 
                it.write("${desc.className},${desc.name}\n")
            }
        }
    }
}

在第二部分中,我们使用测试filter(doc here)将测试限制为failures文件中存在的任何测试:

def failures = new File("${projectDir}/failures.log")
def failedTests = [] 
if (failures.exists()) {
    failures.eachLine { line ->
        def tokens = line.split(",")
        failedTests << tokens[0] 
    }
}
failures.delete()

test {
    filter {
        failedTests.each { 
            includeTestsMatching "${it}"
        }
    }
    // ...
}

完整档案是:

apply plugin: 'java'

repositories {
    jcenter()
}

dependencies {
    testCompile('junit:junit:4.12')
}   

def failures = new File("${projectDir}/failures.log")
def failedTests = [] 
if (failures.exists()) {
    failures.eachLine { line ->
        def tokens = line.split(",")
        failedTests << tokens[0] 
    }
}
failures.delete()

test {
    filter {
        failedTests.each { 
            includeTestsMatching "${it}"
        }
    }

    afterTest { desc, result -> 
        if ("FAILURE" == result.resultType as String) {
            failures.withWriterAppend { 
                it.write("${desc.className},${desc.name}\n")
            }
        }
    }
}

答案 1 :(得分:0)

Test Retry Gradle 插件就是专为做到这一点而设计的。它将重新运行每个失败的测试一定次数,如果总体上发生太多失败,则可以选择使构建失败。

plugins {
    id 'org.gradle.test-retry' version '1.2.1'
}

test {
    retry {
        maxRetries = 3
        maxFailures = 20 // Optional attribute
    }
}