MEF 和使用 NUnit 进行单元测试
几周前,我加入了 MEF(组件模型)潮流,现在将它用于我的许多插件和共享库。总的来说,除了我经常犯的错误(导致调试过程令人沮丧)之外,它还是很棒的。
无论如何,我的应用程序运行得很好,但我的 MEF 相关代码更改导致我的自动构建失败。我的大多数单元测试都失败了,因为我正在测试的模块依赖于需要由 MEF 加载的其他模块。我通过绕过 MEF 并直接实例化这些对象来解决这些情况。
换句话说,通过 MEF 我会有类似的东西
[Import]
public ICandyInterface ci { get; set; }
,
[Export(typeof(ICandyInterface))]
public class MyCandy : ICandyInterface
{
[ImportingConstructor]
public MyCandy( [Import("name_param")] string name) {}
...
}
但在我的单元测试中,我只会使用
CandyInterface MyCandy = new CandyInterface( "Godiva");
此外,CandyInterface 需要连接到数据库,我通过将测试数据库添加到我的单元测试文件夹来解决这个问题,并且我让 NUnit 将其用于所有测试。
好的,这是我关于这种情况的问题:
- 这是一种不好的做事方式吗?
- 你会建议在[SetUp]中编写部件吗?
- 我还没有学会如何在单元测试中使用模拟——这是一个很好的例子,我可能想模拟底层数据库连接(以某种方式)只返回虚拟数据并且并不真正需要数据库?
- 如果您以前遇到过类似的情况,您能提供一下您的经验以及解决问题的方法吗? (或者这应该进入社区维基?)
A few weeks ago I jumped on the MEF (ComponentModel) bandwagon, and am now using it for a lot of my plugins and also shared libraries. Overall, it's been great aside from the frequent mistakes on my part, which result in frustrating debugging sessions.
Anyhow, my app has been running great, but my MEF-related code changes have caused my automated builds to fail. Most of my unit tests were failing simply because the modules I was testing were dependent upon other modules that needed to be loaded by MEF. I worked around these situations by bypassing MEF and directly instantiating those objects.
In other words, via MEF I would have something like
[Import]
public ICandyInterface ci { get; set; }
and
[Export(typeof(ICandyInterface))]
public class MyCandy : ICandyInterface
{
[ImportingConstructor]
public MyCandy( [Import("name_param")] string name) {}
...
}
But in my unit tests, I would just use
CandyInterface MyCandy = new CandyInterface( "Godiva");
In addition, the CandyInterface requires a connection to a database, which I have worked around by just adding a test database to my unit test folder, and I have NUnit use that for all of the tests.
Ok, so here are my questions regarding this situation:
- Is this a Bad Way to do things?
- Would you recommend composing parts in [SetUp]
- I haven't yet learned how to use mocks in unit testing -- is this a good example of a case where I might want to mock the underlying database connection (somehow) to just return dummy data and not really require a database?
- If you've encountered something like this before, can you offer your experience and the way you solved your problem? (or should this go into the community wiki?)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
听起来你走在正确的轨道上。单元测试应该测试一个单元,这就是您直接创建实例时所做的事情。如果您让 MEF 为您编写实例,它们会倾向于集成测试。并不是说集成测试有什么问题,而是单元测试往往更易于维护,因为您单独测试每个单元。
您不需要容器来连接单元测试中的实例。
我通常建议不要在 SetUp 中编写 Fixture,因为它会导致 General Fixture 反模式。
最佳实践是使用 测试替身 替换依赖项。动态模拟是执行此操作的更通用的方法之一,因此绝对是您应该学习的东西。
It sounds like you are on the right track. A unit test should test a unit, and that's what you do when you directly create instances. If you let MEF compose instances for you, they would tend towards integration tests. Not that there's anything wrong with integration tests, but unit tests tend to be more maintainable because you test each unit in isolation.
You don't need a container to wire up instances in unit tests.
I generally recommend against composing Fixtures in SetUp, as it leads to the General Fixture anti-pattern.
It is best practice to replace dependencies with Test Doubles. Dynamic mocks is one of the more versatile ways of doing this, so definitely something you should learn.
我同意手动创建 DOC 比使用 MEF 组合容器来满足导入要好得多,但关于注释“在设置中组合固定装置会导致一般固定装置反模式” - 我想提一下,情况并非总是如此。
如果您使用静态容器并通过 CompositionInitializer.SatisfyImports 满足导入,您将不得不面对常规固定装置反模式,因为 CompositionInitializer.Initialize 不能被多次调用。但是,您始终可以创建 CompositionContainer、添加目录并在容器本身上调用 SatisyImportOnce。在这种情况下,您可以在每个测试中使用新的 CompositionContainer 并摆脱面对共享/通用固定装置反模式的困扰
I agree that creating the DOCs manually is much better than using MEF composition container to satisfy imports, but regarding the note 'compositing fixtures in setup leads to the general fixture anti pattern' - I want to mention that that's not always the case.
If you’re using the static container and satisfy imports via CompositionInitializer.SatisfyImports you will have to face the general fixture anti pattern as CompositionInitializer.Initialize cannot be called more than once. However, you can always create CompositionContainer, add catalogs, and call SatisyImportOnce on the container itself. In that case you can use a new CompositionContainer in every test and get away with facing the shared/general fixture anti pattern
我在博客中介绍了如何使用 MEF 进行单元测试(不是 nunit,但工作原理相同)。
诀窍是使用 MockExportProvider,我为所有测试创建了一个测试库来继承。
这是我的主要 AutoWire 功能,适用于集成和单元测试:
我的帖子的更多信息:https://yoavniran.wordpress.com/2012/10/18/unit-testing-wcf-and-mef/
I blogged on how to do unit tests (not nunit but works just the same) with MEF.
The trick was to use a MockExportProvider and i created a test base for all my tests to inherit from.
This is my main AutoWire function that works for integration and unit tests:
More info on my post: https://yoavniran.wordpress.com/2012/10/18/unit-testing-wcf-and-mef/