我正在处理一些从套接字读取的代码,当它获得某个大输入时它会出错。在修复它之前,我为此添加了一个单元测试,但由于我无法模拟fread
(以及我正在使用的其他 PHP 内置函数,如 fsockopen
、feof
等)而卡住了。
简单来说,我的问题是此代码失败并显示"致命错误:无法重新声明 fgets() ...":
function fgets($fp){
return "xxx";
}
我意识到我可以创建一个套接字包装器类,我的真实代码使用它,然后我可以为该包装类创建一个模拟对象。但那是摇尾巴的狗,我可以想到为什么这是一个坏主意,而不仅仅是事情的原理。(例如,使代码更复杂,效率更高,必须重构尚未测试的代码。
所以,我的问题是如何在单元测试中用我自己的实现替换内置fgets()
实现?(或者,如果你想跳出框框思考,这个问题可以表述为:当$socket
是调用fsockopen
的返回值时,我如何控制调用fgets($socket)
返回的字符串?
旁白
按照正确答案的要求安装 apd 是一项艰苦的工作;它最后一次发布是在 2004 年,并且不支持开箱即用的 php 5.3。没有 Ubuntu 软件包,也pecl install apd
失败了。所以这里是安装它的过程(这些是针对 ubuntu 10.04 的)(全部以 root 身份完成):
pecl download apd
tar xzf apd-1.0.1.tgz
cd apd-1.0.1
phpize
./configure
# Do edits (see below)
make install
请参阅 https://bugs.php.net/bug.php?id=58798 了解您需要执行的修补程序。 注意:只有一行你真正需要更改,所以你可以手动完成,如下所示: 在文本编辑器中打开 php_apd.c,转到第 967 行,然后将CG(extended_info) = 1
行替换为这一行:
CG(compiler_options) |= ZEND_COMPILE_EXTENDED_INFO;
最后,您需要添加一个 php.ini 条目来激活它。请参阅 http://php.net/manual/en/apd.installation.php
如果您无法访问 APD 或 Runkit 但正在使用命名空间,请尝试以下操作: https://stackoverflow.com/a/5337635/664108 (链接中的答案是指 time(),但没有区别)
看看这些:
布尔rename_function (字符串 $original_名称 , 字符串 $new_名称 )
bool override_function ( 字符串 $function_name , 字符串 $function_args , 字符串 $function_code )
将fsockopen
更改为fopen
进行模拟,并且不更改任何其他函数。
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
自
$fp = fopen("/path/to/your/dummy_data_file");
我在博客上写了我的经验,其中包含完整的工作代码,展示了如何使用override_function来实现预期目标;文件读取和套接字读取。我不会在这里重复整篇文章,而只是指出您必须如何使用这些功能:
- 使用
rename_function
为旧的原始函数命名,因此我们可以稍后找到它。 - 使用
override_function
来定义新行为 - 使用
rename_function
为__overridden__
指定一个虚拟名称
如果您希望之后能够恢复原始行为,则第 1 步至关重要。
在最后的"思考的食粮"部分,我展示了一种我认为更健壮的替代方法,因此我认为在使用phpUnit时替换文件函数更安全。它创建一个永久的函数替换,其默认行为是转发到内置函数。然后,它会检查参数(在本例中为资源$handle
),以确定是否在我们希望不同行为的流上调用它。(我认为你可以称之为责任链设计模式的一个例子。