单元测试难题:OleDbCommand

Unit testing conundrum: OleDbCommand

我尝试进行单元测试的 类 之一使用了 OleDbConnection 对象。该对象负责根据传递的 SQL 字符串返回保存在 MS Access 文件中的信息。代码是:

public void getInformation(DateTime start, DateTime finish)
{
    string getInfo = "";
    string query = "//Query for MS Access goes here";
    transactions = addTransactions(query, conn);
}

其中 addTransactions 是一个私有方法,returns 从数据库中检索的列表:

private List<string> addTransactions(string query, OleDbConnection conn)
{
    List<string> returnedData = new List<string>();
    OleDbCommand cmd = new OleDbCommand(query, conn);
    try
    {
        if (conn.State == ConnectionState.Closed) conn.Open();
        OleDbDataReader reader = cmd.ExecuteReader();
        while (reader.Read())
        {
            if (!reader.IsDBNull(0))
        }
        returnedData.Add(reader.GetString(0));
    }
    catch (Exception ex)
    {
        Console.WriteLine("OLEB: {0}", ex.Message);
    }
    conn.Close();
    return returnedData;
}

现在,通过查看此方法我已经知道很难进行单元测试,因为 public getInformation() 方法无效。我能做的是测试返回的数据中是否包含某些内容。我的问题是如何编写涉及 OleDbConnection 的单元测试?

我想到的唯一 2 个可能的解决方案涉及创建一个临时访问文件(不理想,因为我们现在不在单元测试的范围内,而且当测试 运行 时可能会失败在其他开发人员的机器上)或为 OleDbConnection 创建一个包装器并在单元测试中使用它(这听起来是最好的方法,但我不知道从哪里开始)。

只需创建一个 MS 文件并将其放入包中,然后始终使用相同的文件进行测试,这样您甚至可以写入数据并确切知道它返回的内容,而不是假设返回的任何数据都是正确的!只是一个想法,有多种实现方式。

or creating a wrapper for OleDbConnection and using that in the unit test

这就是我的方法。不直接 mockable/testable 的对象通常代表其自身的依赖关系。并且应该提供所有依赖项而不是在内部实例化。

包装器可以是提供相同功能的简单传递。让它由一个接口和该接口的代码驱动,这样您就可以为该接口提供模拟以进行单元测试。 (作为额外的好处,即使在测试方法 returns void 时,你的模拟也会给你一些观察的东西。所以你不必深入研究你正在测试的系统,只需观察模拟中调用的内容即可。)

从您正在模拟的聚合根开始,您需要包装 OleDbConnection。一个简单的入门示例可能如下所示:

public interface IDbConnection
{
    IDbCommand CreateCommand();
    // other methods
}

public class MyOleDbConnection : IDbConnection
{
    private OleDbConnection conn;

    public MyOleDbConnection()
    {
        // initialize "conn" here
    }

    public IDbCommand CreateCommand()
    {
        return conn.CreateCommand();
    }

    // other pass-thru methods
}

本质上,它只是底层对象公开的相同功能的直通包装器。这样做的好处是 this 对象实际上 不需要 进行单元测试,因为它不包含任何有意义的可测试逻辑。 (当然,如果能达到 100% 就好了,但是 return 的投资就没有这个 class。)

如您所见,这还涉及以类似方式为 OleDbCommand 创建包装器。同样,只是传递方法。

一旦您的依赖项被包装并可模拟,您将更改您的 class 以使用包装器。例如,而不是这个:

OleDbCommand cmd = new OleDbCommand(query, conn);

它在内部包含对依赖项的引用,您可以这样做:

IDbCommand cmd = conn.CreateCommand();

它要求依赖项提供对某物的引用。这将维护依赖关系的责任放在了依赖关系本身上,而不是在您要测试的 class 中。 (并且,在这样做的过程中,试图保持对依赖的自由。)

一旦从您的 class 中完全删除依赖项,您就可以自由地模拟和测试您喜欢的一切。