3

同じインターフェイス/抽象クラスの異なる実装をテストしている2つ以上のテストクラスが共通のテストを持っているが、フィクスチャが異なる場合は、テストケースをリファクタリングすることをお勧めしますか?

コードとテストが次のようになっているとしましょう。

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のいずれかが将来機能を追加した場合、これが問題になることもわかります。どちらのクラスに何をしても、ベースに抽象化する必要があるかどうかという質問に常に直面します。テストフレームワークのクラスも同様です。

これは私の心の保守性を複雑にします。複数のクラスを調整する必要があるためではなく(テストクラスでどちらの方法でも発生します)、選択を行うたびに追跡する必要がある追加のコード構造を維持するという追加の負担のためです。どちらかのクラス。

私の考えでは、ユニットテストはこの理由から1対1の構造で適用されます。このメソッドは、クラスが同じ構造と機能を持っている限り、このテストクラスに適用できるという意味で、コードの重複を減らします。一方、私の考えでは、それはクラスをテストに適合させるという誘惑を開きます。その逆ではありません。

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

さて、同じインターフェースSumatorを共有していますが、テストする方法はまったく異なります。MultiplicatorMultiplicator

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

上記のすべては、明らかな落とし穴に加えて、読みやすさと保守性の点で不必要な複雑さを追加します。

皆さんの貢献に感謝します。

于 2012-05-04T00:19:16.143 に答える
0

テスト用の基本クラスを持つことは、主に2つの場合にのみ役立つことがわかりました。

  1. 基本クラスに、作業中のアプリケーションの一般的なユーティリティ/ヘルパーメソッド/クラス、つまり一般的なモッククラスの作成者などが含まれている場合。
  2. テスト対象の製品が他の製品と一部のコードを共有しているが、それをいくらか拡張している場合。したがって、これをテスト基本クラスとその子にミラーリングします。
于 2012-05-04T09:57:27.577 に答える