将常见测试重构为基本测试用例

时间:2012-05-03 12:10:07

标签: 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;
    }
}


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);
        );
    }
}

这似乎结构更好,但可能缺乏可读性。所以最后我不确定它是否可用。

4 个答案:

答案 0 :(得分:1)

如果原始代码发生变化,则必须更改测试。请记住这一点,然后您将看到哪种方式可以更轻松地处理更改。 如果您决定在将来分离界面或者这样的问题可能会帮助您做出决定,该怎么办。

答案 1 :(得分:1)

您已经抽象出了PHPUnitTest的功能,足以让它适用于多个类!凉。我也看到,如果Sumator或Multiplicator将来都添加了功能,这就成了问题 - 无论你对这两个类做什么,你都会遇到一个问题,即你是否应该把它抽象到基础在测试框架中也是如此。

这在我的脑海中使可维护性变得复杂,不是因为你必须调整多个类(这与测试类一起发生),而是因为增加了维护额外代码结构的负担,你需要随时跟踪它任何一个班级的选择。

出于这个原因,我认为单元测试适用于一对一的结构。您的方法减少了代码重复,只要类具有相同的结构和功能,它就适用于此测试类。另一方面,在我看来,它开启了使课程适合考试的诱惑,而不是相反。

答案 2 :(得分:1)

经过一番考虑后,我得出的结论是,这种方法的优点还在于减少代码重复。

提取基础测试用例只能应用于测试类的通用接口,但那些接口不能强制执行业务逻辑的相同工作流程我们尝试测试。让我们修改Multiplicator类来证明这一点。

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;
    }
}

现在,虽然SumatorMultiplicator共享相同的界面,但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;
    }
}

...或自我测试。并且这使得从提取的测试用例中得到的测试重复并且在某种程度上无用。

class MultiplicatorTest extends MathOperationTestCase
{
    // rest of code

    public function createImpl()
    {
        return new Multiplicator(1); // added default value
    }
}

除了明显的缺陷之外,上述所有内容都增加了可读性和可维护性方面的不必要的复杂性。

感谢大家的贡献。

答案 3 :(得分:0)

我发现有一个测试基类主要只在两种情况下有用:

  1. 其中基类仅包含您正在处理的应用程序的常用实用程序/帮助程序方法/类,即常见的模拟类创建者。
  2. 被测产品与其他产品共享某些代码,但在某种程度上延伸;因此,您在测试基类及其子类中对此进行镜像。