【问题标题】:PHPunit Bug Mock assert Binary StringPHPunit Bug Mock 断言二进制字符串
【发布时间】:2016-04-29 08:53:04
【问题描述】:

我发现了关于 phpunit Mock 的奇怪结果

我问自己这个错误是否是由 serialize() 中的 UTF8 字符引起的

当使用privateprotected 序列化对象时,模拟返回类似这样的内容

Expectation failed for method name is equal to <string:error> when invoked zero or more times
Parameter 0 for invocation Bar::error(Binary String: 0x4f3a333a22466...b4e3b7d) does not match expected value.
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'O:6:"Foo":1:{s:5:"title";N;}'
+Binary String: 0x4f3a333a22466f6f223a313a7b733a32303a22002a00666f6f50726f74656374656456616c7565223b4e3b7d

代码

class Foo
{
    public $fooPublicValue;
    protected $fooProtectedValue; //BREAK
    private $fooPrivateValue;     //BREAK
}

class Bar
{
    public function error($message)
    {
        //some process
    }
}

class Baz
{
    public function exec(Bar $bar)
    {
        $bar->error(serialize(new Foo()));
    }
}

class BazTest extends \PHPUnit_Framework_TestCase
{
    public function testExec()
    {
        $loggerMock = $this->getMockBuilder('Bar')
            ->getMock();

        $loggerMock
            ->method('error')
            ->with($this->equalTo('O:6:"Foo":1:{s:5:"title";N;}'));

        (new Baz())->exec($loggerMock);
    }
}

【问题讨论】:

  • 嗯...请问您有什么问题?

标签: php unit-testing serialization mocking phpunit


【解决方案1】:

PHP docs 中所述,私有成员和受保护成员在序列化期间以* 或类名开头。这些前置值将在两边都有空字节。

更多细节:

这意味着,虽然肉眼看不见,但字符串的实际字节表示会发生变化。例如,可以使用bin2hex

class Foo
{
    public $value;
    protected $one;
    private $two;
}

$serialized = serialize(new Foo());
$expected = 'O:3:"Foo":3:{s:5:"value";N;s:6:"*one";N;s:8:"Footwo";N;}';

echo $serialized; // O:3:"Foo":3:{s:5:"value";N;s:6:"*one";N;s:8:"Footwo";N;}
echo $expected;   // O:3:"Foo":3:{s:5:"value";N;s:6:"*one";N;s:8:"Footwo";N;}

echo bin2hex($serialized);  // 4f3a333a22466f6f223a333a7b733a353a2276616c7565223b4e3b733a363a22002a006f6e65223b4e3b733a383a2200466f6f0074776f223b4e3b7d
echo bin2hex($expected);    // 4f3a333a22466f6f223a333a7b733a353a2276616c7565223b4e3b733a363a222a6f6e65223b4e3b733a383a22466f6f74776f223b4e3b7d

您可以清楚地看到一个字符串比另一个字符串长。如果您查看描述受保护的$one 属性的片段,您可以发现空字节:

s:6:"*one";N

733a363a22002a006f6e65223b4e
733a363a22  2a  6f6e65223b4e

既然您知道差异的来源,让我们来看看您的解决方案。

解决方案

通过实现Serializable 接口,您可以使用serialize()unserialize() 返回代表您的对象的序列化数组。由于数组的所有值都是公开的,因此不会在字符串中插入空字节,因此您可以安全地比较它。您的问题已解决:

class Foo implements Serializable
{
    public $value;
    protected $one;
    private $two;

    public function serialize()
    {
        return serialize([$this->value, $this->one, $this->two]);
    }

    public function unserialize($str)
    {
        list($this->value, $this->one, $this->two) = unserialize($str);
    }
}

// true
var_dump(serialize(new Foo()) === 'C:3:"Foo":24:{a:3:{i:0;N;i:1;N;i:2;N;}}');

希望这会有所帮助。

【讨论】:

  • 感谢我的回复,我只是用 str_replace(chr(0), '', $string); 删除 NUL 字节;
  • 酷!在撰写本文时,我没有想到这一点。与实现您自己的序列化方法相比,它非常容易解决。如果您不介意,我会将其添加到答案中。
猜你喜欢
  • 2019-03-15
  • 2020-01-08
  • 2015-03-23
  • 2011-07-11
  • 2016-12-04
  • 1970-01-01
  • 2021-11-09
  • 2017-03-22
  • 2016-02-26
相关资源
最近更新 更多