【问题标题】:Unit Testing Service Layer in .NET.NET 中的单元测试服务层
【发布时间】:2020-06-24 19:29:27
【问题描述】:

我正在开发一个新的 MVC 5 应用程序。我的项目中有一个集成层,其中有一个对外部 Web 服务的服务引用。

然后我创建了一个包含一些方法的接口 - 然后我有一个如下所示的服务类:

public class MyService : IMyService
{
    private readonly ExternalServiceClient _serviceClient;

    public MyService()
    {
        _serviceClient = new ExternalServiceClient ("WSHttpBinding_IMyService");
    }

    public string GetName(Coordinate[] coordinates)
    {
        string name = string.Empty;

        name = _serviceClient.GetInformationsForCoordinates(coordinates);

        return name;
    }

请注意,这已被剥离,实际上将添加带有异常处理等的 try catch 块

我有一个用于集成层单元测试的 Integration.Tests 项目。测试 GetName 方法的最佳实践方法是什么。我是否应该在 Tests 项目中向服务的端点添加另一个服务引用,或者如果我们正在使用 Moq,我是否可以创建我的实际服务的实例,例如 Web 层将调用 - 以及如何完成?

【问题讨论】:

  • 顺便说一句,您的服务似乎没有多大作用。从 ExternalServiceClient 中提取接口并将其作为依赖项直接传递可能会更好。

标签: c# unit-testing design-patterns moq


【解决方案1】:

适合您情况的最佳实践在于您的架构设计。

您的服务显然依赖于ExternalServiceClient,并且您在MyService 类中实例化它,这不允许您轻松切换依赖关系,导致您在测试时头疼。

真正的问题应该是:

如何设计我的服务以使其易于测试?

答案是Dependency Injection

因为您将能够模拟 MyService 依赖项,您将能够对其进行彻底测试,并能够通过红绿重构来证明您的服务可以完美运行。

依我的拙见,你的班级应该是这样的:

public class MyService : IMyService {
    public MyService(ExternalServiceClient serviceClient) {
        externalServiceClient = serviceclient;
    }

    public string GetName(Coordinate[] coordinates) {
        string name = string.Empty;
        name = externalServiceClient.GetInformationForCoordinates(coordinates);
        return name;
    }

    private readonly ExternalServiceClient externalServiceclient;
}

这样,您将能够随意替换依赖项,因此可以使用模拟。

使用NUnitMoq,您可以按如下方式测试您的服务:

[TestFixture]
public class MyServiceTests {
    [TestFixture]
    public class GetCoordinates : MyServiceTests {
        // Given
        string expected = "Name";
        Coordinate[] coordinates = new Coordinate[] { ... }
        externalServiceClientMock.Setup(esc => esc.GetInformationForCoordinates(coordinates)).Returns(expected);

        // When
        string actual = myService.GetName(coordinates);

        // Then
        externalServiceClientMock.Verify(esc => esc.GetInformationCoordinates(coordinates));
        Assert.AreEqual(expected, actual);
    }

    [SetUp]
    public void MyServiceSetup() {
        externalServiceClientMock = new Mock<ExternalServiceClient>("WSHttpBinding_IMyService");
        myService = new MyService(externalServiceClientMock.Object);
    }
    
    private Mock<ExternalServiceClient> externalServiceClientMock;
    private MyService myService;
}

【讨论】:

  • 谢谢 Will - 我正在考虑将 SimpleInjector 用于该项目的 DI。但是在 MyService 类中,DI 这个 ExternalServiceClient 的最佳方法是什么?
  • 构造函数注入是首选方式。查看我的编辑。
  • 使用 DI 工具,您甚至可以使用一些 Conditional BindingExternalServiceClient 依赖注入到 string 原始类型。我不知道SimpleInjector。真正关心的是没有对您的 DI 内核、容器或您的类中的任何名称的任何引用。 DI 注入工具只能且仅在您的Composition Root 即您的程序入口点中引用。
  • 构造函数注入对于 API 用户来说更清晰,比基于注解的注入更容易记录,并且更易于测试。任何一种类型的 DI 都比隐藏依赖项好,但出于测试目的,我更喜欢在 ctor 上显式传递它们。
  • Will - 我在您的回答中唯一没有看到的是为 ExternalServiceClient 指定 ("WSHttpBinding_IMyService") 的位置
【解决方案2】:

一般来说,使用参数化构造函数来注入对对象构造的依赖是一种很好的做法(因为您对ExternalServiceClient 具有只读语义)。然后你可以在你的测试用例中注入模拟。

在您的情况下,如果有在ExternalServiceClient 中实现的接口

private readonly ExternalServiceClient _serviceClient;

public MyService(IExternalServiceClient serviceClient)
{
    _serviceClient = serviceClient;
}

然后将其用作:

var service = new MyService(new ExternalServiceClient ("WSHttpBinding_IMyService"));

测试中

IExternalServiceClient  mockObject = //construct mock with desired behabiour then pass to ctor
var service = new MyService(mockObject);

如果没有实现的接口和添加它的能力(因为它是外部的),你必须做一些虚拟的技巧。

【讨论】:

  • Hamlet - 所以行 var service = new MyService(new ExternalServiceClient ("WSHttpBinding_IMyService"));其中 WSHttp 是 instatntiated - 这将是每个方法中的第一行,然后例如 public string GetName(Coordinate[] coordinates)
  • @KOL 是这个问题吗?
  • 是的,这是一个问题 - 我在每种方法中都需要这个新方法是否正确
  • 每次需要创建对象(MyService)或重新创建上下文时,都需要new
  • 使用依赖注入工具如Ninject、SimpleInjection、StructureMap等,避免每次都要写new This(new That(new Here(new There())))
猜你喜欢
  • 1970-01-01
  • 2013-03-04
  • 1970-01-01
  • 2011-02-20
  • 2020-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-06
相关资源
最近更新 更多