数据访问层的单元测试

8
我一直在研究项目数据访问层的单元测试。大多数选项归结为以下几点:
  • 使用专用测试数据库,但需要在所有单元测试的结束阶段进行清理(或手动清理)
  • 使用数据库,但不提交或仅回滚
  • 模拟数据库
在以前的一个项目中,我们曾使用回滚方式,但我想了解更多关于其他选项的信息,以及如何最好地执行这些选项。如果您有样例、文章、视频等资源,请分享给我。
4个回答

4
你需要在项目中进行两种类型的测试:单元测试和集成测试。
单元测试是针对项目中的某一方面进行测试,而不涉及数据访问和展示。为了进行单元测试,你需要模拟数据库和用户依赖注入,以将代码与数据提供者解耦。这会导致更好的架构,并使你能够插入不同的数据提供者,例如从ADO.net转移到nHibernate。
集成测试是测试整个系统,确保代码可以从数据库中获取正确的数据等。对于集成测试,每个开发人员都应该在他们的工作站上拥有一个数据库副本进行测试。你应该尝试自动创建和填充数据库,以便快速轻松地返回到数据库的良好副本。像nantDBFit这样的工具将帮助你编写数据库脚本。
我不建议使用中央数据库进行测试,因为其他开发人员可能正在同时测试它,而且它可能处于不良状态,这可能会导致错误的结果并花费很长时间来调试一个实际上不存在的问题。

2

我更喜欢使用测试数据库而不是“不提交”想法。

我的开发数据库中包含虚拟记录或完全经过清洗的生产数据样本。

我的集成测试数据库是实际生产数据库的副本(这个版本用于在我将更改实时发布之前进行测试)。


1
DAL的主要职责是从数据库中持久化/获取数据,因此测试系统是DAL +数据库。使用数据库模拟来编写DAL测试没有意义 - 谁真正关心执行了哪个SQL查询以获取特定实体?必须验证是否选择了正确的实体并且所有属性都被正确映射。
为了做到这一点,我通常会清理数据库,用测试数据填充数据库,并使用DAL方法获取它。
       [SetUp]
    public void SetUp()
    {
        Database.Clear();
        manager = new ServiceManager();
    }

    [TearDown]
    public void TearDown()
    {
        manager.Dispose();
    }

    [Test]
    public void InsertAndLoadOrderContract()
    {
        MinOrderModel model = new OrderBuilder().InsertMinimalOrder(manager);

        Contract contract = TestObjectGenerator.GenerateEntity<Contract>();
        manager.Contract.InsertOrderContract(model.Order.OrderCompositeId, contract);

        Contract selectedContract = manager.Contract.SelectById(contract.ContractId);

        AssertContract(selectedContract, contract);
    }

    private static void AssertContract(IContract actual, IContract expected)
    {
        Assert.That(actual.AgencyCodeOther, Is.EqualTo(expected.AgencyCodeOther));
        Assert.That(actual.AgencyFK, Is.EqualTo(expected.AgencyFK));
        Assert.That(actual.ContractId, Is.EqualTo(expected.ContractId));
        Assert.That(actual.Ident, Is.EqualTo(expected.Ident));
    }

这个测试的某些部分可以替换为更方便的内容:

  1. 可以使用 DbUnit 来填充数据库中的数据
  2. 在 "TearDown" 方法中不要清除数据库,而是回滚事务
  3. 为测试使用更方便的数据库引擎(例如 SQLLite)

0

我想模拟数据库。在测试中处理数据库很痛苦,需要创建数据库、创建模式,然后删除它,确保没有挂起的连接等都很麻烦。

另一个让我感到不舒服的事情是验证代码逻辑与代码“相距甚远”。我会选择将 Sql 函数(连接、命令等)放在可模拟的类后面,并验证 DAL 是否调用了正确的方法。此外,这种方式能够大大加快测试的运行速度。

以下是一些快速的 SQL 抽象类和示例用法+单元测试。

public class SqlConnectionBase : IDisposable {
    private readonly SqlConnection m_Connection;

    public SqlConnectionBase(string connString) {
        m_Connection = new SqlConnection(connString);
    }

    public virtual SqlConnection Object { get { return m_Connection; } }

    public virtual void Open() {
        m_Connection.Open();
    }
    public virtual void Close() {
        m_Connection.Close();
    }

    #region IDisposable Members

    public virtual void Dispose() {
        m_Connection.Dispose();
    }

    #endregion
}

public class SqlCommandBase  : IDisposable{
    private readonly SqlCommand m_Command;

    public SqlCommandBase() {
        m_Command = new SqlCommand();
    }
    public SqlCommandBase(string cmdText, SqlConnectionBase connection) {
        m_Command = new SqlCommand(cmdText, connection.Object);
    }
    public SqlCommandBase(SqlConnectionBase connection) {
        m_Command = new SqlCommand();
        m_Command.Connection = connection.Object;
    }

    public virtual int ExecuteNonQuery() { return m_Command.ExecuteNonQuery(); }
    public virtual string CommandText { get { return m_Command.CommandText; } set { m_Command.CommandText = value; } }

    public virtual void AddParameter(SqlParameter sqlParameter) {
        m_Command.Parameters.Add(sqlParameter);
    }

    #region IDisposable Members

    virtual public void Dispose() {
        m_Command.Dispose();
    }

    #endregion
}
public class SqlFactory {
    public virtual SqlCommandBase CreateCommand(string query, SqlConnectionBase conn) {
        return new SqlCommandBase(query, conn);
    }
    public virtual SqlCommandBase CreateCommand(SqlConnectionBase conn) {
        return new SqlCommandBase(conn);
    }

    public virtual SqlConnectionBase CreateConnection(string connString) {
        return new SqlConnectionBase(connString);
    }

}

public class DBUser {
   public DBUser(SqlFactory factory) {
     m_factory = factory; //dependency constructor, will be used during unit testing
   }

   public DBUser() {
     m_factory = new SqlFactory(); //used during normal execution
   }

   public void DoSomething() {
     var conn = m_factory.CreateConnection("server=servername,database=...");
     var cmd =  m_factory.CreateCommand(conn);
     cmd.CommandText = "Select * from users";
     cmd.ExecuteNonQuery();
   }

   [TestMethod]
   public void DoSomethingTest() {
     var factoryMock = new Mock<SqlFactory>();
     var cmdMock = new Mock<CommandBase>();
     factoryMock.Setup(f=>f.CreateConnection(It.IsAny<string>())).Returns(cmdMock.Object);
     DBUser dbUser = new DBUser(factoryMock.Object);
     dbUser.DoSomething();

     //Verify that DoSomething is called.
     cmdMock.Verify(c=>c.DoSomething());
   }
}

测试运行速度会更快:我可以想象。你有描述的一些示例代码吗? - Kris van der Mast
我有一些想法,我正在认真考虑编写 System.Sql.Abstractions 库,类似于 System.IO.Abstractions - Igor Zevaka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接