Forms 当Symfony2表单使用链接到数据库的转换器时,如何对其进行单元测试
TLDR:我不熟悉单元测试,我有几个问题:Forms 当Symfony2表单使用链接到数据库的转换器时,如何对其进行单元测试,forms,unit-testing,symfony,phpunit,transformer,Forms,Unit Testing,Symfony,Phpunit,Transformer,TLDR:我不熟悉单元测试,我有几个问题: 我的变压器测试写得好吗 有没有办法将我的transformer测试与数据库分离 如何使用数据库使用transformer测试我的表单 我是否应该将表单与转换器分离 我不知道我的类是否过于耦合,我的设计是否有缺陷,或者我对单元测试的理解是否糟糕 以下是一些背景资料。 我有一个带有不同小部件的表单对象。其中一个用于模型变压器中。 此模型转换器使用到数据库的连接来检索适当的对象 这是我的密码: class BookToStringTransformer i
我不知道我的类是否过于耦合,我的设计是否有缺陷,或者我对单元测试的理解是否糟糕 以下是一些背景资料。
我有一个带有不同小部件的表单对象。其中一个用于模型变压器中。
此模型转换器使用到数据库的连接来检索适当的对象 这是我的密码:
class BookToStringTransformer implements DataTransformerInterface {
private $om;
public function __construct(ObjectManager $om) {
$this->om = $om;
}
public function transform($book) {
if (!$book instanceof Book) {
return "";
}
return $book->getName();
}
public function reverseTransform($string) {
if (!is_string($string) || !$string) {
return null;
}
$book = $this->om
->getRepository('MyBundle:Book')
->findOneBy(array('name' => $string))
;
if (null === $book) {
throw new TransformationFailedException(sprintf(
'The book "%s" does not exist!', $string
));
}
return $book;
}
}
class ItemType extends AbstractType {
private $om;
public function __construct(ObjectManager $om) {
$this->om = $om;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$bookTransformer = new BookToStringTransformer($this->om);
$builder->add($builder->create('book', 'text', array(
'required' => false,
))->addModelTransformer($bookTransformer));
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\Item',
));
}
public function getName() {
return 'mybundle_item';
}
}
我使用KernelTestCase编写了变压器的单元测试
class BookToStringTransformerTest extends KernelTestCase {
private $name = 'existing name';
private $em;
public function setUp() {
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->em = static::$kernel->getContainer()
->get('doctrine')
->getManager();
}
public function testReverseTransform_whenNameExists_returnsBookObject() {
$transformer = new BookToStringTransformer($this->em);
$book = $transformer->reverseTransform($this->name);
$this->assertInstanceOf('MyBundle\Entity\Book', $book, 'Should return a Book object');
$this->assertEquals($this->name, $book->getName(), 'Should return a Book object with the selected name');
}
/**
* @expectedException Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testReverseTransform_whenNameDoesNotExist_throwsException() {
$transformer = new BookToStringTransformer($this->em);
$transformer->reverseTransform('unknown name');
}
/**
* @param mixed $invalid_parameter
* @dataProvider provideInvalidParameter
*/
public function testReverseTransform_whenParameterIsInvalid_returnsNull($invalid_parameter) {
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$transformer = new BookToStringTransformer($om);
$this->assertNull($transformer->reverseTransform($invalid_parameter), 'Should return a NULL value');
}
/**
* @return array
*/
public function provideInvalidParameter() {
return [
[null],
[false],
[true],
[''],
[[]],
[new \stdClass()],
];
}
public function testTransform_whenParameterIsBookObject_returnsName() {
$book = $this->em->getRepository('MyBundle:Book')
->findOneBy(array('name' => $this->name));
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$transformer = new BookToStringTransformer($om);
$this->assertEquals($this->name, $transformer->transform($book), 'Should return a string containing the name');
}
/**
* @param mixed $not_book
* @dataProvider provideInvalidBookObject
*/
public function testTransform_whenParameterIsNotBookObject_returnsEmptyString($not_book) {
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$transformer = new BookToStringTransformer($om);
$this->assertEquals("", $transformer->transform($not_book), 'Should return an empty string to be chained');
}
/**
* @return array
*/
public function provideInvalidBookObject() {
return [
[null],
[123],
['123'],
[[]],
[true],
[new \stdClass()],
];
}
}
由于我不熟悉单元测试,我甚至不知道这是否是测试变压器的正确方法。我开始为表单对象编写测试。我使用的是TypeTestCase,但是没有简单的方法可以连接到数据库,我不能使用KernelTestCase
class ItemTypeTest extends TypeTestCase {
/**
* @expectedException \PHPUnit_Framework_Error
*/
public function test_whenCreatedWithNoParameters_raiseException() {
new ItemType();
}
/**
* @expectedException \PHPUnit_Framework_Error
*/
public function test_whenCreatedWithBadParameters_raiseException() {
new ItemType(123);
}
public function test_whenCreatedWithGoodParameters_createsFormObject() {
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$type = new ItemType($om);
$form = $this->factory->create($type);
$this->assertInstanceOf('Symfony\Component\Form\Form', $form);
}
public function test_whenSubmittedWithGoodData() {
$formData = array(
'name' => 'existing name',
);
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$type = new ItemType($om);
$form = $this->factory->create($type);
$form->submit($formData);
}
}
最后一个测试失败,因为我正在向表单传递一个模拟,所以转换器确实可以访问数据库。所以我应该得到一个真实的对象(意味着类太耦合了),还是应该找到另一种方法呢
谢谢方法很好,在最后一种方法中,必须模拟回购对象和回购响应。在示例中,请尝试以下代码:
public function test_whenSubmittedWithGoodData() {
$formData = array(
'name' => 'existing name',
);
$om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
$repoMock= $this->getMock('Doctrine\ORM\EntityRepository', array(), array(), '', false);
$om
->expects($this->atLeastOnce())
->method('getRepository')
->withAnyParameters()
->will($this->returnValue($repoMock));
$repoMock
->expects($this->atLeastOnce())
->method('findOneBy')
->withAnyParameters()
->will($this->returnValue($mockedBook));
$type = new ItemType($om);
$form = $this->factory->create($type);
$form->submit($formData);
}
谢谢你的快速回复。据我所知,$mockedBook也是一个模拟对象。我在这一点上得到什么并不重要。我说的对吗?取决于您的测试范围,在您的情况下,您不会对返回对象进行任何检查。您可以模拟repo找不到带有$repoMock->expects($this->atlestOnce())->方法('findOneBy')->withAnyParameters()->will($this->returnValue(null))的书籍;美好的基本上,我隐藏了我的book对象实现的所有细节,只返回有趣的部分。