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

如何使用Gradle只重新运行失败的JUnit测试类?,gradle,junit4,Gradle,Junit4,受此启发,我想我应该迅速做些事情,重新运行Gradle的JUnit测试,但只有失败的测试 但在四处搜索了一段时间后,我找不到任何类似的东西,这也很方便 我提出了以下建议,似乎效果不错,并为我的项目中Test类型的每个任务添加了Rerun任务 import static groovy.io.FileType.FILES import java.nio.file.Files import java.nio.file.Paths // And add a task for each test ta

受此启发,我想我应该迅速做些事情,重新运行Gradle的JUnit测试,但只有失败的测试

但在四处搜索了一段时间后,我找不到任何类似的东西,这也很方便

我提出了以下建议,似乎效果不错,并为我的项目中
Test
类型的每个任务添加了
Rerun
任务

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
                }
            }
        }
    }
}
导入静态groovy.io.FileType.FILES
导入java.nio.file.Files
导入java.nio.file.path
//并为每个测试任务添加一个任务,以便仅重新运行失败的测试
子项目{
评估后{子项目->
//需要将任务存储在静态临时集合中,否则新任务将被实时集合拾取,从而导致堆栈溢出
def testTasks=子项目.tasks.withType(测试)
testTasks.each{testTask->
任务“${testTask.name}重新运行”(类型:Test){
组='验证'
description=“仅重新运行上次运行${testTask.name}时失败的测试。”
//依赖于现有测试任务所依赖的任何东西
dependsOn testTask.dependsOn
//从现有测试任务复制运行时设置
testClassesDirs=testTask.testClassesDirs
classpath=testTask.classpath
//检查输出目录中是否有失败的测试
文件textXMLDir=subproject.File(testTask.reports.junitXml.destination)
info(“扫描:$textXMLDir用于失败的测试。”)
//查找所有失败的类
将allFailedClasses=[]设置为已设置
if(textXMLDir.exists()){
textXMLDir.eachFileRecurse(文件){f->
//见:http://marxsoftware.blogspot.com/2015/02/determining-file-types-in-java.html
字符串文件类型
试一试{
fileType=Files.probeContentType(f.toPath())
}捕获(IOE异常){
debug(“探测:$f.的内容类型时出现异常”)
logger.debug(e)
//无法确定此文件是否为XML文件。可以,跳过此文件。
返回
}
debug(“文件类型:$f是$Filetype。”)
if(['text/xml','application/xml'].contains(fileType)){
debug(“找到测试套件文件:$f.”)
def testSuite=new XmlSlurper().parse(f)
def failedTestCases=testSuite.testcase.findAll{testcase->
testCase.children().find{it.name()=='failure'}
}
如果(!failedTestCases.isEmpty()){
logger.info(“在文件中发现故障:$f.”)
failedTestCases.each{failedTestCase->
def className=failedTestCase['@className']
logger.info(“失败:$className”)
所有失败类
def testPath=c.replaceAll('\\.','/')+'.class'
包含测试路径
}
首先{
logger.warn('重新运行以下测试:')
allFailedClasses.each{c->
记录器警告(c)
}
}
}
outputs.upToDateWhen{false}//始终尝试重新运行失败的测试
//只有在测试失败时才重新运行,否则只需打印警告
onlyIf{
def shouldRun=!allFailedClasses.isEmpty()
如果(!shouldlrun){
logger.warn(“未找到上次运行任务时失败的测试:${subproject.path}:${testTask.name}。”)
}
返回应该运行
}
}
}
}
}
Gradle有没有更简单的方法可以做到这一点?有没有任何方法可以让JUnit以某种方式输出一个失败的综合列表,这样我就不必重复XML报告了


我正在使用JUnit 4.12和Gradle 4.5。

这里有一种方法。完整文件将在末尾列出,并且可用

第一部分是为每个失败的测试编写一个小文件(称为
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")
            }
        }
    }
}
在第二部分中,我们使用测试
过滤器
(doc)将测试限制为
失败
文件中存在的任何测试:

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}"
        }
    }
    // ...
}
def failures=新文件(“${projectDir}/failures.log”)
def failedTests=[]
if(failures.exists()){
failures.eachLine{line->
def标记=行分割(“,”)
失败的测试
def标记=行分割(“,”)
失败的测试
如果(“失败”==result.resultType作为字符串){
失败。withWriterAppend{
写入(${desc.className},${desc.name}\n)
}
}
}
}
Gradle插件正是为了做到这一点而设计的。它将在每次失败的测试中重新运行一定次数,如果总体上发生了太多的失败,它可以选择使构建失败

插件{
id“org.gradle.test retry”版本“1.2.1”
}
试验{
重试{
maxRetries=3
maxFailures=20//可选属性
}
}

请接受最能回答您问题的答案。
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")
            }
        }
    }
}