doctrineのコマンドを実行して、自動生成されたentityのsetter、getterに関するテストは自動生成されてほしい。

  • こんなところでカバレッジが落とすのもちょっとね、、、
  • そもそも自動生成されたコードなんだから、テストケースも自動生成されてほしい

テストコードのないentityがそれなりに数が多いっていうか。entityもそれなりの数のプロパティを持っていたりしてだな。なぜか手書きのコードが多くてだな。うむ。

よく分かる言い方をするとだ。手抜きしたい。そんな話。

What I want

こんなテストコードジェネレーター欲しいす。なければ作るか。

  • constructionした時の状態に関するテスト
    • getしたらnullが取得されること
    • ArrayCollectionの場合、getしたら空のArrayCollectionオブジェクトが取得されること
  • getter、setterに関するテスト
    • setしたら値がgetterで取得されること
  • その他のdomain logicに関するテストケースとは別ファイルにしたい
    • PersonAccessorTest.phpみたいなファイル
  • @coversアノテーションをつけてほしい
    • @covers \Satooshi\Bundle\PersonBundle\Entity\Person
  • @groupアノテーションをつけてほしい
    • @group unit
    • @group entity

あとは、netbeansだと@assertアノテーションを付けておくと、テストケースが自動生成されるとかなんとか、どこかで見た気がする。でも、それはまた別の話か。

で、こんな感じのentityクラスがあったりすると、下のようなテストケースが出来て欲しい。手抜きしたい。

Person.php
<?php
namespace Satooshi\Bundle\PersonBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="Satooshi\Bundle\PersonBundle\Repository\PersonRepository")
* @ORM\Table(name="person")
*
* @author KITAMURA Satoshi <with.no.parachute@gmail.com>
*/
class Person
{
/**
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*
* @var integer
*/
protected $id;
/**
* @ORM\Column(name="name", type="string", length=50)
*
* @var string
*/
protected $name;
/**
* @ORM\Column(name="email", type="string", length=50)
*
* @var string
*/
protected $email;
/**
* @ORM\ManyToMany(targetEntity="Person")
* @ORM\JoinTable(
* name="person_friendship",
* joinColumns={@ORM\JoinColumn(name="person_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="friend_id", referencedColumnName="id")}
* )
*
* @var \Doctrine\Common\Collections\ArrayCollection
*/
protected $friends;
/**
* Constructor.
*/
public function __construct()
{
$this->friends = new ArrayCollection();
}
/**
* Return id.
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name.
*
* @param string $name
*
* @return Person
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Return name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set email.
*
* @param string $email
*
* @return Person
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Return email.
*
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* Add friend.
*
* @param Person $friend
*
* @return Person
*/
public function addFriend(Person $friend)
{
$this->friends[] = $friend;
return $this;
}
/**
* Remove friend.
*
* @param Person $friend
*/
public function removeFriend(Person $friend)
{
$this->friends->removeElement($friend);
}
/**
* Return friends.
*
* @return \Doctrine\Common\Collections\ArrayCollection
*/
public function getFriends()
{
return $this->friends;
}
}
PersonAccessorTest.php
<?php
namespace Satooshi\Bundle\PersonBundle\Tests\Entity;
use Satooshi\Bundle\PersonBundle\Entity\Person;
/**
* @covers \Satooshi\Bundle\PersonBundle\Entity\Person
*
* @group unit
* @group entity
*/
class PersonAccessorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var \Satooshi\Bundle\PersonBundle\Entity\Person
*/
protected $obj;
protected function setUp()
{
$this->obj = new Person();
}
public function testConstruction()
{
$this->assertNull($this->obj->getId());
$this->assertNull($this->obj->getName());
$this->assertNull($this->obj->getEmail());
$this->assertCount(0, $this->obj->getFriends());
}
public function testSetName()
{
$expected = 'name';
$self = $this->obj->setName($expected);
$this->assertEquals($this->obj->getName(), $expected);
$this->assertSame($self, $this->obj);
}
public function testSetEmail()
{
$expected = 'email@example.com';
$self = $this->obj->setEmail($expected);
$this->assertEquals($this->obj->getEmail(), $expected);
$this->assertSame($self, $this->obj);
}
public function testAddFriend()
{
$expected = new Person();
$self = $this->obj->addFriend($expected);
$this->assertCount(1, $this->obj->getFriends());
$friends = $this->obj->getFriends();
$this->assertSame($expected, $friends[0]);
$this->assertSame($self, $this->obj);
}
public function testRemoveFriend()
{
$expected = new Person();
$this->obj->addFriend($expected);
$this->obj->removeFriend($expected);
$this->assertCount(0, $this->obj->getFriends());
}
}

Testing domain models

開発が進んで、運用も始まって、それなりにentityがデカくなってくると、プロパティとアクセッサメソッドが邪魔になってくる。自動生成されるところなんだから、BaseEntityクラスを作った方がいいのかもしれない。generation gapパターンだったか。

  • Entity/Base/Person - プロパティ定義 + setter/getterを生成する
  • Entity/Person extends Base/Person - domain logicを書くところ

あるいは

  • Entity/Model/Person - プロパティ定義
  • Entity/Base/Person extends Model/Person - setter/getterを生成する
  • Entity/Person extends Base/Person - domain logicを書くところ

こんな感じで。

テストコードを書くべきところは、Entity/Personに書いてあるコード、となる。

そういえば、hyper speed developmentなんとか、の話題で、

そもそも自動生成しないといけないようなコードってどうなのよ? そういったコードを書かなくてもいいような進化を続けているのが最近の言語やフレームワークなんじゃないの?

というような意見を、twitterか何かで見かけた気がするけど、ああ、確かにその通りだなーと思ったりした。