【问题标题】:How do you unit test business applications?您如何对业务应用程序进行单元测试?
【发布时间】:2008-09-15 23:18:13
【问题描述】:

人们如何对他们的业务应用程序进行单元测试?我已经看到了很多带有“易于测试”示例的单元测试示例。前任。一个计算器。人们如何对数据密集型应用程序进行单元测试?你是如何把你的样本数据放在一起的?在许多情况下,一个测试的数据可能对另一项测试根本不起作用,这使得只有一个测试数据库很难?

测试代码的数据访问部分相当简单。它正在测试所有适用于似乎难以测试的数据的方法。例如,想象一个发布过程,其中有大量数据访问以确定发布的内容,调整数字等。有许多中间步骤发生(并且需要测试)以及之后的测试,以确保发布是成功的。其中一些步骤实际上可能是存储过程。

过去我曾尝试将测试数据插入测试数据库,然后运行测试,但老实说,编写这种代码非常痛苦(而且容易出错)。我也尝试过预先构建一个测试数据库并回滚更改。这行得通,但在许多地方你也不能轻易做到这一点(很多人会说这是集成测试;就这样吧,我仍然需要能够以某种方式进行测试)。

如果答案是没有一种很好的方法来处理这个问题并且目前只是有点糟糕,那么知道这也是有用的。

感谢任何想法、想法、建议或提示。

【问题讨论】:

    标签: unit-testing


    【解决方案1】:

    我的自动化功能测试通常遵循以下两种模式之一:

    • 数据库连接测试
    • 模拟持久层测试

    数据库连接测试

    当我有连接到数据库的自动化测试时,我通常会制作一个单一的测试数据库模板,其中包含足够的数据用于所有测试。运行自动化测试时,会为每个测试从模板生成一个新的测试数据库。测试数据库必须不断地重新生成,因为测试会经常改变数据。随着测试的添加,我通常会在测试数据库模板中添加更多数据。

    这种测试方法有一些很好的优势。明显的优势是测试还可以锻炼您的模式。另一个优点是在设置初始测试后,大多数新测试将能够重新使用现有的测试数据。这使得添加更多测试变得容易。

    缺点是测试数据库会变得笨拙。因为数据通常会一次添加一个测试,所以会不一致,甚至可能不切实际。当数据库架构发生重大变化时,您还将最终诅咒设置测试数据库的人(对我而言,这通常意味着我最终会诅咒自己)。

    如果不能随意生成新的测试数据库,这种测试方式显然是行不通的。

    模拟持久层测试

    对于这种模式,您可以创建与测试用例一起使用的 mock objects。这些模拟对象拦截对数据库的调用,以便您可以以编程方式提供适当的结果。基本上,当您正在测试的代码调用findCustomerByName() 方法时,您的模拟对象将被调用,而不是持久层。

    使用模拟对象测试的好处是您可以变得非常具体。通常,在没有模拟对象的自动化测试中,有些执行路径是您根本无法达到的。它们还使您免于维护大量单一的测试数据集。

    另一个好处是没有外部依赖。因为模拟对象模拟持久层,所以您的测试不再依赖于数据库。这通常是选择哪种模式时的决定因素。在处理遗留数据库系统或具有严格许可条款的数据库时,模拟对象似乎更受关注。

    模拟对象的缺点是它们通常会导致大量额外的测试代码。这并不可怕,因为几乎任何数量的测试代码在按运行测试的次数摊销时都很便宜,但是测试代码多于生产代码可能会很烦人。

    【讨论】:

      【解决方案2】:

      当我尝试使用回滚解决方案来处理这些集成测试时,我不得不赞同 @Phil Bennett 的评论。

      我有一篇关于集成测试你的数据访问层的非常详细的帖子here

      我不仅展示了示例数据访问类、基类和示例 DB 事务夹具类,还展示了带有示例数据的完整 CRUD 集成测试。使用这种方法,您不需要多个测试数据库,因为您可以控制每次测试的数据,并且在测试完成后,事务全部回滚,因此您的数据库是干净的。

      关于应用程序内部的单元测试业务逻辑,我还将通过 @Phil 和 @Mark 支持 cmets,因为如果您模拟出您的业务对象所具有的所有依赖项,那么在一个实体中测试您的应用程序逻辑变得非常简单时间;)

      编辑:那么您是否正在寻找一个巨大的集成测试来验证所有内容,从逻辑预数据库/存储过程运行与逻辑,最后在返回的路上进行验证?如果是这样,您可以将其分为两个步骤:

      • 1 - 单元测试数据推送之前发生的逻辑 进入您的数据访问代码。为了 例如,如果你有一些代码 根据计算一些数字 一些属性——写一个测试 只检查逻辑是否 这 1 个功能可以满足您的要求 它要做。模拟任何依赖 在数据访问类上,这样你就可以 在这个测试中忽略它 仅应用程序逻辑。

      • 2 - 集成测试一旦你接受你的逻辑就会发生 操纵数据(来自上一个 我们单元测试的方法)并调用 适当的存储过程。做 这里面有一个数据特定的测试 类,这样你就可以在它之后回滚 完全的。存储后 程序已经运行,做一个查询 针对数据库获取您的 反对现在我们已经做了一些 针对数据的逻辑并验证它 具有您期望的值 (后存储过程逻辑/etc)

      如果您需要数据库中的条目来运行存储过程,只需在运行包含您的逻辑的存储过程之前插入该数据即可。例如,如果您有需要测试的产品,它可能需要插入供应商和类别条目,因此在您插入产品之前,请为供应商和类别快速而肮脏地插入,以便您的产品插入按计划工作。

      【讨论】:

        【解决方案3】:

        这取决于您要测试的内容。如果您正在测试一个业务逻辑组件——那么它的数据来自哪里并不重要,您可能会使用模拟或手动存根类来模拟组件在野外调用的数据访问例程。我唯一一次搞砸数据访问是在我实际测试数据访问组件本身的时候。

        即便如此,我还是倾向于在 TestFixtureSetUp 方法中打开一个 DB 事务(显然这取决于您可能使用的单元测试框架)并在测试套件 TestFixtureTeardown 结束时回滚事务。

        【讨论】:

          【解决方案4】:

          模拟框架使您能够测试您的业务对象。 数据驱动测试通常最终变得更像是一种集成测试而不是单元测试,它们还承担着管理测试执行前后数据存储状态以及连接和执行查询所花费的时间的负担。

          一般来说,我会避免从您的业务对象中进行涉及数据库的单元测试。至于测试您的数据库,您需要不同的策略。

          话虽如此,您永远无法完全摆脱数据驱动测试,只限制实际需要调用后端系统的测试数量。

          【讨论】:

            【解决方案5】:

            听起来您可能正在测试基于消息的系统,或具有高度参数化接口的系统,其中有大量的输入数据排列。

            一般来说,标准unti测试的所有规则仍然成立:

            • 尽量使被测单元尽可能小且离散。
            • 尝试使测试独立。
            • 分解依赖关系的因子代码。
            • 使用模拟和存根替换依赖项(如数据访问)

            完成此操作后,您将从测试中消除很多复杂性,并有望揭示出好的单元测试集,并简化示例数据。

            然后编译样本数据以进行仍然需要复杂输入数据的测试的好方法是Orthogonal testing,或参见here

            我使用这种方法为 WCF 和 BizTalk 解决方案生成测试计划,其中输入消息的排列可以创建多个可能的执行路径。

            【讨论】:

              【解决方案6】:

              对于在相同逻辑上但使用不同数据的大量不同运行,您可以使用 CSV,输入任意多的列,输出的最后一列等。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2016-04-04
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-03-21
                • 2011-03-25
                • 1970-01-01
                相关资源
                最近更新 更多