我有一个应用程序返回依赖于指定时间的数据,我可以指定天,月或年。问题是,如果我今天要运行该应用程序并要求它返回1个月前的数据,并且在3个月的时间内我要求该应用程序返回该日期前一个月的数据(即从该日期开始的1个月) )结果显然会有所不同。由于这种动态特性,我发现很难创建单元测试,因为我必须根据运行测试的时间来更改日期。这是否表示设计不好或这是一个例外情况?
答案 0 :(得分:5)
这不是必然一个糟糕的设计,但该软件并非设计用于简单测试,许多人认为易于测试的设计是良好设计的必要方面。 / p>
如果可以修改代码以查找指定日期1个月内的数据,则生产代码可以轻松传递当前日期,测试代码可以使用固定日期。
答案 1 :(得分:2)
这是一个问题 - 但不一定是糟糕的设计。
对于单元测试,您需要提供假模块来处理基于时间和时间的数据的生成。这类似于处理数据库时所需要的。
(而且我还不确定像我这样提供DBMS的人应该在单元测试人员都假设“你要假装数据库”时进行单元测试,但这是一个单独的讨论。)< / p>
答案 2 :(得分:2)
解决特定问题的一种方法是将时间本身封装在一个单独的对象中,然后在测试中可以强制此对象返回一些已知时间。
答案 3 :(得分:1)
你可以重写它,这样获得当前日期的片段和解析日期的片段是分开的。换句话说,您在一个函数中获取日期并将其结果传递给另一个解析它的函数。这样,您可以通过传递固定日期来测试解析。
答案 4 :(得分:1)
你在做什么不是unit test。单元测试应该只运行代码的小“单元”和代码。通过合并系统时间,您还可以测试当前运行的环境。那是system test。单元测试是确保您编写的代码编写正确的非常有效的工具,但它可以帮助您以“可测试”的方式编写代码。
有一些易于学习但很难掌握的技巧可以帮助您编写可测试的代码。他们通常都遵循相同的模式,即在代码中创建他们称之为“接缝”的模式,然后在测试时将“stubs”或“mock objects”注入这些接缝。
要弄清楚的第一件重要事情是接缝的去向。这并不难。基本上,无论何时构建一个新对象,这都是一个接缝的好地方。这个规则的先决条件是你有一个相当不错的面向对象设计开始。 (Google Testing Blog的那些人认为你不能单独测试imperative code,因为你不能做dependency injection。)接缝的另一个好地方就是你与外界交谈的任何时候数据源,如操作系统,文件系统,数据库,Internet等。这就是你正在做的事情。
您需要系统时间。这就是你应该去的地方。我建议您在此处获取a good book以获得对所有选项的完整处理,但这是您可以做的一个示例。至少有2或3种其他方法可以“注入你的依赖关系”当前的系统时间。我将使用Python作为伪代码,但它适用于任何OO语言:
class MyClass(object):
def _get_current_time(self):
'''This is a test seam'''
return datetime.datetime.now()
def age(self):
return self._get_current_time() - self._birthday
然后在您的测试代码中,执行以下操作:
class FakeMyClass(MyClass):
def __init__(self, test_time, *args, **kwargs):
self._test_time = test_time
MyClass.__init__(self, *args, **kwargs)
def _get_current_time(self)
return self._test_time
现在,如果您使用FakeMyClass
进行测试,则可以注入所需的任何系统时间:
myclass = FakeMyClass(t)
self.assertEqual(myclass.age(), expected_age)
同样,这是一个非常大的话题,所以我建议你买一本好书。
答案 5 :(得分:0)
<强>也许即可。这肯定是耦合设计的症状。顺便说一句,这是一个非常好而复杂的问题。
我希望阅读比这个更好的答案。
答案 6 :(得分:0)
重新阅读你的问题之后,我不确定你要测试的是什么。
如果#1,那么也许您可以使用内存数据库,您可以使用固定数据在单元测试中初始化。为此,我之前使用过H2。搜索嵌入式数据库。
如果是#2或#3,您应该能够固定测试数据并获得已知结果。如果嵌入了sql生成或结果处理,那么您可以使用模拟来模拟接收方法。
例如,我通常有一个名为QueryRunner的类,它接受我想要运行的SQL以及它将调用以处理结果集的类。这样我就可以模拟QueryRunner并查看是否使用我期望的SQL调用它,并且它是否被调用了适当的次数。比试图实际模拟JDBC类要容易得多。
答案 7 :(得分:0)
如果测试数据发生变化,则表示它是一种“实时”数据库。为什么要在这样的数据库上运行测试?如果测试结果在两次运行之间不一样,那就违背了测试的目标,单元测试应该是可重复且可靠的,如果你每隔几个月重写它就不会......
您的测试可能需要一个“静态”数据库,一旦您为需要测试的不同场景/用例添加了所有数据,您就可以进行备份,只要数据获得,您就可以恢复它“损坏”。或者甚至更好,你有脚本会在运行测试套件之前将所有这些数据插入空数据库。
答案 8 :(得分:0)
我认为,如果它是真正的单元测试,它是一个糟糕的设计。你的单位/班级依赖于时间,如果没有办法嘲笑时间,那么“单位”不是一个单位。要进行单元测试,您需要能够影响影响控制流的所有外部依赖项。在实践中,这意味着有一个类负责选择您需要的信息,该信息接受一些负责时间的委托。这可能是Calendar对象或类似的东西。
如果它是您正在测试的完整模块,也许您不是单元测试。同样的原则也是如果,如果时间从根本上影响输出,则日期应该是一个可选输入,不仅用于测试,而且因为系统需要在特定日期运行。对于某些人来说,这可能是推测性的:但最好不要直接在系统深处的模块中引用全局状态,而是传递这种状态。测试的难度很容易变成维护困难。所有需要发生的事情都是让客户建议一个命令,比如处理批次(或者你正在做的任何事情,因为系统崩溃了,因为系统崩溃了。特别是将日期深埋,可能多次在当系统对选择逻辑至关重要时,可以在以后进行更好的维护。
如果您的系统具有大量依赖项(很复杂),您可能需要考虑使用Container来管理依赖注入。这些容器通常称为IoC容器,允许将系统编写为松散耦合的对象,这些对象通过容器通过某种配置“连接”在一起。
答案 9 :(得分:0)
什么是阻止您的测试为您的代码提供测试数据?
这可以通过两种方式实现,或者使用其他方法以编程方式将数据放入数据源或模拟数据源。
就个人而言,我很想改变我的代码,以便通过接口派生类访问数据源。这样,您就可以模拟数据源并提供不依赖于时间的已知数据。
如果您使用的是Visual Studio,则可以使用它自动为数据源对象创建接口类,并对其进行修改以将该接口用作基类。
答案 10 :(得分:-1)
这是一个常见问题,尤其是在创建报告时。我看到了三种简单的方法:
1)回退/转发系统的时钟。
2)将测试数据上的日期更新为始终在您的范围内(即更新db set date = now())。
3)在干净的系统上运行查询,然后应用更改,然后再次运行查询。这可能是最简单的方法。