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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+