php - "Static methods are death to testability"- 替代构造函数的替代品?

标签 php unit-testing oop design-patterns static-methods

据说 "static methods are death to testability" 。如果是这样,下面的可行替代模式是什么?

class User {

    private $phone,
            $status = 'default',
            $created,
            $modified;

    public function __construct($phone) {
        $this->phone    = $phone;
        $this->created  = new DateTime;
        $this->modified = new DateTime;
    }

    public static function getByPhone(PDO $pdo, $phone) {
        $stmt = $pdo->prepare('SELECT * FROM `users` WHERE `phone` = :phone');
        $stmt->execute(compact('phone'));
        if (!$stmt->rowCount()) {
            return false;
        }

        $record         = $stmt->fetch(PDO::FETCH_ASSOC);
        $user           = new self($record['phone']);
        $user->status   = $record['status'];
        $user->created  = new DateTime($record['created']);
        $user->modified = new DateTime($record['modified']);
        return $user;
    }

    public function save(PDO $pdo) {
        $stmt = $pdo->prepare(
            'INSERT INTO `users` (`phone`, `status`, `created`, `modified`)
                  VALUES         (:phone,  :status,  :created,  :modified)
             ON DUPLICATE KEY UPDATE `status`   = :status,
                                     `modified` = :modified');

        $data = array(
            'phone'    => $this->phone,
            'status'   => $this->status,
            'created'  => $this->created->format('Y-m-d H:i:s'),
            'modified' => date('Y-m-d H:i:s')
        );

        return $stmt->execute($data);
    }

    ...

}

这只是一个简化的例子。这个类有更多的方法和属性,并且在写入数据库时​​有更多的验证等。这个类背后的指导设计原则是它将用户建模为一个对象。对象的某些属性在创建后无法修改,例如电话号码(充当主 ID)、用户创建日期等。其他属性只能根据严格的业务规则进行更改,这些规则都具有严格验证的 setter 和 getter。

对象本身并不代表数据库记录,数据库仅被视为一种永久存储的可能形式。因此,数据库连接器并未存储在对象中,而是需要在每次对象需要与数据库交互时注入(inject)。

创建新用户时,这看起来像:

$user = new User('+123456789');

当现有用户从永久存储中恢复时,看起来像:

$pdo  = new PDO('...');
$user = User::getByPhone($pdo, '+123456789');

如果我认真对待“可测试性已死”这条线,这被认为是糟糕的。不过,我完全能够测试这个对象,因为它是完全依赖注入(inject)的,并且 static 方法没有状态。我怎样才能以不同的方式做到这一点并避免使用 static 方法?或者更确切地说,在这种情况下,究竟是什么反对 static ?是什么让 static 方法的这种特殊用途如此难以测试?

最佳答案

这主要是(我的观点)the chat that ensued between me and @zerkms 的摘要:

争论点其实是这样的:

public function doSomething($id) {
    $user = User::getByPhone($this->pdo, $id);

    // do something with user

    return $someData;
}

这使得测试 doSomething 变得困难,因为它硬编码了 User 类,它可能有也可能没有很多依赖关系。但这实际上与使用非静态方法实例化对象是一样的:

public function doSomething($id) {
    $user = new User;
    $user->initializeFromDb($this->pdo, $id);

    // do something with user

    return $someData;
}

我们没有使用静态方法,但它仍然是不可模拟的。事实上,情况变得更糟。
答案是使用工厂:

public function doSomething($id) {
    $user = $this->UserFactory->byPhone($id);

    // do something with user

    return $someData;
}

现在可以对工厂进行依赖注入(inject)和模拟,并且 User 类不再是硬编码的。您可能会或可能不会认为这有点矫枉过正,但它确实提高了可模拟性。

尽管这个工厂可以很好地使用静态方法实例化实际的用户对象,但这并没有改变这个事实:

public function byPhone($id) {
    return User::getByPhone($this->db, $id);
}

这里使用静态方法或常规构造函数没有区别。

$user = new User($db, $id);
$user = User::getByPhone($db, $id);

两个表达式都返回 User 的实例,并且都“硬编码”了 User 类。无论如何,这只需要在某个时候发生。

对于我的用例,static 构造函数方法对对象最有意义。正如所证明的那样,static 方法不是问题所在。在哪里调用它们是争论的焦点,而不是它们是否存在。而且我还没有看到不使用静态构造函数的令人信服的论据,因为它们可以包装在工厂中,这减轻了可模拟性的任何问题,就像它对常规对象实例化所做的一样。

关于php - "Static methods are death to testability"- 替代构造函数的替代品?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8966434/

相关文章:

java - 这是使用 Enum 的正确方法吗?

PHP内部编码

php - 如果条目被修改,则从文本文件更新数据库并添加任何其他新条目

c# - 比较具有不同项目类型的集合

.net - 使用 .NET Core 和 xUnit 同时针对多个框架的单元测试代码

.net - 如何在使用 MS Test 时加载 web.config

python - 类构造函数应该返回子类吗?

java - 在这种特殊情况下如何最大限度地减少对象创建?

javascript - 添加额外年份时,将 php 日期添加到 JS 函数中不起作用

php - 查询 1000 万个 mongodb 文档