在数据库而不是假存储库上运行测试是不好的做法吗?
Posted
技术标签:
【中文标题】在数据库而不是假存储库上运行测试是不好的做法吗?【英文标题】:Is it bad practice to run tests on a database instead of on fake repositories? 【发布时间】:2010-10-11 02:14:30 【问题描述】:我知道优势是什么,并且在处理更复杂的系统时会使用假数据。
如果我正在开发一些简单的东西,我可以很容易地在一个真实的数据库中设置我的环境,并且正在访问的数据非常小,以至于访问时间不是一个因素,我只运行了几个测试。
创建假数据仍然很重要,还是我可以忘记额外的编码并直接跳到真实数据?
当我说真实数据库时,我指的不是生产数据库,而是测试数据库,而是使用真实的实时 DBMS 和与真实数据库相同的架构。
【问题讨论】:
“真正的数据库”是指生产数据库 - 还是使用相同 DBMS 的数据库? 我的意思是相同的 DBMS 和架构,但在测试数据库上 Thnx,然后我确认我的答案。 【参考方案1】:使用假数据而不是真实数据库的原因是:
-
速度。如果您的测试很慢,您将不会运行它们。模拟数据库可以使您的测试运行得比其他方式快得多。
控制。您的测试需要成为测试数据的唯一来源。当您使用假数据时,您的测试会选择您将使用哪些假数据。因此,您的测试不会因为有人以不熟悉的状态离开数据库而被破坏。
秩序独立。我们希望我们的测试能够以任何顺序运行。一个测试的输入不应依赖于另一个测试的输出。当您的测试控制测试数据时,测试可以相互独立。
环境独立性。您的测试应该可以在任何环境中运行。您应该能够在火车上、飞机上、家中或工作中运行它们。他们不应该依赖外部服务。当您使用假数据时,您不需要外部数据库。
现在,如果您正在构建一个小型应用程序,并且通过使用真正的数据库(如 mysql)可以实现上述目标,那么请务必使用数据库。我愿意。但请不要误会,随着应用程序的增长,您最终将面临模拟数据库的需要。没关系,当你需要的时候做。雅尼。只要确保你在需要时这样做。如果你放手,你会付出代价的。
【讨论】:
速度是我看到的使用真实数据库的唯一问题。控制、顺序独立和环境独立都可以用真实的数据库来解决。例如,确保在运行第一个测试之前删除数据库,在事务下运行所有测试,不要提交事务。创建一个数据库层的假实现,它的行为足以编写有用的测试,而不仅仅是将您的 DAL 指向特定于测试的数据库。【参考方案2】:这有点取决于您要测试的内容。通常你想测试代码中的实际逻辑而不是数据库中的数据,所以仅仅为了运行测试而建立一个完整的数据库是浪费时间。
还要考虑维护测试和测试数据库的工作量。使用数据库测试您的代码通常意味着您正在测试整个应用程序,而不是孤立地测试不同的部分。这通常会导致大量工作使数据库和测试保持同步。
最后一个问题是测试应该独立运行,因此每个测试应该在自己的数据库版本上运行,或者让它保持与测试运行前完全相同的状态。这包括测试失败后的状态。
话虽如此,如果您真的想在您的数据库上进行测试,您可以。有一些工具可以帮助设置和拆除数据库,例如 dbunit。
我见过有人尝试像这样创建单元测试,但结果几乎总是比实际价值要多得多。大多数在项目中途放弃它,大多数在项目期间完全放弃 ttd,一般认为经验转移到单元测试。
因此,我建议保持测试简单和隔离,并将您的代码封装得足够好,以便可以单独测试您的代码。
【讨论】:
【参考方案3】:只要 Real DB 不会妨碍您,并且您可以通过这种方式走得更快,我会务实并坚持下去。
在单元测试中,“测试”比“单元”更重要。
【讨论】:
恐怕我不同意。不测试单元的测试将更难维护,这可能导致失去编写测试的意愿。我还认为单元测试会带来更简洁的设计,因为需要对单元进行测试。 我的意思是有一个涵盖多个单元的测试总比没有测试好。如果他用真正的 DBMS(不是生产的)进行单元测试,与针对假 DB 的单元测试相比,它会改变整体设计吗?【参考方案4】:我认为这取决于您的查询是否固定在存储库中(更好的选择,IMO),或者存储库是否公开可组合查询;例如 - 如果你有一个存储库方法:
IQueryable<Customer> GetCustomers() ...
然后你的 UI 可以请求:
var foo = GetCustomers().Where(x=>SomeUnmappedFunction(x));
bool SomeUnmappedFunction(Customer customer)
return customer.RegionId == 12345 && customer.Name.StartsWith("foo");
这对于基于对象的虚假 repo 将通过,但对于 actual db 实现将失败。当然,您可以通过让存储库在内部处理所有查询(无外部组合)来取消这一点;例如:
Customer[] GetCustomers(int? regionId, string nameStartsWith, ...) ...
因为这个不能组合,所以可以单独检查DB和UI。使用可组合查询时,如果您希望它有用,则必须在整个过程中使用集成测试。
【讨论】:
【参考方案5】:这取决于数据库是否由测试自动设置,也取决于数据库是否与其他开发人员隔离。
目前这可能不是问题(例如,只有一个开发人员)。但是(对于手动数据库设置)设置数据库是运行测试的额外障碍,这是一件非常糟糕的事情。
【讨论】:
【参考方案6】:如果您只是编写一个您绝对知道不会增长的简单一次性应用程序,那么我认为很多“最佳实践”都会被抛在脑后。
如果您编写的只是一个简单的“联系我们”表单,则您不需要需要使用 DI/IOC 或进行单元测试或模拟您的数据库访问权限。但是,很难在“简单”应用和“复杂”应用之间划清界限。
换句话说,请使用您的最佳判断,因为对此没有一成不变的答案。
【讨论】:
【参考方案7】:只要您不将它们视为“单元”测试,就可以对场景执行此操作。那些将是集成测试。您还需要考虑是否会一次又一次地通过 UI 进行手动测试,因为您可能只是自动化了冒烟测试。鉴于此,您甚至可以考虑根本不进行集成测试,而只在功能/ui 测试级别工作(因为它们已经涵盖了集成)。
正如其他人所指出的那样,很难在复杂/非复杂之间划清界限,而现在通常为时已晚:(。如果你已经习惯这样做,我相信你会赢'不会有太多开销。如果不是这样,您可以从中学习:)
【讨论】:
【参考方案8】:假设您想自动执行此操作,最重要的是您可以以编程方式生成初始条件。听起来就是这样,而且您正在测试真实世界的数据。
但是,也有一些缺点:
您的真实数据库可能无法涵盖代码中的某些条件。如果您有虚假数据,就会导致这种行为发生。
正如您所指出的,您有一个简单的应用程序;当它变得不那么简单时,您将希望拥有可以归类为单元测试和系统测试的测试。单元测试应该针对一个简单的功能,这将更容易处理假数据。
【讨论】:
【参考方案9】:假存储库的一个优点是您的回归/单元测试是一致的,因为您可以预期相同查询的相同结果。这使得构建某些单元测试变得更加容易。
如果您的代码(如果不是只读查询)修改数据,则会有几个缺点: - 如果您的代码中有错误(这可能是您测试的原因),您最终可能会破坏生产数据库。即使你没有打破它。 - 如果生产数据库随着时间的推移发生变化,尤其是在您的代码执行时,您可能会忘记添加的测试材料,并且以后很难将其从数据库中清除。 - 来自访问数据库的其他系统的生产查询可能会将您的测试数据视为真实数据,这可能会破坏未来某处重要业务流程的结果。例如,即使您使用特定标志或前缀标记数据,您能否确保访问数据库的任何人都遵守此架构?
此外,某些数据库受隐私法监管,因此根据您的合同以及谁拥有主数据库,您可能会或可能不会被法律允许访问真实数据。
如果您需要在生产数据库上运行,我建议您在可以在高峰时段轻松创建的副本上运行。
【讨论】:
当然我不会在生产数据库上进行测试。我想在没有模拟数据的测试数据库上进行测试。 您的问题并不清楚,因为您谈到了听起来像生产数据库的“真实数据库”。【参考方案10】:这是一个非常简单的应用程序,您看不到它在增长,我认为在真实数据库上运行您的测试没有问题。但是,如果您认为此应用程序会增长,那么在您的测试中说明这一点很重要。
让一切尽可能简单,如果您以后需要更灵活的测试,那就去做吧。不过请提前计划,因为您不希望在 3 年内拥有一个依赖于旧的和 hacky(对于大型应用程序)测试的大型应用程序。
【讨论】:
【参考方案11】:对数据库运行测试的缺点是速度慢,并且在运行测试之前设置数据库状态很复杂。
如果您对此有控制权,则直接针对数据库运行测试没有问题;这实际上是一种很好的方法,因为它比针对虚假数据运行更好地模拟您的最终产品。关键是采取务实的方法,将最佳实践视为指导方针而不是规则。
【讨论】:
以上是关于在数据库而不是假存储库上运行测试是不好的做法吗?的主要内容,如果未能解决你的问题,请参考以下文章
将所有信息放入列表而不是 OOP 中的类继承是不好的做法吗?
Javascript - 在对象中存储函数 - 不好的做法? [关闭]
在页面重新加载之间直接访问 redux 存储以访问和保持状态是一种不好的做法吗