【问题标题】:PHP: How to make testable static methodsPHP:如何制作可测试的静态方法
【发布时间】:2016-03-26 14:47:48
【问题描述】:

我决定自己做一个穷人的 ORM 框架。由于我不知道如何去做,我决定在测试之前进行开发。 Github。在让它按照我想要的方式工作之后,我意识到我的代码是不可测试的,因为我的查询类的静态方法和 BaseModel 之间的紧密耦合。

所以我想了一些方法让它可测试:

  1. 让每个查询方法接收一个连接对象:但这意味着当我尝试在 BaseModel 类中使用这些方法时,我仍然必须将连接对象与 BaseModel 紧密耦合。
  2. 保持原样:这意味着在 PHPUnit 测试期间,我将不得不覆盖 BaseModel。

无论哪种方式,我都觉得我做的不对。我相信有更好的方法来完成这项工作,我需要你们的帮助。谢谢

【问题讨论】:

  • 快速浏览了你的代码,看看下面的文章verraes.net/2014/06/when-to-use-static-methods-in-php。你可能需要重构一些东西。好的应用程序从技术设计开始,以避免出现问题并轻松实现单元测试等额外功能
  • 我认为这个问题可能比堆栈溢出更适合 codereview.stackexchange.com。在回答您的问题时,由于多种原因,您基本上不能对依赖于静态的代码进行单元测试。您最好尽可能避免使用静态方法并依赖其他技术,例如依赖注入(传递东西)。静力学有时有其用途,但总的来说,它们造成的问题比解决的问题多。
  • @GordonM,是的,你是绝对正确的,如果你看看我的第一个建议,它是针对依赖注入的,但最终必须消耗静态方法,最终导致紧密耦合。如果你仔细观察你会发现使用静态方法是有意义的,尤其是在使用 BaseModel::getAll()

标签: php unit-testing oop static


【解决方案1】:

Statics are Death to Testability 中引用 Miško Hevery:

这是另一种思考方式。单元测试需要接缝,接缝是我们阻止正常代码路径执行的地方,也是我们实现被测类隔离的方式。接缝通过多态工作,我们覆盖/实现类/接口,然后以不同的方式连接被测类,以控制执行流程。使用静态方法没有什么可以覆盖的。是的,静态方法很容易调用,但是如果静态方法调用另一个静态方法,就无法覆盖被调用的方法依赖。

Static considered harmful 中引用 Kore Nordmann:

如果您开始对代码进行单元测试,您会很快注意到为什么静态依赖关系如此糟糕——因为在测试期间替换实现几乎是不可能的,因此您通常最终要测试整个框架堆栈或需要编写几十个只是为了可测试性的帮助类。如果没有静态依赖项,您将不需要它。

TL;DR:如果您想要可测试的代码,请不要使用静态。

关于您的具体情况:

如果您不想重写代码以不使用静态,请考虑在基类上添加查询类的属性,例如添加类似的东西

public static function setQueryClass($className) {
    static::queryClass = $queryClass;
}

然后将所有对StaticSQLQuery 的硬编码调用替换为对集合$className 的调用。虽然这仍然有点难看,但它允许您在测试期间用不同的类替换 StaticSQLQuery,例如with PHP7 and Anonymous classes 你会把它放到你的测试中:

BaseClass::setQueryClass(
    get_class(new class extends StaticSQLQuery {
        public static function init()
        {
            return 'My stub';
        }
    })
);

在 PHP7 之前,您必须改为对类进行硬编码:

class StaticSQLQueryMock extends StaticSQLQuery
{
    public static function init()
    {
        return 'My stub';
    }
}

BaseClass::setQueryClass(StaticSQLQueryMock::class);

【讨论】:

    【解决方案2】:

    为了测试静态方法,我做了自己的模拟库:https://github.com/shagabutdinov/moka

    以下是如何使用它来测试静态代码的快速示例:

    $classMock = \shagabutdinov\Moka::mockClass('MyClass', ['::method' => 'RESULT']);
    $classMock::method('ARG1'); // RESULT
    $classMock::method('ARG2'); // RESULT
    $classMock::moka->report('method'); // [['ARG1', 'ARG2']]
    

    【讨论】:

      猜你喜欢
      • 2013-03-13
      • 2019-06-12
      • 2017-07-04
      • 2012-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-26
      相关资源
      最近更新 更多