MEF 和 NUnit 单元测试

4
几周前,我加入了MEF(ComponentModel)的行列,现在正在将其用于我的许多插件和共享库。总体来说,它很棒,但是由于我经常犯错误,导致令人沮丧的调试会话,这是唯一的缺点。
无论如何,我的应用程序运行得很好,但是与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中?)
3个回答

11

听起来你做得很对。单元测试应该测试一个单元,当你直接创建实例时就是这样的。如果你让MEF为你组合实例,它们会倾向于集成测试。并不是说集成测试有什么问题,但是单元测试更容易维护,因为你可以独立地测试每个单元。

在单元测试中,你不需要容器来连接实例

我通常不建议在SetUp中组合Fixture,因为这会导致General Fixture反模式。

最佳实践是用测试替身替换依赖项。动态模拟是其中一种更多用途的方法,你一定要学会。


那是一些很好的建议,马克。非常感谢你的贡献! - Dave
我阅读了您发布的文章,它们非常有启发性。因此,看起来我的当前测试应该创建另一个ICandyInterface测试存根(或模拟对象?我还没有看到区别),它只返回我期望从数据库中获取的任何数据,而不实际使用数据库。 - Dave

0
我在博客中介绍了如何使用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/


0
我同意手动创建DOCs比使用MEF组合容器来满足导入要好得多,但是关于“在设置中组合装置会导致常规装置反模式”这个注释 - 我想提一句,情况并非总是如此。
如果您正在使用静态容器并通过CompositionInitializer.SatisfyImports来满足导入,那么您将不得不面对常规装置反模式,因为CompositionInitializer.Initialize不能被调用超过一次。然而,您始终可以创建一个CompositionContainer,添加目录,并在容器本身上调用SatisyImportOnce。在这种情况下,您可以在每个测试中使用一个新的CompositionContainer,并且可以避免面对共享/常规装置反模式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接