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.

  1. #include <cppunit/BriefTestProgressListener.h>
  2. #include <cppunit/CompilerOutputter.h>
  3. #include <cppunit/extensions/TestFactoryRegistry.h>
  4. #include <cppunit/TestResult.h>
  5. #include <cppunit/TestResultCollector.h>
  6. #include <cppunit/TestRunner.h>
  7. #include <cppunit/XmlOutputter.h>
  8. int main(int argc, char* argv[])
  9. {
  10. // Retrieve test path from command line first argument. Default to «  » which resolve
  11. // to the top level suite.
  12. std::string testPath = (argc > 1) ? std::string(argv[1]) : std::string(«  »);
  13. // Create the event manager and test controller
  14. CPPUNIT_NS::TestResult controller;
  15. // Add a listener that collects test result
  16. CPPUNIT_NS::TestResultCollector result;
  17. controller.addListener( &result );
  18. // Add a listener that print dots as test run.
  19. CPPUNIT_NS::BriefTestProgressListener progress;
  20. controller.addListener( &progress );
  21. // Add the top suite to the test runner
  22. CPPUNIT_NS::TestRunner runner;
  23. runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
  24. runner.run( controller );
  25. // Print test in a compiler compatible format.
  26. CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
  27. outputter.write();
  28. // Uncomment this for XML output
  29. std::ofstream file( « cppunit-report.xml » );
  30. CPPUNIT_NS::XmlOutputter xml( &result, file );
  31. xml.write();
  32. file.close();
  33. return result.wasSuccessful() ? 0 : 1;
  34. }

L’exécution des tests CppUnit produit un fichier dans un format XML propriétaire au framework

  1. <?xml version=« 1.0 » encoding=‘ISO-8859-1’ standalone=‘yes’ ?>
  2. <TestRun>
  3. <FailedTests>
  4. <FailedTest id=« 3 »>
  5. <Name>MathTest::testSum</Name>
  6. <FailureType>Assertion</FailureType>
  7. <Location>
  8. <File>mathTest.cpp</File>
  9. <Line>56</Line>
  10. </Location>
  11. <Message>equality assertion failed
  12. – Expected: 141496748
  13. – Actual : 16777217
  14. </Message>
  15. </FailedTest>
  16. </FailedTests>
  17. <SuccessfulTests>
  18. <Test id=« 1 »>
  19. <Name>MathTest::testConstructor</Name>
  20. </Test>
  21. <Test id=« 2 »>
  22. <Name>MathTest::testPush</Name>
  23. </Test>
  24. <Test id=« 4 »>
  25. <Name>ExpressionTest::testConstructor</Name>
  26. </Test>
  27. <Test id=« 5 »>
  28. <Name>ExpressionTest::testPush</Name>
  29. </Test>
  30. </SuccessfulTests>
  31. <Statistics>
  32. <Tests>5</Tests>
  33. <FailuresTotal>1</FailuresTotal>
  34. <Errors>0</Errors>
  35. <Failures>1</Failures>
  36. </Statistics>
  37. </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

  1. <?xml version=« 1.0 » encoding=« UTF-8 »?>
  2. <xsl:stylesheet version=« 1.0 » xmlns:xsl=« http://www.w3.org/1999/XSL/Transform »>
  3. <xsl:output method=« xml » indent=« yes » />
  4. <xsl:param name=« suitename » />
  5. <xsl:template match=« / »>
  6. <testsuite>
  7. <xsl:attribute name=« errors »>
  8. <xsl:value-of select=« TestRun/Statistics/Errors » />
  9. </xsl:attribute>
  10. <xsl:attribute name=« failures »>
  11. <xsl:value-of select=« TestRun/Statistics/Failures » />
  12. </xsl:attribute>
  13. <xsl:attribute name=« tests »>
  14. <xsl:value-of select=« TestRun/Statistics/Tests » />
  15. </xsl:attribute>
  16. <xsl:attribute name=« name »>
  17. <xsl:value-of select=« $suitename » />
  18. </xsl:attribute>
  19. <xsl:apply-templates />
  20. </testsuite>
  21. </xsl:template>
  22. <xsl:template match=« /TestRun/SuccessfulTests/Test »>
  23. <testcase>
  24. <xsl:attribute name=« classname »>
  25. <xsl:value-of select=« substring-before(Name, ‘::’) » />
  26. </xsl:attribute>
  27. <xsl:attribute< /span> name=« name »>
  28. <xsl:value-of select=« substring-after(Name, ‘::’) » />
  29. </xsl:attribute>
  30. <xsl:attribute name=« time »>0</xsl:attribute>
  31. </testcase>
  32. </xsl:template>
  33. <xsl:template match=« /TestRun/FailedTests/FailedTest »>
  34. <testcase>
  35. <xsl:attribute name=« classname »>
  36. <xsl:value-of select=« substring-before(Name, ‘::’) » />
  37. </xsl:attribute>
  38. <xsl:attribute name=« name »>
  39. <xsl:value-of select=« substring-after(Name, ‘::’) » />
  40. </xsl:attribute>
  41. <xsl:attribute name=« time »>0</xsl:attribute>
  42. <xsl:choose>
  43. <xsl:when test=« FailureType=’Error' »>
  44. <error>
  45. <xsl:attribute name=« message »>
  46. <xsl:value-of select= » normalize-space(Message) » />
  47. </xsl:attribute>
  48. <xsl:attribute name=« type »>
  49. <xsl:value-of select=« FailureType » />
  50. </xsl:attribute>
  51. <xsl:value-of select=« Message » />
  52. File:<xsl:value-of select=« Location/File » />
  53. Line:<xsl:value-of select=« Location/Line » />
  54. </error>
  55. </xsl:when>
  56. <xsl:otherwise>
  57. <failure>
  58. <xsl:attribute name=« message »>
  59. <xsl:value-of select= » normalize-space(Message) » />
  60. </xsl:attribute>
  61. <xsl:attribute name=« type »>
  62. <xsl:value-of select=« FailureType » />
  63. </xsl:attribute>
  64. <xsl:value-of select=« Message » />
  65. File:<xsl:value-of select=« Location/File » />
  66. Line:<xsl:value-of select=« Location/Line » />
  67. </failure>
  68. </xsl:otherwise>
  69. </xsl:choose>
  70. </testcase>
  71. </xsl:template>
  72. <xsl:template match=« text()|@* » />
  73. </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

  1. #!/usr/bin/python
  2. from Ft.Lib.Uri import OsPathToUri
  3. from Ft.Xml import InputSource
  4. from Ft.Xml.Xslt import Processor
  5. import os.path
  6. class XmlConverter:
  7. def __init__(self, xml, xsl):
  8. self.xmlFile = xml
  9. self.xslFile = xsl
  10. def toXML(self, htmlFile):
  11. if (os.path.exists(self.xslFile) == False):
  12. return False
  13. if (os.path.exists(self.xmlFile) == False):
  14. return False
  15. styuri = OsPathToUri(self.xslFile)
  16. srcuri = OsPathToUri(self.xmlFile)
  17. STY = InputSource.DefaultFactory.fromUri(styuri)
  18. SRC = InputSource.DefaultFactory.fromUri(srcuri)
  19. proc = Processor.Processor()
  20. proc.appendStylesheet(STY)
  21. result = proc.run(SRC)
  22. try:
  23. fileResult = open(htmlFile,« w »)
  24. fileResult.write(result)
  25. fileResult.close()
  26. except IOError:
  27. return False
  28. return True
  29. if __name__ == « __main__ »:
  30. directory = « ./ »
  31. xml = directory + « cppunit-report.xml »
  32. xsl = directory + « cppunit-to-junit.xsl »
  33. resultXML = directory + « junit-result.xml »
  34. xmlC = XmlConverter(xml,xsl)
  35. 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.

  1. createTask(‘transformer’){
  2. ant{
  3. xslt( style:‘cppunit-to-junit.xsl’,
  4. basedir:‘.’,
  5. includes:‘cppunit-report.xml’,
  6. destdir:‘out’,
  7. extension:‘.xml’){
  8. param(name:‘suitename’, expression:‘demo’)
  9. }
  10. }
  11. }

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.
CppUnit configuration
Hudson va analyser le fichier de résultat JUnit et impacter le statut du build du job Hudson.

Résultat dans Hudson

CppUnit Dashboard
CppUnit  Result
CppUnit  Detail Result

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :