如何在 C# 中对 NpgsqlDataReader 扩展进行单元测试

How to unit test NpgsqlDataReader extensions in C#

我一直在研究一些 ADO.NET 扩展,其灵感来自 Rob Conery 的 PostgreSQL Pluralsight 文章。

我是单元测试的新手,并且一直在尝试通过围绕我为 NpgsqlDataReader 编写的几个扩展方法编写一些测试来了解更多关于它的信息 class。

我正在尝试测试的方法示例:

    public static T ToEntity<T>(this NpgsqlDataReader reader) where T : new()
    {
        var result = new T();
        var properties = typeof(T).GetProperties();

        foreach (var prop in properties)
        {
            for (var i = 0; i < reader.FieldCount; i++)
            {
                if (reader.GetName(i).Replace("_", "").Equals(prop.Name, StringComparison.InvariantCultureIgnoreCase))
                {
                    var val = reader.GetValue(i) != DBNull.Value ? reader.GetValue(i) : null;
                    prop.SetValue(result, val);
                }
            }
        }

        return result;
    }

我什至不确定如何开始模拟数据库连接,因为我想要测试的只是迭代返回的结果对象并将列映射到通用实体的属性的功能 class .

如何在不访问数据库的情况下测试实体映射?

谢谢

更新:

我一直用来调用上面扩展的代码是 NpgsqlCommand 的另一个扩展 class:

public static IEnumerable<T> ToList<T>(this NpgsqlCommand cmd) where T : new()
{
    var results = new List<T>();
    var reader = cmd.ExecuteReader();

    while (reader.Read())
    {
        results.Add(reader.ToEntity<T>());
    }

    reader.Close();
    reader.Dispose();

    return results;
}

我也将使用下面答案中发布的相同技术来测试此方法。

首先,方法实现不依赖于NpgsqlDataReader的具体实现。所以你可以把签名改成:

public static T ToEntity<T>(this IDataReader reader) where T : new()

其次,签名不符合逻辑。数据读取器用于遍历结果集并产生 list 行(具有 0、1 或更多元素),而不是单个结果。因此,更合乎逻辑的签名和实现将是:

public static IEnumerable<T> ToEntity<T>(this IDataReader reader) where T : new()
{
    var properties = typeof(T).GetProperties();

    while (reader.Read())
    {
        var result = new T();
        foreach (var prop in properties)
        {
            for (var i = 0; i < reader.FieldCount; i++)
            {
                if (reader.GetName(i).Replace("_", "").Equals(prop.Name, StringComparison.InvariantCultureIgnoreCase))
                {
                    var val = reader.GetValue(i) != DBNull.Value ? reader.GetValue(i) : null;
                    prop.SetValue(result, val);
                }
            }
        }

        yield return result;
    }
}

现在您可以提供 IDataReader 的假实现。您可以使用 NSubstitute 等模拟框架,但如果您不熟悉单元测试,更简单的方法是手动实现一个。例如,in this gist (incomplete implementation) 我在内存中的泛型列表上实现了 IDataReader(以及一个便于使用的扩展方法)。

现在你可以像这样写一个测试方法:

// note the anonymous type here
var expectedEntity = new {MY_PROPERTY = "SomeValue"};
var reader = new[]
{
    expectedEntity
}.AsDataReader();
var entity = reader.ToEntity<MyEntity>().First();
Assert.AreEqual("SomeValue", entity.MyProperty);

最后,您使用反射的解决方案有效,但最终可能会相当缓慢。请记住,这种代码是已解决的问题;查看轻量级 ORM,例如 PetaPoco,dapper.net a.o.