在依赖PHPUnit测试之间传递的对象发生了什么?

时间:2022-10-15 22:43:23

This isn't so much a question as an attempt to save somebody else the hour I just wasted on PHPUnit.

这与其说是一个问题,还不如说是我在PHPUnit上浪费了一个小时来拯救别人。

My problem was that my mock object, when used in a dependent test, was not returning the expected value. It seems that PHPUnit does not preserve the same object between dependent tests, even though the syntax makes it look like it does.

我的问题是,在依赖测试中使用的mock对象没有返回期望值。PHPUnit似乎并没有在依赖测试之间保持相同的对象,即使语法看起来像这样。

Does anyone know why PHPUnit does this? Is this a bug? Things like this in PHPUnit make it very frustrating to use.

有人知道为什么PHPUnit这么做吗?这是一个错误吗?在PHPUnit中这样的东西会让使用变得非常令人沮丧。

<?php 
class PhpUnitTest
extends PHPUnit_Framework_TestCase
{
private $mock;

public function setUp()
{
    $this->mock = $this->getMock('stdClass', array('getFoo'));

    $this->mock->expects( $this->any() )
        ->method('getFoo')
        ->will( $this->returnValue( 'foo' ) );
}

public function testMockReturnValueTwice()
{
    $this->assertEquals('foo', $this->mock->getFoo());
    $this->assertEquals('foo', $this->mock->getFoo());

    return $this->mock;
}

/**
 * @depends testMockReturnValueTwice
 */
public function testMockReturnValueInDependentTest($mock)
{
    /* I would expect this next line to work, but it doesn't! */
    //$this->assertEquals('foo', $mock->getFoo());

    /* Instead, the $mock parameter is not the same object as
     * generated by the previous test! */
    $this->assertNull( $mock->getFoo() );
}

}

2 个解决方案

#1


5  

Mock objects in PHPUnit are attached to the test instance for which they are created, and this by definition means a single test method. The reason for this is that PHPUnit allows you to specify expectations on a mock that must be satisfied during a test. To do this it asserts those expectations once the method terminates successfully. If the mock lived across tests, expectations wouldn't work.

PHPUnit中的模拟对象被附加到它们所创建的测试实例上,而这个定义意味着一个单一的测试方法。原因是PHPUnit允许您指定在测试期间必须满足的mock上的期望。要做到这一点,在方法成功终止后,它断言这些期望。如果模拟在测试之间运行,那么期望将不起作用。

The problem is that this doesn't support stub objects: mocks that contain only canned actions to be taken in response to methods and inputs. Stubs do not validate that their methods are called as full mocks can. Perhaps PHPUnit could benefit from the ability to create stubs in setUpBeforeClass() that are not tied to the test instance.

问题是,这并不支持存根对象:模拟只包含响应方法和输入的固定动作。存根并不验证它们的方法被称为完整的mock。也许PHPUnit可以从在setUpBeforeClass()中创建不绑定到测试实例的存根的能力中获益。

Your other option is to use an external mock object library such as Mockery or Phake.

您的另一种选择是使用外部模拟对象库,如mock或Phake。

Edit: After looking over your sample code again, I wonder why you are surprised by this behavior. As Shaunak wrote, setUp() is called on a new instance before each test method is executed. Thus, each instance receives a new mock stdClass. If you want only one test method to receive an expectation, add it inside the test method itself. You can still create the mock object in setUp() with any behavior that should be common to all test methods.

编辑:再次查看示例代码后,我想知道为什么您会对这种行为感到惊讶。正如Shaunak所写的,在每个测试方法执行之前,setUp()会被调用一个新的实例。因此,每个实例都接收一个新的模拟stdClass。如果您只需要一个测试方法来接收期望,那么将它添加到测试方法本身中。您仍然可以在setUp()中创建模拟对象,任何行为都应该在所有测试方法中都是通用的。

#2


2  

I am not a php guy ,so correct me if I am wrong, but the all unit tests are designed to run in following sequence,

我不是一个php人,所以如果我错了请纠正我,但是所有的单元测试都是按照以下顺序运行的,

Setup --> test function --> destroy.

设置——>测试函数——>销毁。

so setup and destroy function is called each time before executing any test function. This is done on purpose to preserve the purpose of unit testing.

所以每次执行测试函数之前都要调用setup和destroy函数。这样做是为了保持单元测试的目的。

If you want to have dependent unit test cases then you have to code them, that way instead of depending on global variables to do that (this defeats the purpose of unit tests!). If there are is a test case 'A' that depends on some function, call that function from 'A' and then assert the values.

如果您想要有依赖的单元测试用例,那么您必须以这种方式编写它们,而不是依赖全局变量来完成(这违背了单元测试的目的!)如果有一个测试用例“a”依赖于某个函数,那么将该函数从“a”调用,然后断言该值。

#1


5  

Mock objects in PHPUnit are attached to the test instance for which they are created, and this by definition means a single test method. The reason for this is that PHPUnit allows you to specify expectations on a mock that must be satisfied during a test. To do this it asserts those expectations once the method terminates successfully. If the mock lived across tests, expectations wouldn't work.

PHPUnit中的模拟对象被附加到它们所创建的测试实例上,而这个定义意味着一个单一的测试方法。原因是PHPUnit允许您指定在测试期间必须满足的mock上的期望。要做到这一点,在方法成功终止后,它断言这些期望。如果模拟在测试之间运行,那么期望将不起作用。

The problem is that this doesn't support stub objects: mocks that contain only canned actions to be taken in response to methods and inputs. Stubs do not validate that their methods are called as full mocks can. Perhaps PHPUnit could benefit from the ability to create stubs in setUpBeforeClass() that are not tied to the test instance.

问题是,这并不支持存根对象:模拟只包含响应方法和输入的固定动作。存根并不验证它们的方法被称为完整的mock。也许PHPUnit可以从在setUpBeforeClass()中创建不绑定到测试实例的存根的能力中获益。

Your other option is to use an external mock object library such as Mockery or Phake.

您的另一种选择是使用外部模拟对象库,如mock或Phake。

Edit: After looking over your sample code again, I wonder why you are surprised by this behavior. As Shaunak wrote, setUp() is called on a new instance before each test method is executed. Thus, each instance receives a new mock stdClass. If you want only one test method to receive an expectation, add it inside the test method itself. You can still create the mock object in setUp() with any behavior that should be common to all test methods.

编辑:再次查看示例代码后,我想知道为什么您会对这种行为感到惊讶。正如Shaunak所写的,在每个测试方法执行之前,setUp()会被调用一个新的实例。因此,每个实例都接收一个新的模拟stdClass。如果您只需要一个测试方法来接收期望,那么将它添加到测试方法本身中。您仍然可以在setUp()中创建模拟对象,任何行为都应该在所有测试方法中都是通用的。

#2


2  

I am not a php guy ,so correct me if I am wrong, but the all unit tests are designed to run in following sequence,

我不是一个php人,所以如果我错了请纠正我,但是所有的单元测试都是按照以下顺序运行的,

Setup --> test function --> destroy.

设置——>测试函数——>销毁。

so setup and destroy function is called each time before executing any test function. This is done on purpose to preserve the purpose of unit testing.

所以每次执行测试函数之前都要调用setup和destroy函数。这样做是为了保持单元测试的目的。

If you want to have dependent unit test cases then you have to code them, that way instead of depending on global variables to do that (this defeats the purpose of unit tests!). If there are is a test case 'A' that depends on some function, call that function from 'A' and then assert the values.

如果您想要有依赖的单元测试用例,那么您必须以这种方式编写它们,而不是依赖全局变量来完成(这违背了单元测试的目的!)如果有一个测试用例“a”依赖于某个函数,那么将该函数从“a”调用,然后断言该值。