最初は、トピック「テスト」が公式マニュアル( http://yiiframework.ru/doc/guide/ru/test.overview )から読み取られたと想定されています。
そのため、環境が設定され、今度は、カテゴリと製品(カテゴリ、製品)のモデルを作成し、それらをテストでカバーすることがタスクになります。
次のフィールドを持つカテゴリテーブルがあるとします。
- parent_id
- お名前
- 説明
- 状態
製品表:
- category_id
- お名前
- 説明
- 価格
- 状態
Giiを使用して、これらのテーブルからモデルを作成します。 これらは、カテゴリモデルと製品モデルになります。
モデルはデータベースで動作するため、CDbTestCaseからテストクラスを継承します。
カテゴリモデルのテストクラスを作成します。 内部では、テストするクラスのオブジェクトの「カテゴリ」プロパティを作成し、setUp()メソッドで割り当てを規定します。
class CategoryTest extends CDbTestCase { /** * @var Category */ protected $category; protected function setUp() { parent::setUp(); $this->category = new Category(); } }
すべてのモデルで、フィールド検証があり、それでテストを開始します。
モデルに存在する必要のある検証ルールを説明しましょう。
- タイトルは必須フィールドです
- タイトルの長さは最大150文字
- parent_idが入力されている場合、テーブルに存在する必要があります
- 説明の長さは最大4000文字
- ステータスは必須フィールドです
- ステータスには、指定されたステータスのリストの値が含まれている必要があります
したがって、「タイトルは必須フィールドです。」 TDDのガイドに従って、最初にテストを作成します。
public function testTitleIsRequired() { $this->category->title = ''; $this->assertFalse($this->category->validate(array('title'))); }
- テストの名前は、このメソッドがテスト中であることを明確に示しています。
- 内部では、タイトルを空の文字列に割り当て、検証を実行し、それがパスしなかったことを確認します。
まず、テストは赤です。 検証を記述します。
public function rules() { return array( array('title', 'required'), ); }
まず、テストは緑色です。 リファクタリングは不要なので、次のテストに進みます。
タイトルの長さは最大150文字
public function testTitleMaxLengthIs150() { $this->category->title = generateString(151); $this->assertFalse($this->category->validate(array('title'))); $this->category->title = generateString(150); $this->assertTrue($this->category->validate(array('title'))); } // generateString(), function generateString($length) { $random= ""; srand((double)microtime()*1000000); $char_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $char_list .= "abcdefghijklmnopqrstuvwxyz"; $char_list .= "1234567890"; // Add the special characters to $char_list if needed for($i = 0; $i < $length; $i++) { $random .= substr($char_list,(rand()%(strlen($char_list))), 1); } return $random; }
まず、テストは赤です。 検証を追加して、テストをグリーンにする必要があります。
public function rules() { return array( array('title', 'required'), array('title', 'length', 'max' => 150) ); }
まず、テストは緑色で、すべて問題ありません。
次に、モデルの関係の検証に進みます。 「カテゴリー」モデルでは、「親」関係の存在を意味します。
リンクテストを実装するには、フィクスチャが必要です。
目的のフォルダにフィクスチャファイルを作成します。
フィクスチャファイルの名前は、フィクスチャデータが保存されるテーブルと同じ名前にする必要があることを思い出してください。
return array( 'sample' => array( 'id' => 1, ), 'sample2' => array( 'id' => 2, 'parent_id' => 1 ) );
フィクスチャをテストに接続します。
class CategoryTest extends CDbTestCase { public $fixtures = array( 'categories' => 'Category' ); ...
接続を確認するテストを書いています。
public function testBelongsToParent() { $category = Category::model()->findByPk(2); $this->assertInstanceOf('Category', $category->parent); }
まず、テストは赤です。 モデルに関係を記述する必要があります。
public function relations() { return array( 'parent' => array(self::BELONGS_TO, __CLASS__, 'parent_id'), ); }
まず、テストは緑色です。
備品の出現により、問題が発生しました。 テストメソッドでフィクスチャが必要ない場合でも、CDbTestCaseクラスのsetUp()メソッドは、リソースを大量に消費するフィクスチャロードメソッドを呼び出します。 この問題は次のように解決できます。
class DbTestCase extends CDbTestCase { private static $_loadFixturesFlag = false; /** * Load fixtures one time */ protected function setUp() { if (!self::$_loadFixturesFlag && is_array($this->fixtures)) { $this->loadFixtures(); self::$_loadFixturesFlag = true; } } /** * Load fixtures */ public function loadFixtures($fixtures = null) { if ($fixtures === null) { $fixtures = $this->fixtures; } $this->getFixtureManager()->load($fixtures); } }
CDbTestCaseの子孫を作成し、変更しました。 これでフィクスチャは1回だけ呼び出されますが、テストの1つでフィクスチャデータを変更した場合、loadFixtures()メソッドを手動で呼び出してそれらをリロードします。
次に、「Category」クラスと「CategoryTest」クラスの最終バージョンのソースコードを示します。 製品モデルのテストの作成は宿題のままです。
class CategoryTest extends DbTestCase { /** * @var Category */ protected $category; protected function setUp() { parent::setUp(); $this->category = new Category(); } public function testAllAttributesHaveLabels() { $attributes = array_keys($this->category->attributes); foreach ($attributes as $attribute) { $this->assertArrayHasKey($attribute, $this->category->attributeLabels()); } } public function testBelongsToParent() { $category = Category::model()->findByPk(2); $this->assertInstanceOf('Category', $category->parent); } public function testTitleIsRequired() { $this->category->title = ''; $this->assertFalse($this->category->validate(array('title'))); } public function testTitleMaxLengthIs150() { $this->category->title = generateString(151); $this->assertFalse($this->category->validate(array('title'))); $this->category->title = generateString(150); $this->assertTrue($this->category->validate(array('title'))); } public function testParentIdIsExist() { $this->category->parent_id = 'not-exist-value'; $this->assertFalse($this->category->validate(array('parent_id'))); $this->category->parent_id = 1; $this->assertTrue($this->category->validate(array('parent_id'))); } public function testDescriptionMaxLengthIs4000() { $this->category->description = generateString(4001); $this->assertFalse($this->category->validate(array('description'))); $this->category->description generateString(4000); $this->assertTrue($this->category->validate(array('description'))); } public function testStatusIsRequired() { $this->category->status = ''; $this->assertFalse($this->category->validate(array('status'))); } public function testStatusExistsInStatusList() { $this->category->status = 'not-in-list-value'; $this->assertFalse($this->category->validate(array('status'))); $this->category->status = array_rand($this->category->getStatusList()); $this->assertTrue($this->category->validate(array('status'))); } public function testSafeAttributesOnSearchScenario() { $category = new Category('search'); $mustBeSafe = array('title', 'description'); $safeAttrs = $category->safeAttributeNames; sort($mustBeSafe); sort($safeAttrs); $this->assertEquals($mustBeSafe, $safeAttrs); } } /** * This is the model class for table "{{categories}}". * * The followings are the available columns in table '{{categories}}': * @property integer $id * @property integer $parent_id * @property string $title * @property string $description * @property integer $status */ class Category extends CActiveRecord { const STATUS_PUBLISH = 1; const STATUS_DRAFT = 2; /** * Get status list or status label, if key exist * @static * @param string $key * @return array */ public static function getStatusList($key = null) { $arr = array( self::STATUS_PUBLISH => 'Publish', self::STATUS_DRAFT => 'Draft', ); return $key === null ? $arr : $arr[$key]; } /** * @return string the associated database table name */ public function tableName() { return '{{categories}}'; } /** * @return array validation rules for model attributes. */ public function rules() { return array( array('title, status', 'required'), array('title', 'length', 'max' => 150), array('parent_id', 'exist', 'className' => __CLASS__, 'attributeName' => 'id'), array('description', 'length', 'max' => 4000), array('status', 'in', 'range' => array_keys($this->getStatusList())), array('title, description', 'safe', 'on' => 'search') ); } /** * @return array relational rules. */ public function relations() { return array( 'parent' => array(self::BELONGS_TO, __CLASS__, 'parent_id'), ); } /** * @return array customized attribute labels (name=>label) */ public function attributeLabels() { return array( 'id' => 'ID', 'parent_id' => 'Parent ID', 'title' => 'Title', 'description' => 'Description', 'status' => 'Status', ); } /** * Retrieves a list of models based on the current search/filter conditions. * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions. */ public function search() { $criteria=new CDbCriteria; $criteria->compare('title',$this->title,true); $criteria->compare('description',$this->description,true); return new CActiveDataProvider($this, array( 'criteria'=>$criteria, )); }