我正在尝试遵循/学习 TDD。我正在使用 PHPUnit。目前,我正在编写小型、非常简单的类/项目,只是为了将其全部纳入其中。
目前我正在编写游戏卡设置。
所以开始它,我写了这个测试,因为我会避免在我的牌花色中重复,我决定测试应该是一个抽象类,花色可以扩展。
希望当你读完这篇文章时,课程的模式是显而易见的。
BaseSuitTest.php
use Cards\French\Suits\BaseSuit as Card;
class BaseSuitTest extends PHPUnit_Framework_TestCase
{
protected $Card;
public function setUp()
{
$this->Card = $this->getMockForAbstractClass('Cards\French\Suits\BaseSuit', [10]);
}
/**
* @expectedException InvalidArgumentException
*/
public function testConstructThrowsExceptionIfFirstArgLessThan1()
{
$Card = $this->getMockForAbstractClass('Cards\French\Suits\BaseSuit', [0]);
}
/**
* @expectedException InvalidArgumentException
*/
public function testConstructThrowsExceptionIfFirstArgHigherThan13()
{
$Card = $this->getMockForAbstractClass('Cards\French\Suits\BaseSuit', [14]);
}
public function testGetSuitReturnsClassName()
{
$suit = $this->Card->getSuit();
$this->assertSame( get_class($this->Card), $suit);
}
public function testGetValueReturnsValue()
{
$value = $this->Card->getValue();
$this->assertSame(10, $value);
}
}
之后我编写了以下文件,使所有测试通过。在这一点上,我也在考虑是否应该为 BaseSuit 实现 CardInterface(我应该编写一个测试来检查实现还是这不是测试的一部分)。我决定不去。
BaseSuit.php
namespace Cards\French\Suits;
abstract class BaseSuit
{
const MIN_VALUE = 1;
const MAX_VALUE = 13;
protected $value;
public function __construct($value)
{
if( $this->isWithinValueRange($value) === false)
throw new \InvalidArgumentException('The value must be higer than 0 and less than 14 (1-13) Given ' . $value);
$this->value = $value;
}
protected function isWithinValueRange($value)
{
if($value < self::MIN_VALUE OR $value > self::MAX_VALUE)
return false;
return true;
}
public function getSuit()
{
return get_class($this);
}
public function getValue()
{
return $this->value;
}
}
最后,我开始对从基类扩展的实际类/套装进行测试。
该测试与 BaseSuit 中的测试非常相似,这已经让我在脑海中感到烦恼。
HeartTest.php
use Cards\French\Suits\Heart as Heart;
class HeartTest extends PHPUnit_Framework_TestCase
{
protected $Heart;
public function setUp()
{
$this->Heart = new Heart(10);
}
/**
* @expectedException InvalidArgumentException
*/
public function testConstructThrowsExceptionIfFirstArgLessThan1()
{
$Heart = new Heart(0);
}
/**
* @expectedException InvalidArgumentException
*/
public function testConstructThrowsExceptionIfFirstArgHigherThan13()
{
$Heart = new Heart(14);
}
public function testGetSuitReturnsClassName()
{
$suit = $this->Heart->getSuit();
$this->assertSame( get_class($this->Heart), $suit);
}
public function testGetValueReturnsValue()
{
$value = $this->Heart->getValue();
$this->assertSame(10, $value);
}
}
我再次编写代码以使测试通过。
心脏.php
namespace Cards\French\Suits;
class Heart extends BaseSuit {}
所有的测试都通过了,但一切都不是很酷。我想避免重复从 BaseSuit 延伸的套装(如 Heart)的测试。
我最初的想法是将 BaseSuit 中的方法重命名为 protected ,然后从这个测试中扩展。这是重命名的 BaseSuit 测试之一的示例。
BaseSuit.php 中的重构方法
/**
* @expectedException InvalidArgumentException
*/
protected function constructThrowsExceptionIfFirstArgLessThan1($class)
{
// .. i have not written this code, it just a figment of my imagination.
}
这是我分裂的地方。我不确定如何继续。
将 BaseSuitTest 中的测试转移到受保护的方法意味着我要么放弃当前的公共测试方法。这可以修复我制作调用受保护方法的新测试方法。
代码示例、重构建议、模式建议以及阅读本文时脑海中浮现的任何建设性内容,我都会非常感激。
有人建议我为在同一方法上运行的测试使用提供程序。聪明,我会在将来这样做,但我无法看到这如何解决我的情况。只是当它是抽象的 BaseSuit 时,我想在测试中使用 getMockForAbstract,而当它是西装时,我只是应该更像 HeartTest。我需要帮助了解如何以正确的方式实现这一点。