Intégration de CppUnit dans Hudson – Part 1
De nombreux frameworks de tests existent sur le marché. Pour le langage C++, CppUnit est un framework de tests très puissant et très utilisé. L’exécution d’un test ou d’une suite de tests CppUnit ne produit pas des sorties standards comme les frameworks Java de la famille JUnit. Il se pose alors la question de son intégration au sein d’un serveur d’intégration continue comme Hudson.
Pré requis
La première étape consiste à préciser au framework CppUnit la production d’un fichier de résultat au format XML lors de l’exécution des tests.
-
#include <cppunit/BriefTestProgressListener.h>
-
#include <cppunit/CompilerOutputter.h>
-
#include <cppunit/extensions/TestFactoryRegistry.h>
-
#include <cppunit/TestResult.h>
-
#include <cppunit/TestResultCollector.h>
-
#include <cppunit/TestRunner.h>
-
#include <cppunit/XmlOutputter.h>
-
int main(int argc, char* argv[])
-
{
-
// Retrieve test path from command line first argument. Default to “” which resolve
-
// to the top level suite.
-
std::string testPath = (argc > 1) ? std::string(argv[1]) : std::string(“”);
-
// Create the event manager and test controller
-
CPPUNIT_NS::TestResult controller;
-
// Add a listener that collects test result
-
CPPUNIT_NS::TestResultCollector result;
-
controller.addListener( &result );
-
// Add a listener that print dots as test run.
-
CPPUNIT_NS::BriefTestProgressListener progress;
-
controller.addListener( &progress );
-
// Add the top suite to the test runner
-
CPPUNIT_NS::TestRunner runner;
-
runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
-
runner.run( controller );
-
// Print test in a compiler compatible format.
-
CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
-
outputter.write();
-
// Uncomment this for XML output
-
std::ofstream file( “cppunit-report.xml” );
-
CPPUNIT_NS::XmlOutputter xml( &result, file );
-
xml.write();
-
file.close();
-
return result.wasSuccessful() ? 0 : 1;
-
}
L’exécution des tests CppUnit produit un fichier dans un format XML propriétaire au framework
-
<?xml version=“1.0” encoding=‘ISO-8859-1’ standalone=‘yes’ ?>
-
<TestRun>
-
<FailedTests>
-
<FailedTest id=“3”>
-
<Name>MathTest::testSum</Name>
-
<FailureType>Assertion</FailureType>
-
<Location>
-
<File>mathTest.cpp</File>
-
<Line>56</Line>
-
</Location>
-
<Message>equality assertion failed
-
– Expected: 141496748
-
– Actual : 16777217
-
</Message>
-
</FailedTest>
-
</FailedTests>
-
<SuccessfulTests>
-
<Test id=“1”>
-
<Name>MathTest::testConstructor</Name>
-
</Test>
-
<Test id=“2”>
-
<Name>MathTest::testPush</Name>
-
</Test>
-
<Test id=“4”>
-
<Name>ExpressionTest::testConstructor</Name>
-
</Test>
-
<Test id=“5”>
-
<Name>ExpressionTest::testPush</Name>
-
</Test>
-
</SuccessfulTests>
-
<Statistics>
-
<Tests>5</Tests>
-
<FailuresTotal>1</FailuresTotal>
-
<Errors>0</Errors>
-
<Failures>1</Failures>
-
</Statistics>
-
</TestRun>
Conversion dans un format JUnit connu
La seconde étape va consister à transformer le format du fichier de sortie de CppUnit dans un format de fichier pouvant être exploité par les outils du marché comme Hudson. En effet, Hudson fournit une intégration native du résultat des tests JUnit.
Vous trouverez à cette adresse un exemple du format XSD d’un fichier JUnit.
Feuille de style de conversion
Pour obtenir ce format JUnit, une technique simple consiste à fournir une feuille de style XSL et à l’appliquer au fichier de sortie “cppunit-report.xml”. Ci-dessous, la feuille de style “cppunit-to-junit.xsl” pouvant réaliser le travail
-
<?xml version=“1.0” encoding=“UTF-8”?>
-
<xsl:stylesheet version=“1.0” xmlns:xsl=“http://www.w3.org/1999/XSL/Transform”>
-
<xsl:output method=“xml” indent=“yes” />
-
<xsl:param name=“suitename” />
-
<xsl:template match=“/”>
-
<testsuite>
-
<xsl:attribute name=“errors”>
-
<xsl:value-of select=“TestRun/Statistics/Errors” />
-
</xsl:attribute>
-
<xsl:attribute name=“failures”>
-
<xsl:value-of select=“TestRun/Statistics/Failures” />
-
</xsl:attribute>
-
<xsl:attribute name=“tests”>
-
<xsl:value-of select=“TestRun/Statistics/Tests” />
-
</xsl:attribute>
-
<xsl:attribute name=“name”>
-
<xsl:value-of select=“$suitename” />
-
</xsl:attribute>
-
<xsl:apply-templates />
-
</testsuite>
-
</xsl:template>
-
<xsl:template match=“/TestRun/SuccessfulTests/Test”>
-
<testcase>
-
<xsl:attribute name=“classname”>
-
<xsl:value-of select=“substring-before(Name, ‘::’)” />
-
</xsl:attribute>
-
<xsl:attribute< /span> name=“name”>
-
<xsl:value-of select=“substring-after(Name, ‘::’)” />
-
</xsl:attribute>
-
<xsl:attribute name=“time”>0</xsl:attribute>
-
</testcase>
-
</xsl:template>
-
<xsl:template match=“/TestRun/FailedTests/FailedTest”>
-
<testcase>
-
<xsl:attribute name=“classname”>
-
<xsl:value-of select=“substring-before(Name, ‘::’)” />
-
</xsl:attribute>
-
<xsl:attribute name=“name”>
-
<xsl:value-of select=“substring-after(Name, ‘::’)” />
-
</xsl:attribute>
-
<xsl:attribute name=“time”>0</xsl:attribute>
-
<xsl:choose>
-
<xsl:when test=“FailureType=’Error'”>
-
<error>
-
<xsl:attribute name=“message”>
-
<xsl:value-of select=” normalize-space(Message)” />
-
</xsl:attribute>
-
<xsl:attribute name=“type”>
-
<xsl:value-of select=“FailureType” />
-
</xsl:attribute>
-
<xsl:value-of select=“Message” />
-
File:<xsl:value-of select=“Location/File” />
-
Line:<xsl:value-of select=“Location/Line” />
-
</error>
-
</xsl:when>
-
<xsl:otherwise>
-
<failure>
-
<xsl:attribute name=“message”>
-
<xsl:value-of select=” normalize-space(Message)” />
-
</xsl:attribute>
-
<xsl:attribute name=“type”>
-
<xsl:value-of select=“FailureType” />
-
</xsl:attribute>
-
<xsl:value-of select=“Message” />
-
File:<xsl:value-of select=“Location/File” />
-
Line:<xsl:value-of select=“Location/Line” />
-
</failure>
-
</xsl:otherwise>
-
</xsl:choose>
-
</testcase>
-
</xsl:template>
-
<xsl:template match=“text()|@*” />
-
</xsl:stylesheet>
La feuille de style prend en entrée le paramètre “suitename” correspondant au nom de la suite de tests CppUnit. En cas d’erreur, un testcase de type “failure” est généré. Comme CppUnit ne fournit pas le temps de d’exécution d’un test, nous spécifierons dans le fichier JUnit final, un temps de “0” pour chaque cas de test.
Application de la feuille de style
Pour appliquer cette feuille de style, il nous faut un moteur de transformation. Dans l’objectif d’avoir une chaîne d’intégration complète, il est nécessaire d’incorporer cette transformation dans le script de build utilisé. Dans les environnements C++, le builder SCons est très utilisé. Avec cet outil, le script de build est écrit en Python.
Voici le script de transformation en langage Python basé sur la librairie 4suite
-
#!/usr/bin/python
-
from Ft.Lib.Uri import OsPathToUri
-
from Ft.Xml import InputSource
-
from Ft.Xml.Xslt import Processor
-
import os.path
-
class XmlConverter:
-
def __init__(self, xml, xsl):
-
self.xmlFile = xml
-
self.xslFile = xsl
-
def toXML(self, htmlFile):
-
if (os.path.exists(self.xslFile) == False):
-
return False
-
if (os.path.exists(self.xmlFile) == False):
-
return False
-
styuri = OsPathToUri(self.xslFile)
-
srcuri = OsPathToUri(self.xmlFile)
-
STY = InputSource.DefaultFactory.fromUri(styuri)
-
SRC = InputSource.DefaultFactory.fromUri(srcuri)
-
proc = Processor.Processor()
-
proc.appendStylesheet(STY)
-
result = proc.run(SRC)
-
try:
-
fileResult = open(htmlFile,“w”)
-
fileResult.write(result)
-
fileResult.close()
-
except IOError:
-
return False
-
return True
-
if __name__ == “__main__”:
-
directory = “./”
-
xml = directory + “cppunit-report.xml”
-
xsl = directory + “cppunit-to-junit.xsl”
-
resultXML = directory + “junit-result.xml”
-
xmlC = XmlConverter(xml,xsl)
-
print xmlC.toXML(resultXML)
Dans le cas d’utilisation d’un builder Java comme par exemple Gradle, il est est appréciable d’utiliser la tache XSLT de Ant pour réaliser la transformation.
-
createTask(‘transformer’){
-
ant{
-
xslt( style:‘cppunit-to-junit.xsl’,
-
basedir:‘.’,
-
includes:‘cppunit-report.xml’,
-
destdir:‘out’,
-
extension:‘.xml’){
-
param(name:‘suitename’, expression:‘demo’)
-
}
-
}
-
}
Intégration dans Hudson
Par défaut (sans plugins supplémentaires), Hudson fournit une intégration JUnit. Dans l’interface de configuration du job Hudson, il suffit de spécifier l’emplacement du fichier résultat de JUnit.
Hudson va analyser le fichier de résultat JUnit et impacter le statut du build du job Hudson.