这个问题或多或少与编程语言无关。然而,由于我现在主要使用Java,所以我将从中绘制示例。我也在考虑OOP的情况,所以如果你想测试一个方法,你需要一个该方法类的实例。
unit tests 的核心规则是它们应该是自治的,并且可以通过将类与其依赖项隔离来实现。有几种方法可以做到这一点,这取决于你是否使用IoC注入依赖项(在Java世界中我们有Spring,EJB3和其他提供注入功能的框架/平台)和/或模拟对象(对于Java,您有JMock和EasyMock)来将正在测试的类与其依赖项分开。
如果我们需要测试不同类别的方法组*并看到它们是很好的集成,我们写 integration tests 。这是我的问题!
如何在需要时确定测试所需的数据库数据?你为什么选择你选择的方法?
请提供一个动机的答案,因为这是有趣的部分所在的动机。请记住,只是说“这是最好的做法!”它不是一个真正的动机,它只是重复你从别人那里读过或听过的东西。对于这种情况,请解释为什么这是最佳做法。
*我在我的单元测试定义中包含一个在同一个类的(相同或其他)实例中调用其他方法的方法,即使它在技术上可能不正确。请随意纠正我,但让我们把它作为一个侧面问题。
答案 0 :(得分:50)
我更喜欢使用API调用创建测试数据。
在测试开始时,您将创建一个空数据库(内存中或与生产中使用的相同),运行安装脚本以对其进行初始化,然后创建数据库使用的任何测试数据。例如,可以使用Object Mother模式来组织测试数据的创建,以便可以在许多测试中重用相同的数据,可能具有微小的变化。
您希望在每次测试之前让数据库处于已知状态,以便在没有副作用的情况下进行可重复测试。因此,当测试结束时,您应该删除测试数据库或回滚事务,以便下一个测试可以始终以相同的方式重新创建测试数据,无论先前的测试是通过还是失败。
我之所以要避免导入数据库转储(或类似),是因为它会将测试数据与数据库模式耦合在一起。当数据库架构发生更改时,您还需要更改或重新创建测试数据,这可能需要手动操作。
如果在代码中指定了测试数据,您将掌握IDE的重构工具的强大功能。当您进行影响数据库架构的更改时,它可能也会影响API调用,因此您无论如何都需要使用API重构代码。通过几乎相同的工作,您还可以重构测试数据的创建 - 特别是如果重构可以自动化(重命名,引入参数等)。但是,如果测试依赖于数据库转储,除了重构使用API的代码之外,还需要手动重构数据库转储。
与集成测试数据库相关的另一件事是测试从先前的数据库模式升级是否正常。为此,您可能需要阅读本书Refactoring Databases: Evolutionary Database Design或本文:http://martinfowler.com/articles/evodb.html
答案 1 :(得分:5)
在集成测试中,您需要使用真实数据库进行测试,因为您必须验证您的应用程序是否可以实际与数据库通信。将数据库隔离为依赖关系意味着您推迟了对数据库是否已正确部署的真实测试,您的架构是否符合预期,并且您的应用程序配置了正确的连接字符串。部署到生产环境时,您不希望发现这些问题。
您还希望使用预先创建的数据集和空数据集进行测试。您需要使用仅包含默认初始数据的空数据库测试应用程序启动的路径,并开始创建和填充数据,还需要定义明确定义的数据集,这些数据集针对您要测试的特定条件,如压力,性能和等等。
另外,在每个州之前,让您知道数据库处于一个众所周知的状态。您不希望在集成测试之间存在依赖关系。
答案 2 :(得分:3)
我使用DBUnit来获取数据库中记录的快照,并以XML格式存储它们。然后我的单元测试(我们称他们需要数据库时进行集成测试),可以在每次测试开始时从XML文件中擦除和恢复。
我不确定这是否值得付出努力。一个问题是依赖于其他表。我们单独留下了静态参考表,并构建了一些工具来检测和提取所有子表以及请求的记录。我读了某人的建议,禁用集成测试数据库中的所有外键。这样可以更容易地准备数据,但是您不再检查测试中的任何参照完整性问题。
另一个问题是数据库架构更改。我们编写了一些工具,可以为拍摄快照后添加的列添加默认值。
显然,这些测试比纯单元测试慢。
当您尝试测试一些遗留代码时,为单个类编写单元测试非常困难,这种方法可能值得付出努力。
答案 3 :(得分:3)
听起来你的问题实际上是两个问题。您是否应该使用从测试中排除数据库?你什么时候在数据库中,那么你应该如何在数据库中生成数据呢?
如果可能,我更喜欢使用实际的数据库。 CROD类中的查询(用SQL,HQL等编写)经常在面对实际数据库时返回令人惊讶的结果。最好早点解决这些问题。开发人员通常会为CRUD编写非常薄的单元测试;仅测试最良性的病例。使用实际数据库进行测试可以测试您可能根本不知道的所有类型的案例。
话虽如此,可能还有其他问题。每次测试后,您都希望将数据库返回到已知状态。我目前的工作是通过执行所有DROP语句来核对数据库,然后从头开始完全重新创建所有表。这在Oracle上非常慢,但如果您使用内存数据库(如HSQLDB),则速度非常快。当我们需要清除Oracle特定问题时,我们只需更改数据库URL和驱动程序属性,然后针对Oracle运行。如果您没有这种数据库可移植性,那么Oracle也有某种数据库快照功能,可以专门用于此目的;将整个数据库回滚到某个先前的状态。我确定其他数据库有什么。
根据数据库中的数据类型,API或加载方法可能会更好或更差。当您拥有具有多种关系的高度结构化数据时,API将使您的生活变得更轻松,从而使您的数据之间的关系变得明确。在创建测试数据集时,您将更难犯错误。正如其他海报所述,重构工具可以自动处理数据结构的一些变化。通常我认为将API生成的测试数据视为组成场景是有用的;当用户/系统完成步骤X,Y Z然后测试将从那里开始。可以实现这些状态,因为您可以编写一个程序来调用您的用户将使用的相同API。
当您需要大量数据时,加载数据变得更加重要,您的数据之间几乎没有关系,或者数据的一致性无法使用API或标准关系机制表达。在我的团队工作的一项工作是为大型网络数据包检查系统编写报告应用程序。当时的数据量非常大。为了触发有用的测试用例子集,我们确实需要嗅探器生成的测试数据。这样,关于一个协议的信息之间的相关性将与关于另一个协议的信息相关联。很难在API中捕获它。
大多数数据库都有工具来导入和导出表的分隔文本文件。但通常你只想要它们的子集;使用数据转储更复杂。在我目前的工作中,我们需要对Matlab程序生成并存储在数据库中的实际数据进行一些转储。我们有工具可以转储数据库数据的子集,然后将其与“基础事实”进行比较以进行测试。我们的提取工具似乎在不断修改。
答案 4 :(得分:3)
为什么这两种方法被定义为独占?
我看不出任何可行的论据 不使用预先存在的数据集,尤其是具有的特定数据 过去造成了问题。
我做不到 看到不的任何可行论据 以编程方式扩展该数据 所有可能的条件 你可以想象会导致问题甚至是 用于集成的随机数据位 测试
在现代agile approaches中,单元测试是每次运行相同测试真正重要的地方。这是因为单元测试的目的不是发现错误,而是在开发时保留应用程序的功能,允许开发人员根据需要进行重构。
另一方面,集成测试旨在找到您不期望的错误。在我看来,每次运行一些不同的数据甚至可以很好。如果出现故障,您只需确保测试保留失败的数据。请记住,在正式的集成测试中,应用程序本身将被冻结,除了错误修复,因此您的测试可以更改为测试最大可能的数量和类型的错误。在集成中,您可以而且应该在应用程序中抛出厨房水槽。
正如其他人所说,当然,所有这一切自然取决于您正在开发的应用程序类型以及您所在的组织类型等。
答案 5 :(得分:1)
我通常使用SQL脚本来填充您讨论的场景中的数据。
这是直截了当的,非常容易重复。
答案 6 :(得分:1)
我同时做两件事,取决于我需要测试的内容:
我从SQL脚本或数据库转储中导入静态测试数据。此数据用于对象加载(反序列化或对象映射)和SQL查询测试(当我想知道代码是否将返回正确的结果时)。
另外,我通常有一些骨干数据(配置,名称查找表的值等)。这些也在此步骤中加载。请注意,此加载是单个测试(以及从头开始创建数据库)。
当我有修改DB(对象 - > DB)的代码时,我通常是针对一个活DB(在内存或某个测试实例中)运行它。这是为了确保代码有效;不要创建任何大量的行。在测试之后,我回滚事务(遵循测试不能修改全局状态的规则)。
当然,规则有例外:
答案 7 :(得分:0)
如果有的话,这可能无法解答您的所有问题,但我在一个项目中做出了针对数据库进行单元测试的决定。在我的情况下,我觉得DB结构也需要测试,即我的数据库设计是否提供了应用程序所需的内容。在项目的后期,当我觉得数据库结构稳定时,我可能会远离这个。
为了生成数据我决定创建一个外部应用程序,用“随机”数据填充数据库,我创建了一个人名和公司名称生成器等。
在外部程序中执行此操作的原因是: 1.我可以通过测试修改的数据重新运行测试,即确保我的测试能够运行几次,并且测试所做的数据修改是有效的修改。 2.如果需要,我可以清理数据库并重新开始。
我同意这种方法有一些失败的地方,但在我的情况下,例如人生代是业务逻辑的一部分,生成测试数据实际上也在测试那部分。
答案 8 :(得分:0)
我们的团队最近遇到了同样的问题。
之前,我们使用 specflow 进行集成测试。使用 specflow,QA 可以编写每个测试用例,其中将必要的测试数据填充到 DB。
现在,QA 想用 postman 来测试 API,他们如何填充数据?一种解决方案是创建用于填充它们的 API。另一个是从 Prod 同步历史数据到测试环境。
一旦我们尝试不同的解决方案并决定采用哪种解决方案,我就会更新我的答案。