什么是可测试的代码?
对于可测试概念,可能每个程序员心中都有属于自己的那一套定义。而我对可测试的定义:代码调用的最小单元,实际上指的就是一个功能块或者方法。
为什么要做单元测试?
- 安全感:对自己写的代码有掌控的能力
- 兴趣感:有个单元测试,我们能很快的从代码中获取到反馈,从红色变为绿色的,成就感非常强的,会促使自己有动力去修改代码。
- 整洁感:单元测试可以帮助我们对业务逻辑代码和访问代码分离,形成代码架构。
单元测试测什么?
我们先来看看误区:
- 需要模拟容器与模拟数据库才能跑起来 (增加复杂度)
- 测试方法需要依赖外部环境 (依赖过多)
- 服务代码与业务代码耦合在一起。(分层不明)
如何改造代码?
开发人员可能会常写成以下这样:
public function getUserOrder(Request $request)
{
$userId = $request->getParameter('user_id');
$orderId = $request->getParameter('order_id');
$user = User::find($userId);
$order = Order::find($orderId);
if ($order != null && $order->user_id != null && ($order->user_id == $userId)) {
return $order;
}
return null;
}
从代码上来看有了业务逻辑,这说明这段代码需要单元测试了。但是我们往下看就会发现,这段代码里面有容器的依赖 Request 和 数据库。如果要运行这个单元测试就必须先模拟 依赖和数据库。然后模拟就越来越复杂了。我们这时要记住一个准则: 只要出现了模拟,单元测试就开始失效了。 单元测试的代码实际上是开发人员自己写的逻辑。我们来看下改造后的代码:
public function getUserOrder(Request $request)
{
$userId = $request->getParameter('user_id');
$orderId = $request->getParameter('order_id');
$user = User::find($userId);
$order = Order::find($orderId);
return $this->checkUser($order, $user, $userId);
}
protected function checkUser(Order $order, User $user, $userId)
{
if ($order != null && $order->user_id != null && ($order->user_id == $userId)) {
return $order;
}
return null;
}
新建了一个checkUser方法,我们将所需的参数给提取出来了,这样getUserOrder就不需要测试了,我们只需要对 checkUser这块业务逻辑做单元测试就行了。输入参数->调用目标方法->检查是否一致。把自己写的逻辑独立出来,让自己写的逻辑单元只依赖于输入参数,就变得可测了。
如何做单元测试?
php的话可以使用**PHPUnit**测试框架作为单元测试的工具。
- 构建输入参数,并预测该输入所产生的输出;
- 调用要测试的目标方法,获取输出;
- 检测目标方法的输出是否和预期的输出一致(Assert)
单元测试目录结构
- 测试代码是对业务代码的检测,因此测试代码和业务代码的代码组织架构要保持一致,都要是树状架构,且与业务架构一一对应。
- 每个要测试的php类,对应有一个单元测试php类,区别为单元测试php类名后面加Test。方法也类似,就是在原方法名前加个test,方便测试出错的时候找到被测类和被测方法。
总结
单元测试的编写可能会占用一些我们的时间,但是正所谓磨刀不误砍柴工,我们慢慢的将单元测试给建立起来,那样我们的测试工作量将会越来越小,而且只要业务没有太大的改动,单元测试是不需要再维护的了。属于一次投入会持续受益的那种,而且也给开发工程师带来安全感,为我们保驾护航。
|