我应该在单元测试中走多远?

rye*_*guy 6 unit-testing

我正在尝试在个人PHP项目中进行单元测试,就像一个优秀的小程序员,我想要正确地完成它.从我听到你应该测试的是一个方法的公共接口,但我想知道这是否仍然适用于下面.

我有一个方法,在用户忘记密码的情况下生成密码重置令牌.该方法返回以下两种情况之一:如果一切正常,则为nothing(null),或者表示具有指定用户名的用户不存在的错误代码.

如果我只测试公共接口,如果用户名有效,如何确保密码重置令牌在数据库中,如果用户名无效,我怎么能不在数据库中?我应该在测试中进行查询以验证这一点吗?或者我应该假设我的逻辑是合理的?

现在这个方法非常简单,这不是什么大不了的事 - 问题在于同样的情况适用于许多其他方法.你在以数据库为中心的单元测试中做了什么?

代码,如有需要可供参考:

public function generatePasswordReset($username)
{
    $this->sql='SELECT  id
                FROM    users
                WHERE   username = :username';

    $this->addParam(':username', $username);
    $user=$this->query()->fetch();

    if (!$user)
        return self::$E_USER_DOESNT_EXIST;
    else
    {
        $code=md5(uniqid());
        $this->addParams(array(':uid'        => $user['id'],
                               ':code'       => $code,
                               ':duration'   => 24 //in hours, how long reset is valid
                              ));

        //generate new code, delete old one if present
        $this->sql ='DELETE FROM password_resets WHERE user_id=:uid;';
        $this->sql.="INSERT INTO password_resets (user_id, code, expires)
                     VALUES      (:uid, :code, now() + interval ':duration hours')";

        $this->execute();
    }
}
Run Code Online (Sandbox Code Playgroud)

p.c*_*ell 6

至少对于我而言,单元测试的好处在于它向您展示了重构所需的位置.使用上面的示例代码,您基本上在一个方法中发生了四件事:

//1. get the user from the DB
//2. in a big else, check if user is null
//3. create a array containing the userID, a code, and expiry
//4. delete any existing password resets
//5. create a new password reset
Run Code Online (Sandbox Code Playgroud)

单元测试也很棒,因为它有助于突出依赖性.如上所示,此方法依赖于DB,而不是实现接口的对象.此方法与其范围之外的系统交互,实际上只能使用集成测试进行测试,而不是单元测试.单元测试用于确保工作单元的工作/正确性.

考虑单一责任原则:" 做一件事 ".它适用于方法和类.

我建议你的generatePasswordReset方法应该重构为:

  • 被赋予预定义的现有用户对象/ id.在这种方法之外做所有那些健全性检查.做一件事.
  • 将密码重置代码放入自己的方法中.这将是一个单独的工作单元,可以独立于SELECT,DELETEINSERT.
  • 创建一个可以调用的新方法OverwriteExistingPwdChangeRequests()来处理DELETE + INSERT.