如何在 PHPUnit 测试中显示底层测试方法?
我有包含大量测试的测试套件.这是一个中等大小的:
ok 4 - CommodityBasketTest::testStartsOutEmpty好的 5 - CommodityBasketTest::testCanAddACommodity好的 6 - CommodityBasketTest::testWillAddOneCommodityByDefault好的 7 - CommodityBasketTest::testCannotAddACommodityWithAnNonNumericQuantity好的 8 - CommodityBasketTest::testAddingTheSameCommodityWillIncreaseItsQuantity好的 9 - CommodityBasketTest::testMultipleCommodityCanBeAdded好的 10 - CommodityBasketTest::testTakingFromAnEmptyBasketWontWork好的 11 - CommodityBasketTest::testTakesFirstCommodityFromTheBasket好的 12 - CommodityBasketTest::testCanRetrieveASpecificCommodity好的 13 - CommodityBasketTest::testWillThrowExceptionOnMissingCommodity好的 14 - CommodityBasketTest::testReturnsZeroWorthForEmptyBaskets好的 15 - CommodityBasketTest::testReturnsProperWorthOfACommodity好的 16 - CommodityBasketTest::testWillAccuratelyReturnStatistics
我怎样才能装备 PHPUnit,以便我可以以某种方式显示正在测试的底层方法,就像我在粘贴中一样?我的输出很灵活;例如,我想知道 CommodityBasketTest::testReturnsZeroWorthForEmptyBaskets
测试 CommodityBasket::getValuation()
.
这就是我想要的:
-- CommodityBasket::__construct() --好的 4 - CommodityBasketTest::testStartsOutEmpty-- CommodityBasket::add() --好的 5 - CommodityBasketTest::testCanAddACommodity好的 6 - CommodityBasketTest::testWillAddOneCommodityByDefault好的 7 - CommodityBasketTest::testCannotAddACommodityWithAnNonNumericQuantity好的 8 - CommodityBasketTest::testAddingTheSameCommodityWillIncreaseItsQuantity好的 9 - CommodityBasketTest::testMultipleCommodityCanBeAdded-- CommodityBasket::take() --好的 10 - CommodityBasketTest::testTakingFromAnEmptyBasketWontWork好的 11 - CommodityBasketTest::testTakesFirstCommodityFromTheBasket好的 12 - CommodityBasketTest::testCanRetrieveASpecificCommodity好的 13 - CommodityBasketTest::testWillThrowExceptionOnMissingCommodity-- CommodityBasket::getValuation() --好的 14 - CommodityBasketTest::testReturnsZeroWorthForEmptyBaskets好的 15 - CommodityBasketTest::testReturnsProperWorthOfACommodity-- CommodityBasket::dumpStats() --好的 16 - CommodityBasketTest::testWillAccuratelyReturnStatistics
感谢您的建议.
解决方案我的方法是结合 @covers
标签和自定义结果打印机.p>
无论如何你都应该使用@covers 标签来生成更有意义的代码覆盖率,尤其是在更大的测试套件中,确保只有假定的测试很重要测试一个方法真正为它生成覆盖率.
我知道您的问题与报道无关,但我们会在一分钟内解决这个问题.也许只使用该注释对您来说就足够了,因为无论您是否运行所有集成测试等等,每个没有专门测试的方法都会显示 0% 的覆盖率.
<小时>一个自定义的结果监听器来收集你想要的信息
实现可以很好地调整,我只是想制作一些可以很好地展示这个概念的东西,并希望给你一些你可以适应的东西.
代码是 alpha 的,因为我只为那个问题编写了它,但它适用于当前的 phpunit,我想我粘贴了你需要的所有内容.
结果:
--- myClass ----- myClass::a --好的 - myClassTest::testAone失败 - myClassTest::testAtwoFails-- myClass::b --好的 - myClassTest::testB-- myClass::untested --!!方法未经测试!!
我希望这与您想要的输出相匹配.在下面的代码中可以很容易地更改格式.
它会为您要测试的每个班级打印这条信息(一个空的就足够了!)
如果一个测试 @covers 多个方法,它将显示在其中的每一个方法中
<小时>Test下的类
测试用例
assertSame(1, $sut->a());}/*** @覆盖一个*/公共功能 testAtwoFails() {$sut = new myClass();$this->assertSame("错误", $sut->a());}/*** @covers b*/公共功能测试B(){$sut = new myClass();$this->assertSame(2, $sut->b());}}
你需要注册测试监听的phpunit.xml!
<phpunit><听众><listener class="ResultPrinterListener" file="./ResultPrinterListener.php"></listener></听众></phpunit>
结果打印机
suites as $suite) {foreach($suite->tests() as $test) {if(!$test instanceOf PHPUnit_Framework_TestCase) {继续;}$testClass = get_class($test);$classUnderTest = substr($testClass, 0, -4);//现在只是剪切测试"/*** 创建数组结构* 数组[ClassUnderTests][methodUnderTest][arrayOfTestMethodsThatTestThatMethod]* 你至少有一个测试的类的每个方法现在都会显示在这里*/if(!isset($tests[$classUnderTest])) {如果(!class_exists($classUnderTest)){echo "
Can't find matching class '$classUnderTest' for test class $testClass!
";}$class = new ReflectionClass($classUnderTest);foreach($class->getMethods() as $method) {$tests[$classUnderTest][$method->getName()] = array();}}$annotations = $test->getAnnotations();if(!isset($annotations["method"]["covers"])) {继续;}foreach($annotations["method"]["covers"] as $functionUnderTest) {$statusLine = "";$status = $test->getStatus();if($status == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) {$statusLine .= "失败 - ";} else if($status == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED) {$statusLine .="跳过-";} else if($status == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {$statusLine .= "inc - ";} 别的 {$statusLine .= "ok - ";}$statusLine .= $testClass."::".$test->getName();$tests[$classUnderTest][$functionUnderTest][] = $statusLine;}}}foreach($tests as $classUnderTest => $methods) {echo "
--- $classUnderTest ---
";foreach($methods as $method => $testCaseStrings) {echo "-- $classUnderTest::$method --
";如果($testCaseStrings == 数组()){echo " !! 方法未经测试 !!
";继续;}foreach($testCaseStrings 作为 $testCaseString) {echo "$testCaseString
";}回声
";}}}}
I have test suites with lots of tests in them. here is a medium sized one:
ok 4 - CommodityBasketTest::testStartsOutEmpty
ok 5 - CommodityBasketTest::testCanAddACommodity
ok 6 - CommodityBasketTest::testWillAddOneCommodityByDefault
ok 7 - CommodityBasketTest::testCannotAddACommodityWithAnNonNumericQuantity
ok 8 - CommodityBasketTest::testAddingTheSameCommodityWillIncreaseItsQuantity
ok 9 - CommodityBasketTest::testMultipleCommodityCanBeAdded
ok 10 - CommodityBasketTest::testTakingFromAnEmptyBasketWontWork
ok 11 - CommodityBasketTest::testTakesFirstCommodityFromTheBasket
ok 12 - CommodityBasketTest::testCanRetrieveASpecificCommodity
ok 13 - CommodityBasketTest::testWillThrowExceptionOnMissingCommodity
ok 14 - CommodityBasketTest::testReturnsZeroWorthForEmptyBaskets
ok 15 - CommodityBasketTest::testReturnsProperWorthOfACommodity
ok 16 - CommodityBasketTest::testWillAccuratelyReturnStatistics
How can I rig PHPUnit so that I can somehow display the underlying method being tested like I have it in the paste?? I'm flexible on output; I'd just like to know that CommodityBasketTest::testReturnsZeroWorthForEmptyBaskets
tests CommodityBasket::getValuation()
, for instance.
This is what I'd like:
-- CommodityBasket::__construct() --
ok 4 - CommodityBasketTest::testStartsOutEmpty
-- CommodityBasket::add() --
ok 5 - CommodityBasketTest::testCanAddACommodity
ok 6 - CommodityBasketTest::testWillAddOneCommodityByDefault
ok 7 - CommodityBasketTest::testCannotAddACommodityWithAnNonNumericQuantity
ok 8 - CommodityBasketTest::testAddingTheSameCommodityWillIncreaseItsQuantity
ok 9 - CommodityBasketTest::testMultipleCommodityCanBeAdded
-- CommodityBasket::take() --
ok 10 - CommodityBasketTest::testTakingFromAnEmptyBasketWontWork
ok 11 - CommodityBasketTest::testTakesFirstCommodityFromTheBasket
ok 12 - CommodityBasketTest::testCanRetrieveASpecificCommodity
ok 13 - CommodityBasketTest::testWillThrowExceptionOnMissingCommodity
-- CommodityBasket::getValuation() --
ok 14 - CommodityBasketTest::testReturnsZeroWorthForEmptyBaskets
ok 15 - CommodityBasketTest::testReturnsProperWorthOfACommodity
-- CommodityBasket::dumpStats() --
ok 16 - CommodityBasketTest::testWillAccuratelyReturnStatistics
Thank you for your suggestions.
解决方案My approach would be a combination of the @covers
Tag and a custom result printer.
You should use the @covers tag anyways to generate more meaningful code coverage, especially in bigger test suites it is important to make sure that only the tests that are supposed to test a method really generate coverage for it.
I know your question isn't related to coverage but we'll get to that in a minute. And maybe just using that annotation is enough for you as every method that doesn't have a test dedicated to it will show 0% coverage no matter if you run all your integration tests and so forth.
A custom result listener to collect the information you want
The implementation can surly be tuned, i just wanted to produce something that works reasonably well to show of the concept and hopefully give you something you can adapt.
The code is alpha as I've written it only for that question but it works with the current phpunit and I think i pasted everything you need.
The result:
--- myClass ---
-- myClass::a --
ok - myClassTest::testAone
fail - myClassTest::testAtwoFails
-- myClass::b --
ok - myClassTest::testB
-- myClass::untested --
!! Method untested !!
I hope this matches your desired output. The formatting can be changed rather easily in the code below.
It prints this piece of information for every class you have a test for (an empty one is enough!)
If one tests @covers multiple methods it will show up for EVERY ONE of those methods
The class under Test
<?php
class myClass {
public function a() {
return 1;
}
public function b() {
return 2;
}
public function untested() {
return 3;
}
}
The Testcase
<?php
require_once("myClass.php");
class myClassTest extends PHPUnit_Framework_TestCase {
/**
* @covers a
*/
public function testAone() {
$sut = new myClass();
$this->assertSame(1, $sut->a());
}
/**
* @covers a
*/
public function testAtwoFails() {
$sut = new myClass();
$this->assertSame("error", $sut->a());
}
/**
* @covers b
*/
public function testB() {
$sut = new myClass();
$this->assertSame(2, $sut->b());
}
}
The phpunit.xml you need to register the test listener!
<phpunit>
<listeners>
<listener class="ResultPrinterListener" file="./ResultPrinterListener.php"></listener>
</listeners>
</phpunit>
The Result Printer
<?php
class ResultPrinterListener implements PHPUnit_Framework_TestListener {
protected $suites = array();
public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) {}
public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {}
public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {}
public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {}
public function startTest(PHPUnit_Framework_Test $test) {}
public function endTest(PHPUnit_Framework_Test $test, $time) {}
public function startTestSuite(PHPUnit_Framework_TestSuite $suite) {}
public function endTestSuite(PHPUnit_Framework_TestSuite $suite) {
$this->suites[] = $suite;
}
public function __destruct() {
$tests = array();
foreach($this->suites as $suite) {
foreach($suite->tests() as $test) {
if(!$test instanceOf PHPUnit_Framework_TestCase) {
continue;
}
$testClass = get_class($test);
$classUnderTest = substr($testClass, 0, -4); // just cutting the "Test" for now
/**
* Create an array structue
* array[ClassUnderTests][methodUnderTest][arrayOfTestMethodsThatTestThatMethod]
* Every method for a class you have at least one test for will show up here for now
*/
if(!isset($tests[$classUnderTest])) {
if(!class_exists($classUnderTest)) {
echo "
Can't find matching class '$classUnderTest' for test class $testClass!
";
}
$class = new ReflectionClass($classUnderTest);
foreach($class->getMethods() as $method) {
$tests[$classUnderTest][$method->getName()] = array();
}
}
$annotations = $test->getAnnotations();
if(!isset($annotations["method"]["covers"])) {
continue;
}
foreach($annotations["method"]["covers"] as $functionUnderTest) {
$statusLine = "";
$status = $test->getStatus();
if($status == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) {
$statusLine .= "fail - ";
} else if($status == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED) {
$statusLine .= "skip - ";
} else if($status == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
$statusLine .= "inc - ";
} else {
$statusLine .= "ok - ";
}
$statusLine .= $testClass."::".$test->getName();
$tests[$classUnderTest][$functionUnderTest][] = $statusLine;
}
}
}
foreach($tests as $classUnderTest => $methods) {
echo "
--- $classUnderTest ---
";
foreach($methods as $method => $testCaseStrings) {
echo "-- $classUnderTest::$method --
";
if($testCaseStrings == array()) {
echo " !! Method untested !!
";
continue;
}
foreach($testCaseStrings as $testCaseString) {
echo " $testCaseString
";
}
echo "
";
}
}
}
}
相关文章