【问题标题】:Stubbing / mocking a database in .Net在.Net中存根/模拟数据库
【发布时间】:2011-04-05 09:59:20
【问题描述】:

我有一个 web 服务,它基本上只是执行一些存储过程、转换数据并将其发送到浏览器。没有花哨的 ORM 映射器或类似的东西。为了能够在不访问数据库的情况下编写测试,我做了以下操作:

  • 我已将所有对数据库的调用提取到一个类中。这些方法只返回 DataSet 和 DataTable 对象。
  • 为每个方法执行一个示例调用,并将 DataSet/DataTable 序列化到磁盘。
  • 提取了一个公开所有可用方法的接口。
  • 实现了一个伪数据库类,它只加载序列化数据并返回它。

现在我已经序列化了示例结果,我可以在我的项目中签入,我可以在我的测试中使用假数据库。

这对我来说效果很好。是否有一些框架可以更轻松地创建和加载示例数据?我当前的项目很小,但我会在较大的项目中使用相同的架构。

更新:

显然所有的答案都没有错,但没有抓住重点。我知道单元测试的基础知识。但是我的代码正在使用 DataTables,所以我不得不以某种方式伪造我的 DataTables。从头开始构建 DataTable 不是一件容易的事,它会使我的测试膨胀并降低可读性。就我而言,手动生成有用的样本数据是完全不可能的。

因此,我对示例数据库执行了一些示例调用以获取一些数据表。我已将这些表序列化到磁盘并在测试时使用序列化版本创建我的假数据表。这样测试就独立于数据库了。

关于如何构建代码有不同的选择,以使表格的反序列化更容易。但这些是目前不需要讨论的实现细节。我的问题如下:

管理示例调用和(反)序列化表是一项繁琐的工作。我一直在寻找一些工具来简化此操作。

【问题讨论】:

  • 那么您的测试到底在测试什么?假数据库类可以返回假数据吗?还是您正在测试转换?
  • 我正在测试转换。这部分测试假设数据库没有问题,我想测试从数据库中获取 DataSet/DataTable 后发生的所有事情。
  • 如果您只测试转换,理想情况下您会为此隔离代码,并严格在该代码上编写单元测试。
  • 同意 Chris,分离出转换并使用 DI 为其提供应该转换的数据。然后你可以扔掉你的假数据类
  • 我同意 Chris O 关于使用 DI 和在真空中测试 Xforms 的观点。我想补充一点,数据库测试也可以单独进行。请参阅 Fluent NHibernate,了解如何使用相当有限的编码量来做到这一点。

标签: c# database testing mocking


【解决方案1】:

通过阅读您所做的其他答案和各种 cmets,您似乎想要一种更简单的方法来生成大型填充数据集以进行不会影响数据库的集成测试。

NBuilder 是一个很棒的开源库,我已经成功地使用它来创建大量测试数据。只需将 NBuilder、一些基本的 POCO 对象类和一些反射结合起来 - 您将拥有大量可以轻松组合成数据集的巨大数据表:

public class Person
{
    public string First { get; set; }
    public string Last { get; set; }
    public DateTime Birthday { get; set; }
}

private DataTable GenerateDataTable<T>(int rows)
{
    var datatable = new DataTable(typeof(T).Name);
    typeof(T).GetProperties().ToList().ForEach(
        x => datatable.Columns.Add(x.Name));
    Builder<T>.CreateListOfSize(rows).Build()
        .ToList().ForEach(
            x => datatable.LoadDataRow(x.GetType().GetProperties().Select(
                y => y.GetValue(x, null)).ToArray(), true));
    return datatable;
}

var dataset = new DataSet();
dataset.Tables.AddRange(new[]{
        GenerateDataTable<Person>(50),
        GenerateDataTable<Dog>(100)});

【讨论】:

    【解决方案2】:

    要对转换进行单元测试,您根本不需要模拟数据库。我怀疑您已经将转换与数据库调用紧密结合在一起。您在这里要做的是将所有转换逻辑提取到它自己的类中,如下所示:

    public static Transformations
    {
        public static DataSet TransformationA(DataSet dataSet)
        {
            //transformation logic here
        }
    
        public static DataSet TransformationB(DataSet dataSet)
        {
            //transformation logic here
        }
    }
    

    有了这个,您可以通过传入数据集然后断言返回的数据集应用了正确的转换来仅对转换逻辑进行单元测试。这将使您不必为了测试目的而实施另一个数据存储(您的“假”数据库)。

    希望这会有所帮助

    【讨论】:

    • 查看我对问题的更新。主要问题不是转换,而是示例数据集/表的创建。
    • 我认为 stuartf 是完全正确的。到目前为止,我看到问题的唯一原因是“但创建了示例数据集/表。”
    【解决方案3】:

    您可以使用 Rhinomocks 模拟您的 DataAccess 类并返回一个假数据表。所以你可以测试使用这个DataTable的代码。

    var mockedDatatable= GetMockdt();
    
    var mocks = new MockRepository();
    var dal = mocks.StrictMock<DataAccess>();
    
    using (mocks.Record())
    {
      Expect.Call(dal.GetDataTableFromDatabase("", null)).Return(mockedDatatable).IgnoreArguments();
    }
    
    using (mocks.Playback())
    {
      new SomeClass(dal);
    }
    

    更新 mockdt 消息

    private static DataTable GetMockdt()
    {
      var dt = new DataTable();
    
      dt.Columns.Add("pageHeader");
      dt.Columns.Add("templatename");
      dt.Columns.Add("pageText");
      dt.Columns.Add("pageTitleBar");
      dt.Columns.Add("metaDescription");
      dt.Columns.Add("pageStartCode");
      dt.Columns.Add("pageEndCode");
      dt.Columns.Add("templateStartCode");
      dt.Columns.Add("templateEndCode");
      dt.Columns.Add("Author");
      dt.Columns.Add("version_date");
      dt.Columns.Add("pageurl");
      dt.Columns.Add("type");
      dt.Columns.Add("isparent");
      dt.Columns.Add("pagename");
      dt.Columns.Add("parentname");
      dt.Columns.Add("url");
    
      var mockRow = dt.NewRow();
    
      mockRow["pageHeader"] = "homepage";
      mockRow["pageText"] = "<p>home</p>";
      mockRow["templatename"] = "home";
      mockRow["pageTitleBar"] = "homepages";
      mockRow["metaDescription"] = "homepages";
      mockRow["pageStartCode"] = "homepages";
      mockRow["pageEndCode"] = "homepages";
      mockRow["templateStartCode"] = "homepages";
      mockRow["templateEndCode"] = "homepages";
      mockRow["Author"] = "someone";
      mockRow["version_date"] = "";
      mockRow["pageurl"] = "home";
      mockRow["type"] = "internal";
      mockRow["isparent"] = "true";
      mockRow["pagename"] = "homepage";
      mockRow["parentname"] = "root";
      mockRow["url"] = "homepage";
    
      dt.Rows.Add(mockRow);
    
      return dt;
    }
    

    【讨论】:

    • 请查看我的问题和其他答案的更新:我知道如何模拟/存根对象。我对你将如何实现 GetMockdt() 很感兴趣
    • 如果您再次阅读我的更新,您会发现您的解决方案正是我不想做的。在我的情况下,创建样本数据将是很多工作。我的问题完全针对您提出的简单解决方案。
    • 啊哈,我明白了,你想要一个生成样本数据的工具
    • 看看这篇文章,也许它可以帮助你stackoverflow.com/questions/591892/…
    • @Ivo:你认为提供的代码对重构友好吗?
    【解决方案4】:

    由于您要求将数据存储为 DataTables,并且您需要数据库中的原始数据,因此没有任何工具可以满足您的需求。工具的手动部分是将什么指向什么(即存储中的数据与代码中的数据表示)连接起来。这部分你已经做了,还不是自动化的。

    【讨论】:

      【解决方案5】:

      根据我的经验,使用 Fluent NHibernate 进行端到端测试相当容易。当它为你做了这么多时,没有理由不使用这样一个轻量级的层。

      Persistence specification testing

      【讨论】:

      • 链接已断开
      【解决方案6】:

      查看https://github.com/nbuilder/nbuilder

      “这是什么?

      通过流畅、可扩展的界面,NBuilder 允许您快速创建测试数据,自动为内置 .NET 数据类型(例如整数和字符串)类型的属性和公共字段分配值。 NBuilder 允许您使用 lambda 表达式覆盖您感兴趣的属性。”

      【讨论】:

        猜你喜欢
        • 2011-09-25
        • 2019-08-23
        • 2013-02-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多