如何对商业应用程序进行单元测试?

14
人们如何进行业务应用的单元测试?我看到了很多关于“易于测试”的例子,例如计算器等简单示例。人们如何进行数据密集型应用程序的单元测试?人们如何组织样本数据?在许多情况下,一个测试的数据可能根本无法适用于另一个测试,这使得仅拥有一个测试数据库变得困难?
测试代码中的数据访问部分相当简单。难以测试的是所有与数据交互的方法。例如,想象一下发布过程,需要大量数据访问来确定要发布的内容,调整数字等等。此过程中存在许多中间步骤(需要进行测试),以及在之后还需要测试以确保发布成功。其中一些步骤实际上可能是存储过程。
过去,我尝试将测试数据插入测试数据库中,然后运行测试,但老实说,编写这种代码非常痛苦(容易出错)。我也尝试过预先构建测试数据库并回滚更改。那样做还行,但在许多场合下这也不是很容易(许多人会认为这是集成测试;无论如何,我仍然需要以某种方式进行测试)。
如果答案是没有什么好的处理方式,现在只能这样,那也很有用。
欢迎提出任何想法、建议或技巧。
6个回答

6

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

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

数据库连接测试

当我有连接到数据库的自动化测试时,我通常会创建一个单一的测试数据库模板,该模板包含所有测试所需的数据。当运行自动化测试时,为每个测试生成一个新的测试数据库,该测试数据库从模板生成。测试数据库必须不断重新生成,因为测试通常会更改数据。随着添加测试,我通常会在测试数据库模板中追加更多数据。

这种测试方法有一些好处。显而易见的优点是测试还可以练习您的模式。另一个优点是,在设置初始测试之后,大多数新测试都将能够重复使用现有的测试数据。这使得添加更多测试变得容易。

缺点是测试数据库将变得笨重。由于数据通常会逐个测试添加,因此它将不一致甚至可能不真实。当存在重大数据库模式更改时(对我来说通常意味着我最终会诅咒自己),您还将结束对设置测试数据库的人进行谩骂。

如果无法随意生成新的测试数据库,则显然无法使用此测试样式。

模拟持久层测试

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

使用模拟对象测试的好处是,您可以变得非常具体。通常情况下,有些执行路径在没有模拟对象的自动化测试中根本无法到达。它们还使您摆脱了维护大型、单块的测试数据集的负担。

另一个好处是缺少外部依赖项。由于模拟对象模拟了持久层,因此您的测试不再依赖于数据库。这通常是选择哪种模式的决定性因素。处理遗留数据库系统或具有严格许可条款的数据库时,模拟对象似乎更受欢迎。

模拟对象的缺点是它们经常导致大量额外的测试代码。这并不可怕,因为无论您运行测试的次数如何,几乎任何数量的测试代码都是便宜的,但是测试代码比生产代码多可能会让人感到烦恼。


2

这取决于你要测试什么。如果你正在测试业务逻辑组件,那么数据来自何处并不重要,你可能会使用模拟或手动编写的存根类来模拟组件在实际环境中调用的数据访问过程。只有当我真正测试数据访问组件本身时,才会涉及到数据访问。

即使这样,我也倾向于在 TestFixtureSetUp 方法中开启一个 DB 事务(显然这取决于你使用的单元测试框架),并在测试套件 TestFixtureTeardown 结束时回滚该事务。


2
模拟框架可以帮助您测试业务对象。数据驱动测试往往成为集成测试而不是单元测试,并且需要处理数据存储的状态前后执行测试以及连接和执行查询所需的时间。
一般来说,我会避免从业务对象访问数据库进行单元测试。至于测试数据库,您需要采用不同的策略。
话虽如此,您永远无法完全摆脱数据驱动测试,只能限制实际需要调用后端系统的测试数量。

2

我必须赞同@Phil Bennett的评论,因为我试图用回滚解决方案来处理这些集成测试。

我有一篇非常详细的帖子,介绍了如何对数据访问层进行集成测试。这里

我展示了样例数据访问类、基类和样例数据库事务fixture类,以及完整的CRUD集成测试,其中包含样例数据。采用这种方法,您不需要多个测试数据库,因为您可以控制每个测试中输入的数据,并在测试完成后将所有事务回滚,使您的数据库保持干净。

关于在应用程序内部测试业务逻辑,我也赞同@Phil和@Mark的评论,因为如果您模拟业务对象所具有的所有依赖项,则可以很容易地逐个实体地测试应用程序逻辑;)

编辑:那么您是否正在寻找一个巨大的集成测试,来验证从逻辑到数据运行再到逻辑验证的所有内容?如果是这样,您可以将其分为两个步骤:

  • 1-单元测试在推送数据到您的数据访问代码之前发生的逻辑。例如,如果您有一些根据某些属性计算数字的代码,请编写一个仅检查该1函数逻辑是否按照您要求的方式执行的测试。模拟数据访问类的任何依赖项,以便您可以忽略它,只测试应用程序逻辑。

  • 2-集成测试在获取您的操作数据(从我们之前单元测试的方法中)并调用相应的存储过程后发生的逻辑。在特定于数据的测试类中执行此操作,以便完成后可以回滚。运行存储过程后,针对数据库进行查询,以获取对象,因为我们已经对数据进行了一些逻辑操作,并验证它是否具有您期望的值(存储过程后逻辑/等)

如果您需要存储过程运行时在数据库中插入条目,请在运行包含逻辑的sproc之前插入该数据。例如,如果您需要测试产品,可能需要插入供应商和类别条目,因此在插入产品之前,为供应商和类别进行快速且简单的插入,以便您的产品插入按计划进行。


1

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

总的来说,标准单元测试的所有规则仍然适用:

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

完成这些步骤后,您将从测试中删除大量复杂性,希望揭示出良好的单元测试集,并简化样本数据。

编译仍需要复杂输入数据的测试样本数据的良好方法是正交测试,或者请参见此处

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


0

对于同一逻辑但使用不同数据的多次运行,您可以使用 CSV,输入可以有任意多列,最后一列为输出等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接