6 votes

Comment ré-exécuter uniquement les classes de test JUnit qui ont échoué en utilisant Gradle ?

Inspiré par cette tâche soignée de TestNG et cette question SO Je me suis dit que j'allais préparer quelque chose de rapide pour relancer les tests JUnit qui ont échoué depuis Gradle.

Mais après avoir cherché pendant un certain temps, je n'ai rien trouvé d'analogue qui soit aussi pratique.

J'ai trouvé ce qui suit, qui semble fonctionner assez bien et qui ajoute un <testTaskName>Rerun pour chaque tâche de type Test dans mon projet.

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

Existe-t-il un moyen plus simple de le faire à partir de Gradle ? Existe-t-il un moyen de faire en sorte que JUnit produise une liste consolidée des échecs afin que je n'aie pas à avaler les rapports XML ?

J'utilise JUnit 4.12 et Gradle 4.5.

8voto

Michael Easter Points 7482

Voici une façon de le faire. Le fichier complet sera listé à la fin, et est disponible ici .

La première partie consiste à écrire un petit fichier (appelé failures ) pour chaque test échoué :

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")
            }
        }
    }
}

Dans la deuxième partie, nous utilisons un test filter (doc ici ) pour restreindre les tests à ceux qui sont présents dans le fichier failures fichier :

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

Le dossier complet est :

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")
            }
        }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X