使用现有方法测试.NET Web API/SQL项目



我继承了一个用ado.net访问sql Server数据库的C#/。网络编写的Web API 2项目。

该项目的数据访问层包含许多看起来与此相似的方法:

public class DataAccessLayer
{
    private SqlConnection _DBConn;
    public DataAccessLayer()
    {
        _DBConn = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
    }
    public string getAllProductsAsJSON()
    {
        DataTable dt = new DataTable();
        using (SqlConnection con = _DBConn)
        {
            using (SqlCommand cmd = new SqlCommand("SELECT productId, productName FROM product ORDER BY addedOn DESC", con))
            {
                cmd.CommandType = CommandType.Text;
                // add parameters to the command here, if required.
                con.Open();
                SqlDataAdapter da = new SqlDataAdapter(cmd);
                da.Fill(dt);
                return JsonConvert.SerializeObject(dt);
            }
        }
    }
    // ... more methods here, but all basically following the above style of 
    //     opening a new connection each time a method is called.
}

现在,我想为此项目编写一些单元测试。我已经研究了使用SQL交易的想法,以便将模拟数据插入数据库中,对模拟数据进行测试,然后回滚交易以允许针对"实时"(开发)数据库进行测试,以便您进行测试可以可以访问SQL Server功能而无需完全嘲笑它(例如,您可以确保您的视图/功能返回有效的数据,并且API立即正确处理数据)。数据访问层中的某些方法将数据添加到数据库中,因此我想启动事务,调用一组DAL方法来插入模拟数据,调用其他方法以用断言测试结果,然后然后回滚整个测试,以便不投入模拟数据。

我遇到的问题是,如您所见,该类旨在每次进行查询时创建一个新的数据库连接。如果我试图像原始开发人员那样思考,我可以看到它至少可以做到这一点,考虑到这些类是由Web API使用的事实,因此持久的数据库连接将是不切实际的Web API调用涉及交易,因为然后您确实需要单独的每个请求连接才能保持分离。

但是,由于这种情况正在发生,因此我认为我可以使用交易想法来编写测试,因为在数据库连接中无法访问未访问的数据。因此,如果我编写了一个调用dal方法(以及又依次调用dal方法的商业逻辑层方法)的测试,则每种方法都会打开自己与数据库的连接,因此我无法包装所有方法调用从一开始的交易中。

我可以重写每种方法以接受SQLConnection作为其参数之一,但是如果这样做,我不仅必须重构60种方法,而且我还必须重新刷新此类方法在此处调用此类方法的每个地方。Web API控制器。然后,我必须将创建和管理DB连接的负担转移到Web API(并远离DAL,这是哲学上应该是的)。

实际上是重写/重构60多种方法和整个Web API的缺点,我可以采用其他方法来为该项目编写单元测试吗?


编辑:我的新想法是简单地将所有呼叫删除到con.Open()。然后,在构造函数中,不仅创建连接,还可以打开连接。最后,我将添加直接在连接对象上操作的开始传播,cistranstransaction和RollbackTransaction方法。核心API不需要调用这些功能,但是单位测试可以调用它们。这意味着单元测试代码可以简单地创建一个实例,该实例将创建一个连接,该连接始于类的整个生命周期。然后,它可以使用begintransaction,然后执行所需的任何测试,最后滚动反击。具有commitransaction有益于完整性,并且将此功能暴露于业务层次层具有潜在用途。

这个问题有多个可能的答案,具体取决于您试图完成的工作:

  1. 您是否主要对单元测试应用程序逻辑(例如,控制器方法)感兴趣,而不是数据访问层本身?
  2. 您是否想单位测试数据访问层中的逻辑?
  3. 或者您是要一起测试所有内容(即集成或端到端测试)?

我假设您对第一种情况感兴趣,测试您的应用程序逻辑。在这种情况下,我建议不要在单元测试中完全连接到数据库(甚至是开发数据库)。通常,单元测试不应与任何外部系统(例如数据库,文件系统或网络)进行交互。

我知道您提到您有兴趣一次测试功能的多个部分:

我已经研究了使用SQL Transactions [...]的想法,因此您可以访问SQL Server功能而无需完全嘲笑它(例如,您可以确保您的视图/函数正在返回有效的数据,并且API是一次正确处理数据)。

然而,这与单位测试的哲学有关。单位测试的全部要点是隔离测试单个单元。通常,此单元(从技术上讲,"正在测试的系统"或SUT)是某些类中的一种方法(例如,您的一个控制器中的一个操作方法)。除SUT之外的任何其他东西都应被固定或嘲笑。

为了实现这一目标,从广义上讲,您需要重构代码以使用依赖项注入,并在测试中使用模拟框架:

  • 依赖项注入:如果您还不使用依赖项注入框架,则可能是您的控制器类直接实例化DataAccessLayer类。这种方法对单位测试不起作用 - 相反,您需要重构控制器类,以通过构造函数接受其依赖项,然后使用依赖项注入框架将真实的DataAccessLayer注入应用程序代码,并注入模拟/stub stub在您的测试中实现。一些流行的依赖注入框架包括AUTOFAC,NINICT和MICROSOFT UNITY。根据您选择的框架,这也可能需要您重构DataAccessLayer,以便实现接口(例如IDataAccessLayer)。
  • 模拟框架:在您的测试中,而不是直接使用真实的DataAccessLayer类,而是创建一个模拟,并在该模拟上设置期望。.NET的一些流行的模拟框架包括MOQ,Rhinomocks和Nsubstitute)。

批准,如果最初未考虑单位测试(即没有依赖性注射)编写代码,则可能涉及相当多的重构。这是Alltej的建议,创建用于与遗产(即未经测试)代码进行交互的包装器。

我强烈建议您阅读本书单元测试的艺术:c#中的示例(由Roy Osherove)。这将有助于您更好地了解单元测试背后的意识形态。

如果您实际上有兴趣一次测试功能的多个部分,则您所描述的内容(正如其他人指出的那样)是集成或端到端测试。此设置的设置将完全不同(通常更具挑战性),但是即使到那时,推荐的方法也将是连接到单独的数据库(特别用于集成测试,即使与开发数据库分开),而不是回滚交易。

在使用旧系统时,我要做的就是为此DLL/项目创建一个包装器,以隔离遗产并保护您的新的正直子系统/域或有限上下文。该隔离层被称为DDD术语中的反腐败层。该层包含根据您的新界环境编写的接口。接口适应您的API层或域中的其他服务。然后,您可以使用此接口编写单元/模拟测试。您还可以从反腐败层创建一个集成测试,该测试最终将通过传统DLL调用数据库。

实际上,从我在代码中看到的内容,dal仅在构造函数中创建一个连接,然后它不断使用它来触发命令,dal中的每个方法一个命令。它仅在创建DAL类的另一个实例时才会创建新连接。

现在,您所描述的是多次测试,集成,端到头,我不相信交易想法虽然是原始的,但实际上是可行的。

编写集成测试时,我更喜欢实际创建测试所需的所有数据,然后在最后删除它,这样一无所知,您可以确定您的系统是否有效。

所以想象一下您正在测试用户检索帐户数据,我会创建用户,激活他们,附加帐户然后对该真实数据进行测试。

除非您真的想完成结束测试,否则UI不需要一路完成。如果您不这样做,那么您只能为要测试的每种情况模拟数据,并查看UI在每种情况下的行为。

我建议您单独测试API,测试每个端点,并确保通过涵盖所需所有情况的集成测试按预期工作。

如果有时间,请写一些端到端测试,可能使用诸如硒或其他您喜欢的工具。

我还将从该DAL中提取一个接口,以准备在需要时模拟整个层。那应该给您一个良好的开始。

最新更新