TP5自定义函数需脱离控制器才能测试,应移入工具类作静态方法或显式加载helper文件,用PHPUnit\Framework\TestCase测试并覆盖边界值。

TP5 自定义函数不能直接用 $this->visit() 测试,必须抽离为可调用对象或静态方法,再用 PHPUnit 原生方式断言。
自定义函数得先“脱离控制器”才能测
TP5 里很多人把工具函数写在 application/common.php 或某个 helper 文件里,比如:
function format_money($amount) {
return '¥' . number_format($amount, 2);
}
这类函数没有类上下文,PHPUnit 默认无法自动加载——因为 TP5 的自动加载机制不覆盖全局函数文件。
- 必须在测试前显式
require_once对应的 helper 文件,否则报Call to undefined function - 不能依赖
think-testing的TestCase,它只封装了 HTTP 请求链路,对纯函数无感知 - 推荐做法:把函数挪进一个工具类(如
app\common\helper\MoneyHelper),用静态方法暴露,更易 mock 和复用
测试类要继承 PHPUnit\Framework\TestCase,不是 TestCase
如果你沿用 tests\ExampleTest extends TestCase,会发现 $this->visit() 等方法根本用不上,还可能因框架初始化失败报错。
- 改用标准 PHPUnit 基类:
class MoneyHelperTest extends \PHPUnit\Framework\TestCase - 在
setUp()里加require_once APP_PATH . 'common.php';(路径按你实际调整) - 或者更干净:在
phpunit.xml的<php>节点里配置<include path="application/common.php"/> - 注意:TP5.1 的
APP_PATH是常量,测试时未定义,得手动 define 或用__DIR__ . '/../application/common.php'
断言要覆盖边界值,别只测“能跑”
比如上面的 format_money,只测 format_money(100) 返回 ¥100.00 不够。容易漏掉这些情况:
- 输入
null或空字符串 → 是否抛异常?还是静默转成¥0.00?按你函数实际逻辑断言 - 输入负数:
format_money(-99.5)→ 返回¥-99.50还是拦截?确认后再写$this->assertEquals() - 浮点精度问题:
format_money(100.005)→number_format默认舍去,结果是¥100.00,不是四舍五入 - 如果函数内部调用了
config()或input(),说明它其实有框架依赖——这时得用Mockery或重写为参数驱动
避免在测试中引入不必要的 TP 初始化
很多教程让测试类继承 TP 的 TestCase,只为用它的 setUp(),结果每次跑测试都触发完整框架启动,慢且易受路由/中间件干扰。
- 纯函数测试不需要
App、Request、Config实例,硬加载反而增加失败概率 - 若函数真依赖
config('money.precision'),那就把它改成参数传入:format_money($amount, $precision = 2),测试时直接控制变量 - 实在绕不开(比如遗留代码),可在测试前临时 define
IN_UNIT_TEST并 patchthink\facade\Config,但这是下策
真正难的不是写断言,而是判断这个函数到底“属于谁”——是独立工具,还是隐式绑定框架生命周期。前者直接测,后者得先解耦。

















