【发布时间】:2015-09-06 01:54:51
【问题描述】:
我正在开发的 WPF 应用程序将有许多插件来扩展其功能。每个插件将包含一个或多个程序集(通常是“主”插件程序集,其中包含用于表示层组件和视图的单独程序集),但应用程序会将其视为单个 Prism 模块。 Prism 将用于发现和加载加载项,MEF 用于确保 Prism 模块可以访问加载项相关程序集中的依赖项。 Prism 模块的 Initialize 方法将负责配置 IoC 容器(在本例中为 Unity)。在已部署的场景中,这一切都将在启动时使用MefBootstrapper 进行加载和管理。
我在尝试对加载项进行单元测试时出现问题。为了使加载项代码与主应用程序半隔离,每个加载项也将有自己的单元测试程序集。这些测试程序集之一将负责检查服务在 IoC 容器中的注册。在测试场景中,我不想使用引导程序来加载 Prism 模块,因为它们具有我不想引入我的测试程序集的依赖项。因此,我编写了我的测试夹具基类,以便它创建自己的MefModuleManager 来加载要测试的模块。
ResolutionTestBase.cs
public abstract class ResolutionTestBase
{
[ClassInitialize]
public static void TestFixtureInitialise(TestContext context)
{
// Create the main resolution container.
var container = new UnityContainer();
// Install the service locator.
var locator = new UnityServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => locator);
}
// Here go some helper methods for performing resolution tests.
protected IUnityContainer Container
{
get { return ServiceLocator.Current.GetService(typeof(IUnityContainer)) as IUnityContainer; }
}
}
AddInResolutionTestBase.cs
public abstract class AddInResolutionTestBase:ResolutionTestBase
{
static AddInResolutionTestBase()
{
Logger = new EmptyLogger();
}
[TestInitialize]
public virtual void TestInitialise()
{
// Create MEF catalog.
var aggregateCatalog = new AggregateCatalog();
foreach (var testAssembly in TestAssemblies)
{
aggregateCatalog.Catalogs.Add(new AssemblyCatalog(testAssembly));
}
// Load module manager.
var container = new CompositionContainer(aggregateCatalog);
var serviceLocator = new MefServiceLocatorAdapter(container);
var parts = new DownloadedPartCatalogCollection();
var moduleInitialiser = new MefModuleInitializer(serviceLocator, Logger, parts, aggregateCatalog);
var moduleManager = new MefModuleManager(moduleInitialiser, ModuleCatalog, Logger);
moduleManager.ModuleTypeLoaders = new[] { new MefFileModuleTypeLoader() };
moduleManager.Run();
}
protected static ILoggerFacade Logger { get; private set; }
protected abstract IModuleCatalog ModuleCatalog { get; }
protected abstract IEnumerable<Assembly> TestAssemblies { get; }
}
对于插件,它们的主程序集中有一个独立的类来实现 Prism 模块的要求,以及用于配置容器的 Unity 扩展。
模块.cs
[ModuleExport("AddInModule", typeof(Module), InitializationMode = InitializationMode.OnDemand)]
public class Module : IModule
{
private readonly IEnumerable<IUnityContainerExtensionConfigurator> _extensions;
[ImportingConstructor]
public Module([ImportMany]IEnumerable<IUnityContainerExtensionConfigurator> extensions)
{
_extensions = extensions;
}
public void Initialize()
{
// Load the dependency injection container.
var container = ServiceLocator.Current.GetService(typeof(IUnityContainer)) as IUnityContainer;
if (container != null)
{
foreach (var extension in _extensions)
{
container.AddExtension((UnityContainerExtension) extension);
}
}
}
}
ContainerInstallerExtension.cs(在加载项的主程序集中)
[Export(typeof(IUnityContainerExtensionConfigurator))]
public class ContainerInstallerExtension : UnityContainerExtension
{
protected override void Initialize()
{
// perform container configuration here.
}
}
PresentationInstallerExtension.cs(在外接程序的演示程序集中)
[Export(typeof(IUnityContainerExtensionConfigurator))]
public class PresentationInstallerExtension:UnityContainerExtension
{
protected override void Initialize()
{
// perform container configuration here.
}
}
AddInResolutionTest.cs(在外接程序的 IoC 测试程序集中)
[TestClass]
public class AddInResolutionTest : AddInResolutionTestBase
{
private IEnumerable<Assembly> _testAssemblies;
private IModuleCatalog DoGetModuleCatalog()
{
var moduleInfo = new ModuleInfo("AddInModule", typeof (Module).AssemblyQualifiedName)
{
InitializationMode = InitializationMode.WhenAvailable,
Ref = typeof (Module).Assembly.CodeBase
};
return new ModuleCatalog(new[] {moduleInfo});
}
protected override IModuleCatalog ModuleCatalog
{
get { return DoGetModuleCatalog(); }
}
protected override IEnumerable<Assembly> TestAssemblies
{
get { return _testAssemblies ?? (_testAssemblies = new[] { typeof(ContainerInstallerExtension).Assembly, typeof(PresentationInstallerExtension).Assembly }); }
}
[TestMethod]
public void ResolveSomeService()
{
// perform resolution test here.
}
}
值得注意的是分辨率测试夹具,“测试程序集”通过项目引用链接到 IoC 测试程序集,并按类型直接引用(而不是使用目录扫描目录),因此我可以避免使用生成后事件将程序集复制到公共文件夹以进行测试。
当我(按原样)运行单元测试时,我收到一个异常,表明模块管理器未能加载 Prism 模块:
初始化方法 AddInResolutionTest.TestInitialise 抛出异常。 Microsoft.Practices.Prism.Modularity.ModuleTypeLoadingException:Microsoft.Practices.Prism.Modularity.ModuleTypeLoadingException:无法加载模块 AddInModule 的类型。
如果在 Silverlight 应用程序中使用 MEF 时发生此错误,请确保对 MefExtensions 程序集的引用的 CopyLocal 属性在主应用程序/shell 中设置为 true,在所有其他程序集中设置为 false。
错误是:对象引用未设置为对象的实例.. ---> System.NullReferenceException:对象引用未设置为对象的实例.. 在 Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.LoadModuleType(ModuleInfo moduleInfo) --- 内部异常堆栈跟踪结束 --- 在 Microsoft.Practices.Prism.Modularity.ModuleManager.HandleModuleTypeLoadingError(ModuleInfo moduleInfo,Exception 异常) 在 Microsoft.Practices.Prism.Modularity.ModuleManager.IModuleTypeLoader_LoadModuleCompleted(对象发件人,LoadModuleCompletedEventArgs e) 在 Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.RaiseLoadModuleCompleted(LoadModuleCompletedEventArgs e) 在 Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.RaiseLoadModuleCompleted(ModuleInfo 模块信息,异常错误) 在 Microsoft.Practices.Prism.MefExtensions.Modularity.MefFileModuleTypeLoader.LoadModuleType(ModuleInfo moduleInfo) 在 Microsoft.Practices.Prism.Modularity.ModuleManager.BeginRetrievingModule(ModuleInfo moduleInfo) 在 Microsoft.Practices.Prism.Modularity.ModuleManager.LoadModuleTypes(IEnumerable`1 模块信息) 在 Microsoft.Practices.Prism.Modularity.ModuleManager.LoadModulesWhenAvailable() 在 Microsoft.Practices.Prism.Modularity.ModuleManager.Run() 在 AddInResolutionTestBase.cs 中的 AddInResolutionTestBase.TestInitialise():第 xx 行
在调用 moduleManager.Run() 时,我的代码中没有任何内容为空,所以我不清楚“真正的”问题是什么。
我尝试了各种更改来解决问题,包括:
- 在 AddInResolutionTestBase.cs 中调用
moduleManager.LoadModule()而不是moduleManager.Run() - 操作AddInResolutionTest中创建的
ModuleInfo的State绕过模块管理器中的问题
我所做的任何其他更改都导致了不同的错误,但仍然表明模块管理器尝试加载 Prism 模块时出现问题。
是否需要一些额外的步骤才能正确配置模块管理器以便能够以这种方式加载模块,同时记住单元测试不需要一些通常的开销(例如记录器)?
【问题讨论】:
标签: c# unit-testing mstest mef prism-4