单元测试和静态方法

发布于 2024-11-07 03:24:52 字数 979 浏览 0 评论 0原文

阅读并学习单元测试,尝试理解 下面的文章解释了静态函数调用的困难。

我不太清楚这个问题。我一直认为静态函数是在类中汇总实用函数的好方法。例如,我经常使用静态函数调用来初始化,即:

Init::loadConfig('settings.php');
Init::setErrorHandler(APP_MODE); 
Init::loggingMode(APP_MODE);

// start loading app related objects ..
$app = new App();

// 读完这篇文章后,我现在的目标是这个......

$init = new Init();
$init->loadConfig('settings.php');
$init->loggingMode(APP_MODE);
 // etc ...

但是,我为此类编写的几十个测试是相同的。我什么也没改变,它们仍然全部通过。我做错了什么吗?

该帖子的作者声明如下:

静态方法的基本问题是它们是过程代码。我不知道如何对程序代码进行单元测试。单元测试假设我可以单独实例化我的应用程序的一部分。在实例化过程中,我将依赖项与模拟/友好连接起来,以替换真正的依赖项。对于过程式编程,由于没有对象,因此无需“连接”任何内容,代码和数据是分开的。

现在,我从帖子中了解到静态方法创建依赖关系,但没有直观地理解为什么不能测试静态方法的返回值和常规方法一样容易吗?

我将避免静态方法,但我希望了解静态方法何时有用(如果有的话)。从这篇文章看来,静态方法与全局变量一样邪恶,应该尽可能避免。

有关该主题的任何其他信息或链接将不胜感激。

Reading up and picking up on unit testing, trying to make sense of the following post on that explains the hardships of static function calls.

I don't clearly understand this issue. I have always assumed static functions were a nice way of rounding up utility functions in a class. For example, I often use static functions calls to initialise, ie:

Init::loadConfig('settings.php');
Init::setErrorHandler(APP_MODE); 
Init::loggingMode(APP_MODE);

// start loading app related objects ..
$app = new App();

// After reading the post, I now aim for this instead ...

$init = new Init();
$init->loadConfig('settings.php');
$init->loggingMode(APP_MODE);
 // etc ...

But, the few dozen tests I had written for this class are the same. I changed nothing and they still all pass. Am I doing something wrong?

The author of the post states the following:

The basic issue with static methods is they are procedural code. I have no idea how to unit-test procedural code. Unit-testing assumes that I can instantiate a piece of my application in isolation. During the instantiation I wire the dependencies with mocks/friendlies which replace the real dependencies. With procedural programing there is nothing to “wire” since there are no objects, the code and data are separate.

Now, I understand from the post that static methods create dependencies, but don't grasp intuitively why one cannot test the return value of a static method just as easily as a regular method?

I will be avoiding static methods, but I would of liked having an idea of WHEN static methods are useful, if at all. It seems from this post static methods are just about as evil as global variables and should be avoided as much as possible.

Any additional information or links on the subject would be greatly appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

蝶…霜飞 2024-11-14 03:24:52

静态方法本身并不比实例方法更难测试。当方法(静态或其他方式)调用其他静态方法时,就会出现问题,因为您无法隔离正在测试的方法。以下是一个难以测试的典型示例方法:

public function findUser($id) {
    Assert::validIdentifier($id);
    Log::debug("Looking for user $id");  // writes to a file
    Database::connect();                 // needs user, password, database info and a database
    return Database::query(...);         // needs a user table with data
}

您可能想用此方法测试什么?

  • 传递除正整数以外的任何值都会引发 InvalidIdentifierException
  • Database::query() 收到正确的标识符。
  • 找到时返回匹配的 User,未找到时返回 null

这些要求很简单,但您还必须设置日志记录、连接到数据库、加载数据等。Database 类应该单独负责测试它是否可以连接和查询。 Log 类应该对日志记录执行相同的操作。 findUser() 不应该处理任何这些,但它必须处理,因为它依赖于它们。

相反,如果上面的方法调用 DatabaseLog 实例上的实例方法,则测试可以传入具有特定于当前测试的脚本化返回值的模拟对象。

function testFindUserReturnsNullWhenNotFound() {
    $log = $this->getMock('Log');  // ignore all logging calls
    $database = $this->getMock('Database', array('connect', 'query');
    $database->expects($this->once())->method('connect');
    $database->expects($this->once())->method('query')
             ->with('<query string>', 5)
             ->will($this->returnValue(null));
    $dao = new UserDao($log, $database);
    self::assertNull($dao->findUser(5));
}

如果 findUser() 忽略调用 connect(),为 $id 传递错误的值 (5< /code> 上面),或者返回 null 以外的任何内容。优点在于不涉及数据库,使测试快速而稳健,这意味着它不会因与测试无关的原因(例如网络故障或错误的样本数据)而失败。它使您能够专注于真正重要的事情:findUser() 中包含的功能。

Static methods themselves aren't harder to test than instance methods. The trouble arises when a method--static or otherwise--calls other static methods because you cannot isolate the method being tested. Here is a typical example method that can be difficult to test:

public function findUser($id) {
    Assert::validIdentifier($id);
    Log::debug("Looking for user $id");  // writes to a file
    Database::connect();                 // needs user, password, database info and a database
    return Database::query(...);         // needs a user table with data
}

What might you want to test with this method?

  • Passing anything other than a positive integer throws InvalidIdentifierException.
  • Database::query() receives the correct identifier.
  • A matching User is returned when found, null when not.

These requirements are simple, but you must also setup logging, connect to a database, load it with data, etc. The Database class should be solely responsible for testing that it can connect and query. The Log class should do the same for logging. findUser() should not have to deal with any of this, but it must because it depends on them.

If instead the method above made calls to instance methods on Database and Log instances, the test could pass in mock objects with scripted return values specific to the test at hand.

function testFindUserReturnsNullWhenNotFound() {
    $log = $this->getMock('Log');  // ignore all logging calls
    $database = $this->getMock('Database', array('connect', 'query');
    $database->expects($this->once())->method('connect');
    $database->expects($this->once())->method('query')
             ->with('<query string>', 5)
             ->will($this->returnValue(null));
    $dao = new UserDao($log, $database);
    self::assertNull($dao->findUser(5));
}

The above test will fail if findUser() neglects to call connect(), passes the wrong value for $id (5 above), or returns anything other than null. The beauty is that no database is involved, making the test quick and robust, meaning it won't fail for reasons unrelated to the test like network failure or bad sample data. It allows you to focus on what really matters: the functionality contained within findUser().

╰◇生如夏花灿烂 2024-11-14 03:24:52

Sebastian Bergmann 同意 Misko Hevery 的观点,并经常引用他的观点:

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

静态方法的主要问题是它们引入了耦合,通常是通过对依赖关系进行硬编码到您使用的代码中,使得在单元测试中很难用存根或模拟来替换它们。这违反了开放/封闭原则依赖倒置原则,其中两个SOLID 原则

静态被认为是有害的,您说得完全正确。避开他们。

请检查链接以获取更多信息。

更新:请注意,虽然静态仍然被认为是有害的,从 PHPUnit 4.0 开始,已删除存根和模拟静态方法的功能

Sebastian Bergmann agrees with Misko Hevery and quotes him frequently:

Unit-Testing needs seams, seams is where we prevent the execution of normal code path and is how we achieve isolation of the class under test. Seams work through polymorphism, we override/implement class/interface and then wire the class under test differently in order to take control of the execution flow. With static methods there is nothing to override. Yes, static methods are easy to call, but if the static method calls another static method there is no way to override the called method dependency.

The main issue with static methods is that they introduce coupling, usually by hardcoding the dependency into your consuming code, making it difficult to replace them with stubs or mocks in your Unit-Tests. This violates the Open/Closed Principle and the Dependency Inversion Principle, two of the SOLID principles.

You are absolutely right that statics are considered harmful. Avoid them.

Check the links for additional information please.

Update: note that while statics are still considered harmful, the capability to stub and mock static methods has been removed as of PHPUnit 4.0

变身佩奇 2024-11-14 03:24:52

测试静态方法时我没有看到任何问题(至少在非静态方法中不存在任何问题)。

  • 使用依赖注入将模拟对象传递给正在测试的类。
  • 模拟静态方法可以使用合适的自动加载器或操作include_path传递给被测类。
  • 后期静态绑定处理调用同一类中静态方法的方法。

I do not see any problem when testing static methods (at least none that doesn't exists in non-static methods).

  • Mock objects are passed to classes under test using dependency injection.
  • Mock static methods can be passed to classes under test using a suitable autoloader or manipulating the include_path.
  • Late static binding deals with methods calling static methods in the same class.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文