在单元测试时进行企业框架调用
Making an Enterprise Framework Call while Unit Testing
这是我们正在做的事情的基本思路:
- 该网站以表单形式从用户那里收集数据,并将其全部保存到 table 中的一行中,我们将其称为 FooObject。
- 然后将 FooObject 传递到业务逻辑中 class
- 基于 app.config 中的键,工厂 class 将实例化三个具体 classes 之一:
- XMLStringA : IFooInterface
- XML字符串 B : IFooInterface
- XMLStringC : IFooInterface
- 每个class有两个方法:
- 生成XML(FooObject fooObject)
- 解析响应(字符串服务呼叫响应)
- class实例化后,我们调用GenerateXML(),传入FooObject,取回一个XML字符串,然后提交给三个单独的任意一个,外部,第三方网络服务。 (服务的地址也在app.config中。)
- 我们得到响应,并将其发送到 ParseResponse()
一切顺利。但是,C 有一些额外的要求。向此服务提交 XML 时,其中一个 XML 元素需要进行广泛的查找。我们决定将该查找所需的数据添加到我们数据库中的 table。因此,XMLStringC 中有一个私有方法,它使用 EF 进行数据库调用并获取所需的数据以添加到 XML 字符串。
我有点意识到这样做违反了单一职责原则,因为这些 classes 除了构建 XML 字符串之外什么都不应该做。 A 和 B classes 不调用数据库。
当我尝试进行单元测试以测试 A、B 和 C 时,我的所作所为可能是愚蠢的。因为我们不在上下文中,当 运行 单元测试时,C尝试调用数据库时失败。
我不确定在哪里为 C 执行此自定义逻辑。一方面,它仅在我们要提交给 C 服务时发生,因此在 C 中执行此操作很有意义 class.另一方面,我不喜欢从 class 内部进行数据库调用。最终这可能并不重要,如果我能弄清楚如何对其进行单元测试并使其工作。
执行此操作的最佳实践方法是什么?
If I did that, then A,B and C would all need it. But A and B don't care about it. They all implement the same interface.
如果您遵循依赖项注入最佳实践,则您的依赖项不是接口的一部分,而是对象构造函数的一部分。
您的评估是正确的,这违反了 SRP。您需要的是一种执行查找的服务,该服务作为依赖项传递到 C 中。那么您的服务不会违反 SRP,您仍然可以对 XMLStringC
class.
进行单元测试
public class XMLStringB : IFooInterface
{
// No constructor defined here - we have no dependencies
public string GenerateXML(FooObject fooObject)
{
// implementation here
}
public void ParseResponse(string serviceCallResponse)
{
// implementation here
}
}
public class XMLStringC : IFooInterface
{
private readonly IDatabaseLookupService databaseLookupService;
public XMLStringC(IDatabaseLookupService databaseLookupService)
{
if (databaseLookupService == null)
throw new ArgumentNullException("databaseLookupService");
this.databaseLookupService = databaseLookupService;
}
public string GenerateXML(FooObject fooObject)
{
// Use this.databaseLookupService as needed.
var data = this.databaseLookupService.Lookup(fooObject.ID);
// implementation here
}
public void ParseResponse(string serviceCallResponse)
{
// Use this.databaseLookupService as needed.
var data = this.databaseLookupService.Lookup(someID);
// implementation here
}
}
您对数据库的依赖将转移到 IDatabaseLookupService
,而不是绑定到您的业务逻辑。
这是我们正在做的事情的基本思路:
- 该网站以表单形式从用户那里收集数据,并将其全部保存到 table 中的一行中,我们将其称为 FooObject。
- 然后将 FooObject 传递到业务逻辑中 class
- 基于 app.config 中的键,工厂 class 将实例化三个具体 classes 之一:
- XMLStringA : IFooInterface
- XML字符串 B : IFooInterface
- XMLStringC : IFooInterface
- 每个class有两个方法:
- 生成XML(FooObject fooObject)
- 解析响应(字符串服务呼叫响应)
- class实例化后,我们调用GenerateXML(),传入FooObject,取回一个XML字符串,然后提交给三个单独的任意一个,外部,第三方网络服务。 (服务的地址也在app.config中。)
- 我们得到响应,并将其发送到 ParseResponse()
一切顺利。但是,C 有一些额外的要求。向此服务提交 XML 时,其中一个 XML 元素需要进行广泛的查找。我们决定将该查找所需的数据添加到我们数据库中的 table。因此,XMLStringC 中有一个私有方法,它使用 EF 进行数据库调用并获取所需的数据以添加到 XML 字符串。
我有点意识到这样做违反了单一职责原则,因为这些 classes 除了构建 XML 字符串之外什么都不应该做。 A 和 B classes 不调用数据库。
当我尝试进行单元测试以测试 A、B 和 C 时,我的所作所为可能是愚蠢的。因为我们不在上下文中,当 运行 单元测试时,C尝试调用数据库时失败。
我不确定在哪里为 C 执行此自定义逻辑。一方面,它仅在我们要提交给 C 服务时发生,因此在 C 中执行此操作很有意义 class.另一方面,我不喜欢从 class 内部进行数据库调用。最终这可能并不重要,如果我能弄清楚如何对其进行单元测试并使其工作。
执行此操作的最佳实践方法是什么?
If I did that, then A,B and C would all need it. But A and B don't care about it. They all implement the same interface.
如果您遵循依赖项注入最佳实践,则您的依赖项不是接口的一部分,而是对象构造函数的一部分。
您的评估是正确的,这违反了 SRP。您需要的是一种执行查找的服务,该服务作为依赖项传递到 C 中。那么您的服务不会违反 SRP,您仍然可以对 XMLStringC
class.
public class XMLStringB : IFooInterface
{
// No constructor defined here - we have no dependencies
public string GenerateXML(FooObject fooObject)
{
// implementation here
}
public void ParseResponse(string serviceCallResponse)
{
// implementation here
}
}
public class XMLStringC : IFooInterface
{
private readonly IDatabaseLookupService databaseLookupService;
public XMLStringC(IDatabaseLookupService databaseLookupService)
{
if (databaseLookupService == null)
throw new ArgumentNullException("databaseLookupService");
this.databaseLookupService = databaseLookupService;
}
public string GenerateXML(FooObject fooObject)
{
// Use this.databaseLookupService as needed.
var data = this.databaseLookupService.Lookup(fooObject.ID);
// implementation here
}
public void ParseResponse(string serviceCallResponse)
{
// Use this.databaseLookupService as needed.
var data = this.databaseLookupService.Lookup(someID);
// implementation here
}
}
您对数据库的依赖将转移到 IDatabaseLookupService
,而不是绑定到您的业务逻辑。