第一节 检测依赖

        令人惊讶的是,这很容易。 记住单元测试的一个黄金法则:隔离。 在程序中,如果另一个函数被调用或另一个类时,则含有它的代码不被隔离。 这可能导致测试方法或功能之外的测试失败。 因此,它不能被视为单元测试。 使用诸如文件系统,数据库和网络之类的资源也是如此。 结果可能会受到这些资源的影响,从而导致代码不是孤立的,因此它不是单元测试。

        为了演示依赖在现实世界中的含义,下面的代码片段是User类的一个简单示例。 此类包装代码以创建用户,将其存储在数据库中,并发送激活电子邮件。

<?php
namespace Application;
/**
 * Class User
 * @package Application
 */
class User
{
    public $userId;
    public $firstName;
    public $lastName;
    public $email;
    public $password;
    public $salt;

    /**
     * @param array $options
     */
    public function __construct ( array $options )
    {
        foreach ($options as $key => $value) {
            if (property_exists( $this, $key )) {
                $this->{$key} = $value;
            }
        }
    }

    /**
     * validates properties
     * * @return bool
     */
    public function isInputValid ()
    {
        if (empty( $this->firstName ) || empty( $this->lastName ) ||
            empty( $this->email ) || empty( $this->password ) ||
            !filter_var( $this->email, FILTER_VALIDATE_EMAIL )) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * creates password hash
     */
    public function createPassword ()
    {
        $this->salt     =
            substr( str_shuffle( "0123456789abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ" ), 0, 15 ); $this->password = sha1( $this->password . $this->salt ); } /** * verifies password * @param string $password * @return bool */ public function verifyPassword ( $password ) { return ( $this->password === sha1( $password . $this->salt ) ); }

如你所见,到目前为止一切顺利。 这里没问题。 代码可以很容易地测试。 每种方法都很简单; 仅使用User类中的代码。 但是,从测试的角度来看,以下代码段更糟糕:

    /**
     * sends activation email
     */
    private function sendActivationEmail ()
    {
        global $config;
        $email = new \Util\Mail( $config );
        $email->setEmailFrom( $config->email );
        $email->setEmailTo( $this->email );
        $email->setTitle( 'Your account has been activated' );
        $email->setBody( "Dear {$this->firstName}\n
    Your account has been activated\n
    Please visit {$config->site_url}\n
    Thank you" );
        $email->send();
    }
    
    /**
     * stores user to the database
     * @return bool
     */
    public function createUser ()
    {
        global $config;
        if (!$this->isInputValid()) {
            return false;
        }
        $this->createPassword();
        $db = $config->db;
        /* @var $db \PDO */
        $sql       = "INSERT INTO users(firstname, lastname, email,
    password, salt) VALUES (:firstname, :lastname, :email,
    :password, :salt)";
        $statement = $db->prepare( $sql );
        $statement->bindParam( ':firstname', $this->firstName );
        $statement->bindParam( ':lastname', $this->lastName );
        $statement->bindParam( ':email', $this->email );
        $statement->bindParam( ':password', $this->password );
        $statement->bindParam( ':salt', $this->salt );
        if ($statement->execute()) {
            $this->userId = $db->lastInsertId();
            $this->sendActivationEmail();
            return true;
        } else {
            throw new \Exception( 'User wasn\'t saved:' . implode( ':', $statement->errorInfo() ) );
        }
    }
}

这是一个简单的User类,它包含或多或少的普通PHP代码,用于处理用户帐户的创建并将数据存储在数据库中。 你可以说它是简单,高效的代码; 它没有任何问题,为什么要担心呢?

首先,让我们看看代码做了什么:

  • 构造函数接受一个数组并为该对象赋予属性
  • isInputValid()函数执行基本验证
  • createPassword()函数创建salt变量,然后使用sha1()函数创建密码哈希
  • verifyPassword()函数验证提供的用户密码是否与存储在sha1()散列中的密码匹配
  • sendActivationEmail()函数发送有关新创建的帐户的通知电子邮件
  • createUser()函数将用户帐户存储到数据库中

以下是测试此类时的问题:

  • 使用全局变量$ config
  • 使用\ Util \ Mail类,它是一个外部类,并且如预期的那样,将发送一封电子邮件
  • 使用PDO数据库连接($ config-> db)

为什么这些问题呢? 他们真的有问题吗?

通过在代码中添加依赖项,您将增加复杂性。 代码越复杂,它包含错误的可能性就越大,重构就越困难。 例如,只需将$ config设置为全局变量,就可以非常轻松地将全局变量传递给测试。 然而,问题是以简单传递的对象开始的东西通常会变得更复杂。 要设置应用程序的配置,您需要创建类加载器,错误处理程序,加载配置等等。 突然间,您必须启动应用程序并执行数百行代码; 而不是测试一个类,您正在测试一半的应用程序。 一个简单的建议是避免类中的全局变量和会话变量。

我们的示例中的另一个问题是使用Mail类。 如果要运行测试,则不希望发送电子邮件。

同样,另一个问题是与数据库的连接。 PHP已经成为一种脚本语言,它用于快速存储和检索MySQL数据库中的数据。 这种只是将数据传入和传出数据库的代码非常常见。 它有效,但考虑一下你是否想要改变行为; 例如,导入用户而没有他们的密码? 在这种情况下你能做些什么; 使用另一种方法并执行代码的副本?

公平地说,有很多方法可以做到这一点。 有时您可能会对将数据库交互留在类中感到满意,但是当您最终将所有代码放在一个巨大的类中时,这可能会成为问题。

以下可能是如何测试此代码的基本选项:

  • 重构代码并将逻辑拆分为实体和管理器
  • 使用集成测试并连接到数据库
  • 使用mocks创建一个模仿依赖代码功能的虚拟类

猜你喜欢

转载自www.cnblogs.com/mysic/p/9442037.html