【问题标题】:MEF and unit testing with NUnit使用 NUnit 进行 MEF 和单元测试
【发布时间】:2011-02-20 12:33:42
【问题描述】:

几周前,我加入了 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 将它用于所有测试。

好的,下面是我关于这种情况的问题:

  1. 这是一种糟糕的做事方式吗?
  2. 您是否建议在 [SetUp] 中组成部分
  3. 我还没有学会如何在单元测试中使用模拟——这是一个很好的例子,我可能想模拟底层数据库连接(以某种方式)只返回虚拟数据而不真正需要数据库?
  4. 如果您以前遇到过这样的事情,您能提供您的经验和您解决问题的方法吗? (或者这应该进入社区 wiki 吗?)

【问题讨论】:

    标签: unit-testing mef system.componentmodel


    【解决方案1】:

    我在博客上写过如何使用 MEF 进行单元测试(不是 nunit,但工作方式相同)。 诀窍是使用 MockExportProvider,然后我为我的所有测试创建了一个测试库来继承。

    这是我的主要 AutoWire 功能,适用于集成和单元测试:

    protected void AutoWire(MockExportProvider mocksProvider, params Assembly[] assemblies){
    
    CompositionContainer container = null;
    
    var assCatalogs = new List<AssemblyCatalog>();
    
    foreach(var a in assemblies)
    {
        assCatalogs.Add(new AssemblyCatalog(a));
    }
    
    if (mocksProvider != null)
    {
        var providers = new List<ExportProvider>();
    
        providers.Add(mocksProvider); //need to use the mocks provider before the assembly ones            
    
        foreach (var ac in assCatalogs)
        {
            var assemblyProvider = new CatalogExportProvider(ac);                    
            providers.Add(assemblyProvider);
        }
    
        container = new CompositionContainer(providers.ToArray());
    
        foreach (var p in providers) //must set the source provider for CatalogExportProvider back to the container (kinda stupid but apparently no way around this)
        {
            if (p is CatalogExportProvider)
            {
                ((CatalogExportProvider)p).SourceProvider = container;
            }
        }
    }
    else
    {
        container = new CompositionContainer(new AggregateCatalog(assCatalogs));
    }
    
    container.ComposeParts(this);        
    }
    

    关于我的帖子的更多信息:https://yoavniran.wordpress.com/2012/10/18/unit-testing-wcf-and-mef/

    【讨论】:

      【解决方案2】:

      我同意手动创建 DOC 比使用 MEF 组合容器来满足导入要好得多,但关于“在设置中组合夹具会导致一般夹具反模式”的注释 - 我想提一下,情况并非总是如此.

      如果您使用静态容器并通过 CompositionInitializer.SatisfyImports 满足导入,您将不得不面对一般的夹具反模式,因为 CompositionInitializer.Initialize 不能被多次调用。但是,您始终可以在容器本身上创建 CompositionContainer、添加目录和调用 SatisyImportOnce。在这种情况下,您可以在每个测试中使用新的 CompositionContainer 并摆脱面对共享/通用夹具反模式

      【讨论】:

        【解决方案3】:

        听起来你是在正确的轨道上。单元测试应该测试一个unit,这就是你直接创建实例时所做的。如果您让 MEF 为您编写实例,它们将倾向于集成测试。并不是说集成测试有什么问题,而是单元测试往往更易于维护,因为您单独测试每个单元。

        You don't need a container to wire up instances in unit tests.

        我通常建议不要在 SetUp 中编写 Fixtures,因为它会导致 General Fixture 反模式。

        最好用Test Doubles 替换依赖项。动态模拟是执行此操作的更通用的方法之一,因此绝对是您应该学习的东西。

        【讨论】:

        • 这是一些很棒的建议,马克。非常感谢您的意见!
        • 我阅读了您发布的文章,内容非常丰富。因此,听起来我当前创建依赖于 SQLite 的对象的测试应该有另一个 ICandyInterface 测试存根(或模拟对象?我还没有看到区别),它只返回我希望从数据库获得的任何数据,没有实际使用数据库。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-20
        • 2017-04-04
        • 1970-01-01
        • 2010-09-15
        • 1970-01-01
        • 2010-10-17
        相关资源
        最近更新 更多