【问题标题】:Unit Testing a CRUD operation of a Repository - function which routes to test or live DB based on HttpContext is making testing difficult单元测试存储库的 CRUD 操作 - 基于 HttpContext 路由到测试或实时数据库的功能使测试变得困难
【发布时间】:2016-05-19 13:36:53
【问题描述】:

目前我们正在开发一个使用类库的新项目。我正在为该站点开发一项功能,研究存储库的基础知识,该存储库现在具有与数据库通信的简单 CRUD 操作方法。我们最近决定开始尝试对我们的代码进行单元测试,以进一步鼓励良好的编程习惯。

现在我的单元测试失败了,因为它试图在我们的 Live DB 上使用一个存储过程——一个当前在 Live 上不存在的存储过程。起初它只是失败了(抛出了一个异常),我欺骗了 HttpContext 并将服务器名称 URL 交给它,它应该触发连接到测试数据库。但是它正在连接到当前我认为导致这种情况的代码是:

public static bool TestMode()
    {
        if (
            HttpContext.Current.Request.ServerVariables["SERVER_NAME"] == "localhost" ||
            HttpContext.Current.Request.ServerVariables["SERVER_NAME"] == "test.org"
            )
            return true;
        else
            return false;
    }

该库旨在与 MVC 站点结合使用,因此这种类型的设置并非没有必要,不幸的是,我不会选择重构我们如何执行此操作。

这就是我的单元测试

[TestClass]
public class CalendarTests {

    public CalendarRepository _repo { get; set; }

    [TestInitialize]
    public void Setup() {
        _repo = new CalendarRepository();

        HttpContext.Current = new HttpContext(
                                new HttpRequest(null, "http://test.org", null),
                                new HttpResponse(null)
                              );
    }

    [TestMethod]
    public void CreateEventTest() {

        int val = _repo.CreateEvent(1,
                                    "test title",
                                    "demo-description",
                                    DateTime.Now,
                                    DateTime.Now.AddDays(1),
                                    true,
                                    1,
                                    1);

        Assert.IsTrue(val > 0);
    }
}

我所做的大多数搜索都有这个问题,因为他们正在 MVC 项目上测试控制器,但是这略有不同,我不确定从这里去哪里。我知道我需要找到一种方法来模拟上下文以进行测试。各种测试框架都被推荐了,比如 NUnit,还有 mocking 框架,比如 NSubstitute。我对这些很感兴趣,但不确定是否有必要使用它们。

感谢所有帮助!

编辑:

我尝试过的:

虽然我删除了代码,但我尝试编写类似于this 以及this 的代码

编辑2:

这是连接字符串的来源

public static string DSHA()
    {
        var db = new Database()
        {
            LiveConnectionString = "sanitized-live",
            TestConnectionString = "sanitized-test"
        };

        return ResolveConnection(db);
    }

这是TestMode被调用的代码

private static string ResolveConnection(Database db)
    {
        if (Helpers.TestMode())
            return db.TestConnectionString;
        else
            return db.LiveConnectionString;
    }

所以我要求连接字符串,它调用 DSHA,导致来自 ResolveConnection 的评估,其条件是 TestMode 最终决定返回的连接字符串。

编辑2:

Nkosi 的回复非常有帮助,但是现在我正在尝试确保 Helpers 获得服务器真正提供的默认 ServerVariables。我尝试使用构造函数

public Helpers(){
  ServerVariable = new ServerVariables();
}

但是,当我在单元测试中检查上下文时,我得到一个空引用异常,我认为这是因为 Helpers 具有静态方法,但 Helpers 类本身不是静态的。

[TestMethod]
public void CheckContext(){
    var context = Helpers.ServerVariable.Name;
    Assert.IsTrue(context == "localhost");
}

我不确定使用容器注入接口的正确方法,但我希望尽可能避免更改 Helpers 类的行为,因为有相当多的代码是使用它编写的。需要为它提供任何其他东西可能意味着要进行大量狩猎以确保它的行为随处改变。

【问题讨论】:

  • HttpContext 测试任何东西都很困难。这就是为什么许多人建议您将接口背后所需的功能抽象化,以便提高可测试性。
  • 我该怎么做呢?我尝试在其上使用包装器方法,但不确定是否正确实现。
  • 展示你的尝试,看看社区是否能指出你哪里出错了
  • 我添加了一些链接来展示我在说什么
  • ok 显示使用静态TestMode 的代码。因为就目前而言,您将不得不进行一些重构。您还提到您实际上是在调用数据库。这是集成测试还是单元测试?

标签: c# .net unit-testing asp.net-mvc-5 httpcontext


【解决方案1】:

从您指出的 cmets 中,您基本上需要从 HttpContext 获取服务器名称。

在包装HttpContext 并为您提供所需内容的服务背后抽象出该功能。

类似...

public interface IServerVariable {
    string Name { get; }
}

你可以有一个实现...

public class ServerVariables : IServerVariable {
    public string Name {
        get {
            return HttpContext.Current.Request.ServerVariables["SERVER_NAME"];
        }
    }
}

IServerVariable 可以根据测试需要进行模拟。所以你可以注入你的接口来获取你需要的数据,而不是直接引用HttpContext

您可能必须重构当前代码以允许通过容器注入接口,否则您将不得不手动执行此操作。但除此之外,这应该使您的代码更具可测试性。

假设...

public class Helpers {

    public static IServerVariable ServerVariable {get;set;}

    public static bool TestMode()
    {
        if (ServerVariable != null 
            && (ServerVariable.Name == "localhost" || ServerVariable.Name == "test.org")
            )
            return true;
        else
            return false;
    }

}

测试可能看起来像...

[TestClass]
public class CalendarTests {

    private class FakeServerVariable : IServerVariable {
        public string Name { get { return "test.org"; }
    }

    public CalendarRepository _repo { get; set; }

    [TestInitialize]
    public void Setup() {
        _repo = new CalendarRepository();

        Helpers.ServerVariable = new FakeServerVariable();
    }

    [TestMethod]
    public void CreateEventTest() {

        int val = _repo.CreateEvent(1,
                                    "test title",
                                    "demo-description",
                                    DateTime.Now,
                                    DateTime.Now.AddDays(1),
                                    true,
                                    1,
                                    1);

        Assert.IsTrue(val > 0);
    }
}

【讨论】:

  • 恩科西,这帮了我很多忙。现在我正确地调用了数据,但是我对注入接口供我的 Helpers 类使用的正确方法有点迷茫。 public Helpers(){ ServerVariable = new ServerVarialbes();我不确定如何知道默认情况下,如果没有按照您在测试中说明的方式提供,Helper 将使用获得的正常 httpcontext
  • 您还在使用我提供的静态 Helper 示例,还是重构为其他内容。
  • 在您的启动中,您可以创建默认类并将其传递给助手。我不建议在构造函数中新建一个。鉴于您不想对现有代码进行太多更改,这就是我按照他们的方式构建答案的原因。
  • 我正在逐字使用您的示例,并且我确实成功使用了测试数据库,但我需要确保默认情况下它保留其正常功能。
  • 虽然我不喜欢这种结构,但您可以使用静态构造函数并在那里设置默认值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-10
  • 2021-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多