在为该对象编写类之前为该对象编写模拟/存根?

发布于 2024-12-27 15:06:09 字数 543 浏览 0 评论 0原文

我正在设计一个具有两个依赖项的类。依赖类之一已编写并测试。另一个还没写。

我突然想到,因为剩下的依赖项将被编写以方便使用它的类,所以我应该先编写后者,然后设计前者的接口,学习它应该做什么。

在我看来,这是编写代码的好方法。毕竟,只要主类在其构造函数中获得模拟,我就可以编写它并测试它,而无需意识到它的依赖项不存在,然后一旦我确定我知道我需要什么,我就可以创建依赖项。

那么:我该怎么做?创建一个骨架类,我会在进行过程中对其进行修改。也许是这样的:

class NonExistantSkeleton
{
    public function requiredMethod1()
    {
    }

    public function newlyDiscoveredRequirement()
    {
    }
}

然后使用 PHPUnit 模拟它,并设置存根等,以使我的类在开发中保持愉快?

这是要走的路吗?

这似乎是一种开发代码的好方法 - 在我看来,这比开发依赖项更有意义,而无需真正确定它将如何使用。

I'm designing a class that has two dependencies. One of the dependency classes has been written and tested. The other has not yet been written.

It has occurred to me because the remaining dependency will be written to facilitate the class that will use it, that I should write the latter first, and design the interface of the former as I go along, learning what it should do.

That seems to me to be a great way to make code. After all, as long as the main class gets a mock in its constructor, I can write it and test it without it being aware that its dependency doesn't exists, then I can create the dependency once I am sure I know what I need.

So: how do I do this? Create a skeleton class that I modify as I go along. Perhaps something like:

class NonExistantSkeleton
{
    public function requiredMethod1()
    {
    }

    public function newlyDiscoveredRequirement()
    {
    }
}

and then mock it using PHPUnit, and setting up stubs, etc, to keep my class under development happy?

Is this the way to go?

It seems like a nice way to develop code - and seems to me to make more sense than developing a dependency, without really knowing for sure how it's going to be used.

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

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

发布评论

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

评论(2

怪异←思 2025-01-03 15:06:09

简而言之:

是的。至少这就是我现在正在做的事情。


更长的版本:

如果您的类的预期协作者在您正在构建的类的测试中需要它们的时间点不存在,您有几个选项

  • 模拟不存在的类( phpunit 可以做到这一点)
  • 创建类骨架并模拟它们
  • 只需创建接口并获取这些接口的模拟(phpunit 也可以做到)
  • 也许您不需要上述任何内容,具体取决于对象

如果你针对接口进行编程无论如何,您需要做的就是创建该接口并告诉 PHPUnit 从它创建一个存根/模拟

  • +没有测试就没有新类
  • +在适当的时候使用接口被认为更好/比仅仅暗示类更好

模拟不存在的类时,您会得到一些我不喜欢的缺点:

  • - 高模拟维护成本
  • - 更改该类上的方法既缓慢又乏味
  • - 如果您创建了你应该上的课再次修改模拟,

所以我建议不要这样做。

中间方法是仅使用其方法创建空类骨架并使用它们进行模拟。

在没有界面提示的情况下,我非常喜欢这种方式,因为它速度很快并且可以创建稳定的测试代码。

对我来说,拥有带有公共 API 的准系统类并不违反 TDD。


有些类你不需要模拟。

数据传输对象值对象 始终可以在生产代码中使用 new 在任何地方创建,以便您的测试也可以只是真实的物体

它有助于使您的测试更加干净,因为您不需要模拟/期望大量的 getter/setter 方法等等。

In short:

Yes. At least thats what I'm doing right now.


Longer Version:

If the expected collaborators of your class don't exist at the point in time where you need them in your tests for the class you are building you have a few options:

  • Mock non existing classes (which phpunit can do)
  • Create class skeletions and mock those
  • Just create interfaces and get mocks for those (which phpunit can do too)
  • Maybe you don't need any of the above depending on the object

If you programm against an interface anyways than all you need to do is to create that interface and tell PHPUnit to create a stub/mock from it

  • +No new class without a test
  • +Using interfaces when appropriate is considered nicer/better than just hinting against classes

When mocking non existing classes you get some drawbacks that I don't like:

  • -High mock maintenance cost
  • -Chaning the methods on that classes is slow and tedious
  • -If you created the class you should rework the mocks again

so I'd advice against that.

The middle way would be to just create the empty class skeleton with its method and use those for mocking.

I quite like that way in cases where there is no interface to hint against as It is fast and creates stable test code.

Having barebone classes with public apis, for me, is no violation of TDD.


There are classes you don't need to mock.

Data transfer objects and Value Objects can always be created anywhere using the new in your production code so your tests also can just the the real objects.

It helps to keep your tests a little cleaner as you don't need to mock/expect a lot of getter/setter methods and so on.

梦纸 2025-01-03 15:06:09

如果您遵循那么通常的方法如下:

  1. 弄清楚你的类要做什么,以及它们面向公众的 API 应该是什么。
  2. 实现“空”类,该类仅包含具有空主体的公共方法签名(正如您在给出的代码示例中所做的那样)。
  3. 制定实施策略。这意味着找出哪些类相互依赖,并按照一定的顺序实现它们,这意味着依赖的类只有在它所依赖的类完成或至少具有足够的功能来进行开发之前才会实现。这意味着首先处理没有依赖关系的类,然后处理仅依赖于已完成的类的类,依此类推。
  4. 编写你的测试。现在就可以编写测试,因为您知道类的黑匣子是什么样子,它们需要将什么作为输入以及它们应该返回什么作为输出。
  5. 运行测试。您应该获得 0% 的成功,但也应该获得 100% 的代码覆盖率。现在这是您的基线。
  6. 根据您的实施策略开始实施您的课程。在此过程中不时运行单元测试,例如完成一个类后,以确保它符合单元测试中规定的规范。理想情况下,每个测试都应显示测试通过次数的增加,同时保持 100% 的代码覆盖率。

编辑:正如 edorian 指出的那样,PHP 接口在这里提供了巨大的帮助,因为 PHPUnit 可以从接口以及类生成模拟和存根。它们也是减少耦合和提高可替代性的绝佳工具。它们允许您替换任何实现预期接口的类,而不仅仅是预期类的子类。

If you follow a development methodology then the usual approach is as follows:

  1. Figure out what your classes are meant to do, and what their public-facing APIs should be.
  2. Implement "empty" classes that consist of nothing but the public methods signitures with empty bodies (as you have done in the code example you gave).
  3. Work out an implementation strategy. This means working out which classes are dependant on each other and implementing them in an order that means that dependant classes aren't implemented until the classes it depends on are finished, or at least sufficiently functional to develop against. This means doing the classes with no dependencies first, then the classes that depend only on the completed classes, and so on.
  4. Write your tests. It's possible to write the tests now because you know what the black box for your classes look like, what they need to take as input and what they're supposed to return as output.
  5. Run the tests. You should get 0% success, but also 100% code coverage. This is now your baseline.
  6. Start to implement your classes according to your implementation strategy. Run your unit tests from time to time during this process, say once you've got a class completed, to make sure that it meets its specification as laid down in the unit test. Ideally, each test should show an increase in test passes whilst maintaining 100% code coverage.

EDIT: As edorian pointed out, PHP interfaces are a huge help here because PHPUnit can generate mocks and stubs from interfaces as well as from classes. They're also an excellent tool in reducing coupling and improving substitutability in general. They allow you to substitute any class that implements the expected interface, instead of just subclasses of the expected class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文