如何构建相互构建的鼻子单元测试?
示例
假设您有一个如下所示的假设 API:
import foo
account_name = foo.register()
session = foo.login(account_name)
session.do_something()
关键点是,为了执行 do_something()
,您需要注册并登录。
这是一个过于简化的、首次通过的套件人们可能会写的单元测试:
# test_foo.py
import foo
def test_registration_should_succeed():
foo.register()
def test_login_should_succeed():
account_name = foo.register()
foo.login(account_name)
def test_do_something_should_succeed():
account_name = foo.register()
session = foo.login(account_name)
session.do_something()
问题
当注册失败时,所有测试都会失败,这使得它在哪里变得不明显 真正的问题是。看起来好像一切都坏了,但实际上只有一件至关重要的东西坏了。除非您熟悉所有测试,否则很难找到曾经至关重要的东西。
问题
如何构建单元测试,以便后续测试在其所依赖的核心功能失败时不会执行?
想法
以下是我想到的可能的解决方案。
- 手动检测每个测试中的失败并引发 SkipTest。 - 可行,但需要大量手动且容易出错的工作。
- 当主要生成器发生故障时,利用生成器不产生后续测试。 - 不确定这是否真的有效(因为我如何“知道”先前产生的测试失败)。
- 将测试分组到测试类中。例如,这些都是需要您登录的单元测试。 - 不确定这实际上解决了我的问题。难道不会有同样多的失败吗?
Example
Let's say you have a hypothetical API like this:
import foo
account_name = foo.register()
session = foo.login(account_name)
session.do_something()
The key point being that in order to do_something()
, you need to be registered and logged in.
Here's an over-simplified, first-pass, suite of unit tests one might write:
# test_foo.py
import foo
def test_registration_should_succeed():
foo.register()
def test_login_should_succeed():
account_name = foo.register()
foo.login(account_name)
def test_do_something_should_succeed():
account_name = foo.register()
session = foo.login(account_name)
session.do_something()
The Problem
When registration fails, all the tests fail and that makes it non-obvious where
the real problem is. It looks like everything's broken, but really only one, crucial, thing is broken. It's hard to find that once crucial thing unless you are familiar with all the tests.
The Question
How do you structure your unit tests so that subsequent tests aren't executed when core functionality on which they depend fails?
Ideas
Here are possible solutions I've thought of.
- Manually detect failures in each test and and raise SkipTest. - Works, but a lot of manual, error-prone work.
- Leverage generators to not yield subsequent tests when the primary ones fail. - Not sure if this would actually work (because how do I "know" the previously yielded test failed).
- Group tests into test classes. E.g., these are all the unit tests that require you to be logged in. - Not sure this actually addresses my issue. Wouldn't there be just as many failures?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为更好的答案是使用 模拟对象,而不是回答明确的问题。一般来说,单元测试不应要求访问外部数据库(可能需要登录)。但是,如果您希望进行一些集成测试来执行此操作(这是一个好主意),那么这些测试应该测试集成方面,并且您的单元测试应该测试单位方面。我什至将集成测试和单元测试保存在单独的文件中,以便您可以非常定期快速运行单元测试,并以稍微少一点的速度运行集成测试定期(尽管仍然至少每天一次)。
Rather than answering the explicit question, I think a better answer is to use mock objects. In general, unit tests should not require accessing external databases (as is presumably required to log in). However, if you want to have some integration tests that do this (which is a good idea), then those tests should test the integration aspects, and your unit tests should tests the unit aspects. I would go so far as to keep the integration tests and the unit tests in separate files, so that you can quickly run the unit tests on a very regular basis, and run the integration tests on a slightly less regular basis (although still at least once a day).
这个问题表明 Foo 做得太多了。您需要区分关注点。那么测试就会变得很容易。如果您有一个 Registrator、一个 LoginHandler 和一个 DoSomething 类,以及一个编排工作流程的中央控制器类,那么所有内容都可以单独测试。
This problem indicates that Foo is doing to much. You need to separate concerns. Then testing will become easy. If you had a Registrator, a LoginHandler and a DoSomething class, plus a central controller class that orchestrated the workflow, then everything could be tested separately.