单元测试数据访问层

时间:2010-07-28 13:30:53

标签: visual-studio unit-testing

我一直在阅读单元测试项目的数据访问层。大多数选项归结为:

  • 使用专用测试数据库,但在所有单元测试的最终阶段进行清理(或手动完成)
  • 使用数据库但不提交或只是回滚
  • 模拟数据库

在之前的项目中,我们曾经使用回滚方式,但我想了解更多有关其他选项以及如何最好地执行这些选项的信息。如果您有样品/文章/视频/ ...请分享。

4 个答案:

答案 0 :(得分:3)

您需要对项目进行两种类型的测试。 单元测试和集成测试

单元测试测试项目的一个方面,而不依赖于数据访问和表示。对于单元测试,您将模拟数据库和用户依赖项注入,以便从数据提供程序中解耦代码。这样可以提供更好的体系结构,并且可以根据需要插入不同的数据提供程序。例如从ADO.net迁移到nHibernate。

集成测试是测试整个系统并确保代码可以从数据库中获取正确数据的地方。对于集成测试,每个开发人员都应该在他们的工作站上有一个数据库的副本,他们可以根据这些数据进行测试。您应该尝试自动化数据库的创建和填充,以便您可以快速轻松地返回到数据库的良好副本。像nantDBFit这样的工具可以帮助您编写数据库创建脚本。

我不会使用中央数据库进行测试,因为其他开发人员可能会同时对其进行测试,并且它可能不会处于良好状态,您可能会出现误报并花费多年时间来尝试调试问题不是问题。

答案 1 :(得分:2)

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

我的开发人员数据库有虚拟记录或完全消毒的生产数据样本。

我的集成测试数据库是实际生产数据库的副本(此版本是在我实际更改之前用于测试的版本)。

答案 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)

答案 3 :(得分: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());
   }
}