Php 将常见测试重构为基本测试用例
如果两个或多个测试类测试同一接口/抽象类的不同实现,它们有共同的测试,但有不同的夹具,重构测试用例是一个好主意吗 假设代码和测试如下所示:Php 将常见测试重构为基本测试用例,php,unit-testing,testing,phpunit,xunit,Php,Unit Testing,Testing,Phpunit,Xunit,如果两个或多个测试类测试同一接口/抽象类的不同实现,它们有共同的测试,但有不同的夹具,重构测试用例是一个好主意吗 假设代码和测试如下所示: interface MathOperation { public function doMath($a, $b); } class Sumator implements MathOperation { public function doMath($a, $b) { return $a + $b; } }
interface MathOperation
{
public function doMath($a, $b);
}
class Sumator implements MathOperation
{
public function doMath($a, $b)
{
return $a + $b;
}
}
class Multiplicator implements MathOperation
{
public function doMath($a, $b)
{
return $a * $b;
}
}
// tests
class SumatorTest extends PHPUnit_Framework_TestCase
{
/**
* @var Sumator
*/
protected $sumator;
public function setUp()
{
$this->sumator = new Sumator;
}
/**
* @dataProvider fixtures
*/
public function testDoMath($a, $b, $expected)
{
$result = $this->sumator->doMath($a, $b);
$this->assertEqual($expected, $result);
}
public function fixtures()
{
return array(
array(1, 1, 2);
array(2, 1, 3);
array(100, -1, 99);
);
}
}
class MultiplicatorTest extends PHPUnit_Framework_TestCase
{
/**
* @var Multiplicator
*/
protected $multiplicator;
public function setUp()
{
$this->multiplicator = new Multiplicator;
}
/**
* @dataProvider fixtures
*/
public function testDoMath($a, $b, $expected)
{
$result = $this->multiplicator->doMath($a, $b);
$this->assertEqual($expected, $result);
}
public function fixtures()
{
return array(
array(1, 1, 1);
array(2, 1, 2);
array(100, -1, -100);
);
}
}
我希望它们(测试)是这样的:
class MathOperationTestCase extends PHPUnit_Framework_TestCase
{
/**
* @var MathOperation
*/
protected $operation;
public function setUp()
{
$this->operation = $this->createImpl();
}
/**
* @return MathOperation
*/
abstract function createImpl();
/**
* @dataProvider fixtures
*/
public function testDoMath($a, $b, $expected)
{
$result = $this->operation->doMath($a, $b);
$this->assertEqual($expected, $result);
}
abstract public function fixtures();
}
class SumatorTest extends MathOperationTestCase
{
public function createImpl()
{
return new Sumator;
}
public function fixtures()
{
return array(
array(1, 1, 2);
array(2, 1, 3);
array(100, -1, 99);
);
}
}
class MultiplicatorTest extends MathOperationTestCase
{
public function createImpl()
{
return new Multiplicator;
}
public function fixtures()
{
return array(
array(1, 1, 1);
array(2, 1, 2);
array(100, -1, -100);
);
}
}
这看起来结构更好,但可能缺乏可读性。所以最后我不确定这是不是可行的做法 如果原始代码更改,测试也必须更改。记住这一点,然后您将看到哪种方式可以更轻松地处理更改。
如果您决定将来分离接口,或者类似的问题可能会帮助您做出决定。您已经抽象出phpunitest的功能,足以使其应用于多个类!酷。我还看到,如果将来Sumator或乘法器添加了功能,那么这将成为一个问题——无论您对任何一个类做了什么,您都将始终面临一个问题,即您是否应该在测试框架中将其抽象到基类 在我看来,这使可维护性变得复杂,不是因为您必须调整多个类(对于测试类,这两种情况都会发生),而是因为维护额外的代码结构增加了负担,您需要在选择任何一个类时跟踪这些代码结构
在我看来,基于这个原因,单元测试适用于一对一的结构。您的方法减少了代码重复,因为只要一个类具有相同的结构和功能,它就适用于这个测试类。另一方面,在我看来,它打开了使类适合测试的诱惑,而不是相反。经过一番考虑,我得出结论,这种方法的唯一好处是减少代码重复 提取基本测试用例只能应用于测试类的公共接口,但这些接口不能强制执行我们尝试测试的业务逻辑的相同工作流。让我们修改乘法器类来证明这一点
class Multiplicator implements MathOperation
{
private $factor; // added factor which influences result of doMath()
public function __construct($factor)
{
$this->factor = $factor;
}
public function doMath($a, $b)
{
return ($a * $b) * $factor;
}
}
现在,虽然Sumator
和Multiplicator
共享相同的接口,但测试乘法器的方式却完全不同,例如
class MultiplicatorTest extends MathOperationTestCase
{
// rest of code
public function testDoMath2($ab, $b, $factor, $expected)
{
$multiplicator = new Multiplicator($factor);
$result = $multiplicator->doMath($a, $b);
$this->assertEqual($expected, $result);
}
}
另外,我必须通过对测试类的轻微修改来保持与基本测试用例的向后兼容性,,这是一个巨大的禁忌
class Multiplicator implements MathOperation
{
// rest of code
public function __construct($factor = 1) // default value set in class
{
$this->factor = $factor;
}
}
…或通过modyfing测试本身。这使得从提取的测试用例中派生的测试重复并且毫无用处
class MultiplicatorTest extends MathOperationTestCase
{
// rest of code
public function createImpl()
{
return new Multiplicator(1); // added default value
}
}
所有这些都增加了,除了明显的缺陷,在可读性和可维护性方面不必要的复杂性
感谢大家的贡献。我发现测试基类主要只在两种情况下有用:
其中基类仅包含您正在处理的应用程序的公共实用程序/帮助程序方法/类,即公共模拟类创建者
被测产品与其他产品共享一些代码,但在某种程度上扩展了代码;因此,您可以在测试基类及其子类中镜像这一点