PHPUnit &&順序付けられたテスト

すべてのプログラマーは怠け者です。 そして、誰もが追加のコードを書くのではなく、すでに用意されているものを使用したいのです。 さらに、これは良い習慣です。



そのため、コピー&ペーストではなく、いくつかのテストを実行したいという問題がありました。 しかし、後続の各テストは前のテストのデータなどに依存していました...その結果、テストの厳密なシーケンスと依存関係に対応する機能が必要でした。 どのような解決策、カットの下を見て...





前提条件


任意のアクションを実行できるシステムのファサードがあります。 依存アクションがありますが、独立アクションがあります。 依存するアクションには、独立したアクション(および複数のアクション)が必要です。 したがって、最終的に、いわゆるアクションの順序付きリストを取得する必要があります。



呼び出されるすべてのアクションをテストする必要があるため、それに応じて順序付けられたテストのリストを取得する必要があります。 PHPUnitは、特定のタイプのアクションに対して条件が十分であったため、テストフレームワークとして使用されました。



状態


だから、今私は明らかにトマトを投げて、PHPUnitに依存関係があると言いたいです。 そして、本当にそうです。 しかし、著者自身が書いたように、依存関係では厳密なテスト実行順序を指定できません。

PHPUnitは、テストメソッド間の明示的な依存関係の宣言をサポートしています。 このような依存関係は、テストメソッドの実行順序を定義しませんが、プロデューサーがテストフィクスチャのインスタンスを返し、依存するコンシューマーに渡すことを許可します。


さらに、後で見つけたように、子孫クラスの1つのテストメソッドが親クラスのテストメソッドに依存することを示すため、これはテストを無視する必要があることをすぐに示すのと同じです。

例:

Class ParentTestCase extends PHPUnit_Framework_TestCase { public function testOne() { self::assertTrue(true); } /** * @depends testOne */ public function testTwo() { self::assertTrue(true); } }
      
      





 class ChildTestCase extends ParentTestCase { /** * @depends testTwo */ public function testThree() { self::assertTrue(true); } }
      
      





子孫クラスから最初のメソッドが収集され、次に祖先クラスから収集されるため、これはリフレクションの使用が原因で発生することを指摘します。



この点で、テストのシーケンスをサポートし、このシーケンスの基礎がテストの実行への依存を示すことにあるように、PHPUnitを変更するというアイデアを思いつきました。



解決策


最初に、テストの依存関係の順序を保存する場所を決定しましょう。 これを行うには、 TestCaseの子孫を作成し、この順序を制御する関数を追加します。

 class MagicTestCase extends PHPUnit_Framework_TestCase { /** * @var array */ protected $order = array(); /** * Sets the orderSet of a TestCase. * * @param array $orderSet */ public function setOrderSet(array $orderSet) { $this->order = $orderSet; } /** * Get the orderSet of a TestCase. * * @return array $order */ public function getOrderSet() { return $this->order; } }
      
      







その後、各テストの初期依存関係を指定する必要があります。 これを行うには、継承クラスのPHPUnit_Framework_TestSuiteクラスのaddTestMethodメソッドを再定義します。 依存関係をインストールします。

 $test->setOrderSet(PHPUnit_Util_Test::getDependencies($class->getName(), $name));
      
      





次に、TestSuiteのコンストラクターを追加して、設定した順序ですべてのテストをソートする必要があります。 そこで、各テストの再帰順序を定義します。

  foreach($this->tests as $test) { $test->setOrderSet( array_unique($this->getRecursiveOrderSet($test, $test->getName())) ); } usort($this->tests, array("MagicUtilTest ", "compareTestOrder"));
      
      







さらに、 addTestSuite関数はそれ自体のインスタンス( PHPUnit_Framework_TestSuite )を作成します。最初のケースでは変更されたTestSuiteコンストラクターを使用しないため、変更されたTestSuiteを作成する必要があります。 それはすべて1行に依存し、メソッド内で再定義します。

 $this->addTest(new MagicTestSuite($testClass));
      
      







結果のクラス:

 class MagicTestSuite extends PHPUnit_Framework_TestSuite { /** * Constructs a new TestSuite: * * @param mixed $theClass * @param string $name * @throws InvalidArgumentException */ public function __construct($theClass = '', $name = '') { parent::__construct($theClass, $name); foreach($this->tests as $test) { $test->setOrderSet( array_unique($this->getRecursiveOrderSet($test, $test->getName())) ); } usort($this->tests, array("MagicUtilTest ", "compareTestOrder")); } /** * @param $object * @param $methodName * @return array */ protected function getRecursiveOrderSet($object, $methodName) { $orderSet = array(); foreach($this->tests as $test) { if ($test->getName() == $methodName && get_class($object) == get_class($test)) { $testOrderSet = $test->getOrderSet(); if (!empty($testOrderSet)) { foreach($testOrderSet as $orderMethodName) { if(!in_array($orderMethodName, $orderSet)) { $orderResult = $this->getRecursiveOrderSet($test, $orderMethodName); $orderSet = array_merge($orderSet, $orderResult); } } } $orderSet = array_merge($orderSet, $testOrderSet); } } return $orderSet; } /** * @param ReflectionClass $class * @param ReflectionMethod $method */ protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method) { $name = $method->getName(); if ($this->isPublicTestMethod($method)) { $test = self::createTest($class, $name); if ($test instanceof PHPUnit_Framework_TestCase || $test instanceof PHPUnit_Framework_TestSuite_DataProvider) { $test->setDependencies( PHPUnit_Util_Test::getDependencies($class->getName(), $name) ); } $test->setOrderSet(PHPUnit_Util_Test::getDependencies($class->getName(), $name)); $this->addTest($test, PHPUnit_Util_Test::getGroups( $class->getName(), $name) ); } else if ($this->isTestMethod($method)) { $this->addTest( self::warning( sprintf( 'Test method "%s" is not public.', $name ) ) ); } /** * Adds the tests from the given class to the suite. * * @param mixed $testClass * @throws InvalidArgumentException */ public function addTestSuite($testClass) { if (is_string($testClass) && class_exists($testClass)) { $testClass = new ReflectionClass($testClass); } if (!is_object($testClass)) { throw PHPUnit_Util_InvalidArgumentHelper::factory( 1, 'class name or object' ); } if ($testClass instanceof PHPUnit_Framework_TestSuite) { $this->addTest($testClass); } else if ($testClass instanceof ReflectionClass) { $suiteMethod = FALSE; if (!$testClass->isAbstract()) { if ($testClass->hasMethod(PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME)) { $method = $testClass->getMethod( PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME ); if ($method->isStatic()) { $this->addTest( $method->invoke(NULL, $testClass->getName()) ); $suiteMethod = TRUE; } } } if (!$suiteMethod && !$testClass->isAbstract()) { $this->addTest(new MagicTestSuite($testClass)); } } else { throw new InvalidArgumentException; } } }
      
      





それに応じて、2つのテストを比較し、それらをソートする方向を示すユーティリティ関数を追加します。

 class MagicUtilTest { /** * @static * @param PHPUnit_Framework_TestCase $object1 * @param PHPUnit_Framework_TestCase $object2 * @return int */ public static function compareTestOrder(PHPUnit_Framework_TestCase $object1, PHPUnit_Framework_TestCase $object2) { if (in_array($object2->getName(), $object1->getOrderSet())) { return 1; } if (in_array($object1->getName(), $object2->getOrderSet())) { return -1; } return 0; } }
      
      







申込み


テスト実行ファイルを作成します。

 require dirname(__FILE__) . DIRECTORY_SEPARATOR.' runSuite.php'; PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT'); require_once 'PHPUnit/TextUI/Command.php'; $tests= runSuite::suite(); PHPUnit_TextUI_TestRunner::run($tests); exit;
      
      







テストを直接起動する一般的なTestSuiteクラスを作成します。 作成したものから継承します:

 require_once 'MagicTestSuite.php'; class runSuite extends MagicTestSuite { public static function suite() { $suite = new self(); $suite->addTestSuite(“ChildTestCase”); } }
      
      





さて、テストと依存関係を含むTestCases:

 require_once 'MagicTestCase.php'; class ParentTestCase extends MagicTestCase { public function testOne() { self::assertTrue(true); } /** * @depends testOne */ public function testTwo() { self::assertTrue(true); } }
      
      





 require_once 'ParentTestCase.php'; class ChildTestCase extends ParentTestCase { /** * @depends testTwo */ public function testThree() { self::assertTrue(true); } }
      
      







制限事項


現在のソリューションでは、連動する依存関係を解消することはできません。 つまり、相互に依存する2つのテストがある場合、無限ループが発生します。



もちろん、テストの依存関係のツリーを構築することは可能でした...しかし、正直なところ、テストにはクロージャーの依存関係を使用できるかどうか(人為的エラーを除く)は考えていませんでした。



All Articles