WCF 示例 - 第 X 章 - Spring.Net 依赖注入






4.94/5 (16投票s)
如何使用 Spring.Net DI 来降低耦合并增强自动化测试。
![]() |
![]() |
|
第九章 | 第十一章 |
系列文章
WCF 示例系列文章介绍如何使用 WCF 进行通信和 NHibernate 进行持久化来设计和开发 WPF 客户端。该系列简介描述了文章的范围,并从高层次讨论了架构解决方案。该系列的源代码可在 CodePlex 上找到。
章节概述
在此阶段,该系列文章已涵盖了我们如何为应用程序建立基线,以便我们可以开始向客户展示功能并收集利益相关者和产品所有者的反馈。正如我们在前几章中所解释的那样,对于 RAD 和 TDD 方法论而言,内存实现被证明是一种有价值的方法,可以推迟在业务域更稳定时再开发其他成本更高的后端基础设施组件。
在本章中,我们将解释如何在设计中利用依赖注入,从而为测试、开发和业务探索提供完美的机制。目前,客户端应用程序引用了我们不打算在生产环境中随客户端一起部署的程序集。我们将结合已引入的 ServiceLocator 模式使用 Spring.Net 来移除客户端中的这些硬编码引用;我们还将在测试项目中利用此机制。在后续章节中,我们将展示这种设计如何促进持久化和通信组件的引入。
我们还将花一些时间在测试中介绍 DI,以便能够使用内存存储库或 NHibernate 存储库执行我们的测试用例,而无需更改测试逻辑。
评估
目前,我们在单个进程中运行应用程序,其中客户端、服务器和内存组件都紧密耦合,因为客户端应用程序引用了在生产环境中不会随客户端一起部署的程序集。下图从高层次显示了执行内存客户端时使用的主要组件。
我们需要提供一种机制,以便我们可以“注入”上述实现,同时确保客户端项目不保留对生产环境中不会随附部署的程序集的引用。
为了简化 eDirectory 应用程序的构建,我们将把服务器和模拟引用保留在客户端项目中。然而,在真实的解决方案中,这类引用不应存在,并且它们向 bin 文件夹的部署应由构建机制来解决。
Spring.Net 容器
我们将在客户端和服务器端都使用 DI,因此我们将在公共程序集中创建一个名为 DiContext
的“持有者”类来保存 Spring.Net 容器。
public class DiContext
{
public static XmlApplicationContext AppContext { get; set; }
}
因此,我们需要向我们的程序集添加两个引用。
重构客户端
WCFClient 包含一个用于初始化依赖项的类,名为 eDirectoryBootStrapper
;客户端在此处指示上述组件的实现实例。我们将通过设置 Spring 容器来替换此代码。我们将从 App.config 文件中获取 Spring 文件名,然后使用它来设置容器。以下是重构前后的代码。
客户端不再需要其他更改;只剩下设置 App.config 文件和 Spring 文件。App.config 文件如下所示:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="SpringConfigFile" value="file://InMemoryConfiguration.xml" />
</appSettings>
</configuration>
内存 Spring 配置文件
我们将从应用程序的角度简要讨论 Spring 配置文件,涵盖声明 DI Spring.Net 文件的最常见场景。但是,您可能需要查看参考文档,它相当不错。在 VS 中处理配置文件的一个好习惯是将 schemas
属性设置为 Spring.Net 架构定义文件。
我们需要配置 ClientServiceLocator
,指明它将是 Singleton,以及 Spring.Net 需要用来创建该类实例的方法。我们还指示 CommandDispatcher
属性通过在 CommandDispatcherRef
中声明的实例进行设置。
值得注意的是,即使 setter 是私有的,Spring.Net 也能够设置 CommandDispatcher
属性的引用;这是一个很好的机制,可以确保服务仅由 DI 基础结构注入。CommandDispatcher
声明如下:
我们还需要正确设置 GlobalContext
。如果我们在此不设置 GlobalContext
而运行应用程序,将会抛出以下异常:
让我们定义服务器端的容器。
容器仅公开静态属性,因此我们不需要工厂方法;Spring.Net 允许注入静态引用,如 RequestContext
静态属性。
最后要做的是声明 GlobalContext
和 TransactionManagerFactory
。
然后需要添加 TransFactory
引用;在 InMemory
模式下,我们使用 Naive
实现(顺便说一句,我们将在本章中重命名这些类,因此“EntityStore”的后缀已被替换为“InMemory”)。
重构测试
我们需要重构测试,以便在执行它们时使用 DI;正如我们所指出的,一些 setter 是私有的,以避免滥用服务;这会破坏我们测试的编译。另一方面,在测试中引入 DI 使我们可以非常轻松地针对不同的服务实现运行相同的测试,而无需为不同的执行模式创建不同的版本,这被证明是有益的。我们将在后面的章节中看到,只需更改我们的 DI 配置,就可以针对内存存储库和 NHibernate 存储库执行相同的测试。
最好的方法是认为我们的测试只是另一种客户端;因此,我们需要某种引导程序,类似于客户端上的引导程序,用于初始化 Spring.Net 容器并设置我们的服务。在测试用例中,我们将创建一个名为 TestBootStrapper
的新类,该类使用 AssemblyInitialize
属性。
关于使用此方法的几点说明;为了使该方法正常工作,类需要被标记为 TestClass
属性,并且该方法需要是静态的,接受一个 TestContext
参数。此方法仅在运行测试时执行一次。
我们需要添加与我们在客户端使用的相同的 Spring.Net 引用,并引用 Spring.Net 配置文件。我们使用一个小技巧来在多个项目之间共享文件;您可能需要查看项目文件以了解它是如何实现的。App.config 文件需要与客户端文件以相同的方式进行更改。TestBootStrapper
最终如下所示:
如果我们还记得,服务测试会在构造函数中初始化服务。
有了 Spring.Net 容器,我们可以将代码简化为:
这看起来不算什么大事,但我们的测试委托给 Spring.Net 来设置服务这一事实被证明是非常有益的,正如我们之前所说;从现在开始,如果我们希望我们的测试执行不同的实现,我们不需要更改更多代码。
如果我们在此阶段运行测试,则会检索到以下结果:
CheckFindAllNotication
测试失败的原因是我们的测试执行影响了其他测试。内存存储库在下一个测试执行时不会重新创建,因此客户实例在测试执行之间被保存在内存中;这根本不好。为了证明这一点,如果我们单独运行失败的测试,它会通过;只有在执行多个测试时,测试才会失败。为了解决这个问题,我们需要为我们的测试创建一个基类,该基类确保在测试执行结束时重置内存存储库。
[TestClass]
public abstract class eDirectoryTestBase
{
[TestInitialize]
public virtual void TestsInitialize()
{
}
[TestCleanup]
public virtual void TestCleanUp()
{
ResetLocator();
}
private static void ResetLocator()
{
using (ITransManager manager =
GlobalContext.Instance().TransFactory.CreateManager())
{
manager.ExecuteCommand(locator =>
{
var resetable = locator as IResetable;
if (resetable == null) return null;
resetable.Reset();
return new DtoResponse();
});
}
}
}
代码很简单;当测试完成时,它会调用 ResetLocator
方法,该方法会调用 Locator 中的 Reset
方法;内存实现实现了这个接口。
实现非常简单;它只是丢弃旧的存储库。唯一需要做的就是确保我们的测试继承自新的基类。
如果再次执行测试,我们可以看到所有测试都通过了。
章节总结
在本章中,我们看到了如何使用 Spring.Net DI 消除了客户端应用程序中对服务器和 Naive 组件的硬编码引用;我们还花了一些时间重构测试,使它们也以相同的方式使用 DI。我们已经证明了 DI 如何增强我们的代码,并在使用不同实现时提供一种简洁的机制。
在接下来的两章中,我们将介绍 NHibernate 和 WCF 组件;我们将看到 DI 如何促进新组件与我们在前几章中已经涵盖的组件之间的透明实现。
下一章 涵盖了 NHibernate 存储库的实现,并解释了我们可以如何轻松地切换我们的测试以在不更改测试的情况下运行数据库;这种方法被证明是非常有益的,因为我们可以在编写代码时快速在内存中运行测试,然后持续集成环境可以执行相同的 NHibernate 测试并与数据库交互。不错吧?