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

Tom*_*sky 6 junit gradle

受到这个整洁的TestNG任务的启发,以及这个SO问题,我认为我会迅速提出一些建议,以便仅重新运行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
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有没有更简单的方法可以从Gradle做到这一点?有什么方法可以让JUnit以某种方式输出失败的合并列表,这样我就不必处理XML报告了?

我正在使用JUnit 4.12和Gradle 4.5。

Mic*_*ter 8

这是一种方法。完整文件将在末尾列出,并在此处提供

第一部分是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")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在第二部分中,我们使用一个测试filter此处的文档)将测试限制为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}"
        }
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

完整文件为:

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")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)