3

如果两个或多个测试相同接口/抽象类的不同实现的测试类具有共同的测试但具有不同的夹具,那么重构测试用例是一个好主意吗?

假设代码和测试如下所示:

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

4 回答 4

1

如果您的原始代码更改,则测试也必须更改。牢记这一点,然后您将看到哪种方式可以更轻松地处理更改。如果您决定将来分离接口或类似的问题可能会帮助您做出决定,该怎么办。

于 2012-05-03T12:23:04.997 回答
1

您已经将 PHPUnitTest 的功能抽象出来,足以使其适用于多个类!凉爽的。我还看到,如果 Sumator 或 Multiplicator 在未来添加了功能,这将成为问题——无论您对任一类做什么,您都将始终面临是否应该将其抽象到基础的问题测试框架中的类也是如此。

在我看来,这使可维护性变得复杂,不是因为您必须调整多个类(无论哪种方式都发生在测试类中),而是因为维护额外代码结构的额外负担,您在做出选择时需要跟踪任一类。

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

于 2012-05-03T13:04:41.097 回答
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
    }
}

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

感谢大家的贡献。

于 2012-05-04T00:19:16.143 回答
0

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

  1. 基类仅包含您正在处理的应用程序的常见实用程序/帮助方法/类之类的东西,即常见的模拟类创建者。
  2. 被测产品与其他产品共享一些代码,但在某种程度上对其进行了扩展;因此,您在测试基类及其子类中反映了这一点。
于 2012-05-04T09:57:27.577 回答