From b862f4d756bf235b7c262743ca489da7f8970f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gildas=20Qu=C3=A9m=C3=A9ner?= Date: Fri, 31 Jan 2014 19:51:43 +0100 Subject: [PATCH] Created a simple JUnit formatter Although it does not handle advanced data, it is JUnit xsd compliant! --- features/bootstrap/PhpSpecContext.php | 19 ++ .../formatter/use_the_junit_formatter.feature | 41 +++++ spec/PhpSpec/Formatter/JUnitFormatterSpec.php | 89 +++++++++ src/PhpSpec/Formatter/JUnitFormatter.php | 173 ++++++------------ src/PhpSpec/Resources/schema/junit.xsd | 90 +++++++++ 5 files changed, 290 insertions(+), 122 deletions(-) create mode 100644 features/formatter/use_the_junit_formatter.feature create mode 100644 spec/PhpSpec/Formatter/JUnitFormatterSpec.php create mode 100644 src/PhpSpec/Resources/schema/junit.xsd diff --git a/features/bootstrap/PhpSpecContext.php b/features/bootstrap/PhpSpecContext.php index a2914b359..1e6a85bbd 100644 --- a/features/bootstrap/PhpSpecContext.php +++ b/features/bootstrap/PhpSpecContext.php @@ -44,6 +44,15 @@ public function iRunPhpspec() $this->applicationTester->run('run --no-interaction', array('decorated' => false)); } + /** + * @When /^I run phpspec using the "([^"]*)" format$/ + */ + public function iRunPhpspecUsingTheFormat($format) + { + $this->applicationTester = $this->createApplicationTester(); + $this->applicationTester->run(sprintf('run --no-interaction -f%s', $format), array('decorated' => false)); + } + /** * @When /^(?:|I )run phpspec and answer "(?P[^"]*)" when asked if I want to generate the code$/ */ @@ -98,6 +107,16 @@ public function iShouldSee($message) expect($this->applicationTester->getDisplay())->toMatch('/'.preg_quote($message, '/').'/sm'); } + /** + * @Then /^I should see valid junit output$/ + */ + public function iShouldSeeValidJunitOutput() + { + $dom = new \DOMDocument(); + $dom->loadXML($this->applicationTester->getDisplay()); + expect($dom->schemaValidate(__DIR__ . '/../../src/PhpSpec/Resources/schema/junit.xsd'))->toBe(true); + } + /** * @Then /^(?:|the )suite should pass$/ */ diff --git a/features/formatter/use_the_junit_formatter.feature b/features/formatter/use_the_junit_formatter.feature new file mode 100644 index 000000000..bdf7918c8 --- /dev/null +++ b/features/formatter/use_the_junit_formatter.feature @@ -0,0 +1,41 @@ +Feature: Use the JUnit formatter + In order to provide my CI tool with parsable phpspec results + As a developper + I need to be able to use a JUnit formatter + + Scenario: Successfully export phpspec results in JUnit format + Given the spec file "spec/Formatter/SpecExample/MarkdownSpec.php" contains: + """ + toHtml('Hi, there')->shouldReturn('

Hi, there

'); + } + } + + """ + And the class file "src/Formatter/SpecExample/Markdown.php" contains: + """ + %s

', $text); + } + } + + """ + When I run phpspec using the "junit" format + Then I should see valid junit output diff --git a/spec/PhpSpec/Formatter/JUnitFormatterSpec.php b/spec/PhpSpec/Formatter/JUnitFormatterSpec.php new file mode 100644 index 000000000..e244b9964 --- /dev/null +++ b/spec/PhpSpec/Formatter/JUnitFormatterSpec.php @@ -0,0 +1,89 @@ +beConstructedWith($presenter, $io, $stats); + } + + function it_stores_a_testcase_node_after_example_run( + ExampleEvent $event, + SpecificationNode $specification, + \ReflectionClass $class + ) { + $event->getResult()->willReturn(ExampleEvent::PASSED); + $event->getTitle()->willReturn('example title'); + + $this->afterExample($event); + + $this->getTestCaseNodes()->shouldReturn([ + '' + ]); + } + + function it_aggregates_testcase_nodes_and_store_them_after_specification_run(SpecificationEvent $event) + { + $event->getTitle()->willReturn('specification title'); + + $this->setTestCaseNodes(array( + '', + '', + '', + )); + $this->afterSpecification($event); + + $this->getTestSuiteNodes()->shouldReturn([ + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' + ]); + $this->getTestCaseNodes()->shouldHaveCount(0); + } + + function it_aggregates_testsuite_nodes_and_display_them_after_suite_run(SuiteEvent $event, $io) + { + $this->setTestSuiteNodes(array( + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '', + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' + )); + $this->afterSuite($event); + $io->write( + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' . "\n" . + '' + )->shouldBeCalled(); + } +} diff --git a/src/PhpSpec/Formatter/JUnitFormatter.php b/src/PhpSpec/Formatter/JUnitFormatter.php index 722f81d76..bff654120 100644 --- a/src/PhpSpec/Formatter/JUnitFormatter.php +++ b/src/PhpSpec/Formatter/JUnitFormatter.php @@ -18,164 +18,93 @@ use PhpSpec\Event\SpecificationEvent; /** - * @author Nick Peirson + * The JUnit Formatter + * + * @author Gildas Quemener */ class JUnitFormatter extends BasicFormatter { + /** @var array */ + protected $testCaseNodes = array(); + + /** @var array */ + protected $testSuiteNodes = array(); + /** - * @var - */ - private $xml; - /** - * @var - */ - private $testSuite; - /** - * @var - */ - private $currentGroup; - /** - * @var - */ - private $currentFile; - /** - * @var int - */ - private $suiteTime = 0; - /** - * @var int - */ - private $assertionCount = 0; - /** - * @var int - */ - private $passCount = 0; - /** - * @var int - */ - private $pendingCount = 0; - /** - * @var int - */ - private $failCount = 0; - /** - * @var int + * Set testcase nodes + * + * @param array $testCaseNodes */ - private $brokenCount = 0; + public function setTestCaseNodes(array $testCaseNodes) + { + $this->testCaseNodes = $testCaseNodes; + } /** - * @param SuiteEvent $event + * Get testcase nodes + * + * @return array */ - public function beforeSuite(SuiteEvent $event) + public function getTestCaseNodes() { - $this->xml = new \SimpleXMLElement(""); + return $this->testCaseNodes; } /** - * @param SpecificationEvent $event + * Set testsuite nodes + * + * @param array $testSuiteNodes */ - public function beforeSpecification(SpecificationEvent $event) + public function setTestSuiteNodes(array $testSuiteNodes) { - $this->currentGroup = $event->getTitle(); - $this->currentFile = $event->getSpecification()->getClassReflection()->getFileName(); - - $this->testSuite = $this->xml->addChild('testsuite'); - $this->testSuite->addAttribute('name', $this->currentGroup); - $this->testSuite->addAttribute('file', $this->currentFile); - - $this->suiteTime = 0; - $this->assertionCount = 0; - $this->passCount = 0; - $this->pendingCount = 0; - $this->failCount = 0; - $this->brokenCount = 0; + $this->testSuiteNodes = $testSuiteNodes; } /** - * @param \SimpleXMLElement $case - * @param ExampleEvent $event - * @param string $exampleTitle - * @param string $failureType - * @param string $failureString - * @param bool $backtrace + * Get testsuite nodes + * + * @return array */ - private function addFailedTestcase(\SimpleXMLElement $case, ExampleEvent $event, $exampleTitle, $failureType, $failureString, $backtrace = true) + public function getTestSuiteNodes() { - $failureMsg = PHP_EOL . $exampleTitle - . ' ('.$failureString.')' . PHP_EOL; - $failureMsg .= $event->getMessage() . PHP_EOL; - if ($backtrace) { - $failureMsg .= $event->getException()->getTraceAsString() . PHP_EOL; - } - - $error = $case->addChild($failureType, $failureMsg); - $error->addAttribute( - 'type', - get_class($event->getException()) - ); + return $this->testSuiteNodes; } /** - * @param ExampleEvent $event + * {@inheritdoc} */ public function afterExample(ExampleEvent $event) { - $title = preg_replace('/^it /', '', $event->getTitle()); - $line = $event->getExample()->getFunctionReflection()->getStartLine(); - $time = $event->getTime(); - $assertions = 0; - - $case = $this->testSuite->addChild('testcase'); - $case->addAttribute('name', $title); - $case->addAttribute('class', $this->currentGroup); - $case->addAttribute('file', $this->currentFile); - $case->addAttribute('line', $line); - $case->addAttribute('assertions', $assertions); - $case->addAttribute('time', $time); - - switch ($event->getResult()) { - case ExampleEvent::PASSED: - $this->passCount++; - break; - case ExampleEvent::PENDING: - $this->addFailedTestcase($case, $event, $title, 'failure', 'PENDING', false); - $this->pendingCount++; - break; - case ExampleEvent::FAILED: - $this->addFailedTestcase($case, $event, $title, 'failure', 'FAILED'); - $this->failCount++; - break; - case ExampleEvent::BROKEN: - $this->addFailedTestcase($case, $event, $title, 'error', 'ERROR'); - $this->brokenCount++; - break; - } - - $this->assertionCount += $assertions; - $this->suiteTime += $time; + $this->testCaseNodes[] = sprintf('', $event->getTitle()); } /** - * @param SpecificationEvent $event + * {@inheritdoc} */ public function afterSpecification(SpecificationEvent $event) { - $this->testSuite->addAttribute('tests', $event->getSpecification()->count()); - $this->testSuite->addAttribute('assertions', $this->assertionCount); - $this->testSuite->addAttribute('failures', $this->failCount); - $this->testSuite->addAttribute('errors', $this->brokenCount); - $this->testSuite->addAttribute('time', $this->suiteTime); + $this->testSuiteNodes[] = sprintf( + '' . "\n" . + '%s' . "\n" . + '', + $event->getTitle(), + count($this->testCaseNodes), + implode("\n", $this->testCaseNodes) + ); + + $this->testCaseNodes = array(); } /** - * @param SuiteEvent $event + * {@inheritdoc} */ public function afterSuite(SuiteEvent $event) { - $dom = new \DOMDocument('1.0'); - $dom->preserveWhitespace = false; - $dom->formatOutput = true; - $dom->loadXml($this->xml->asXml()); - $this->getIO()->write($dom->saveXML()); + $this->getIo()->write(sprintf( + '' . "\n" . + '%s' . "\n" . + '', + implode("\n", $this->testSuiteNodes) + )); } } diff --git a/src/PhpSpec/Resources/schema/junit.xsd b/src/PhpSpec/Resources/schema/junit.xsd new file mode 100644 index 000000000..901a7f716 --- /dev/null +++ b/src/PhpSpec/Resources/schema/junit.xsd @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +