在某些情况下如何引起程序员的注意?


13

让我们从一个例子开始。

比方说,我有一个称为方法export,该方法在很大程度上取决于数据库架构。“严重依赖”是指我知道(经常)向特定表中添加新列会导致相应的export方法更改(通常也应将新字段也添加到导出数据中)。

程序员通常会忘记更改export方法,因为还不清楚您是否应该看一下。我的目标是迫使程序员明确决定是否要忘记查看该export方法还是只是不想在导出数据中添加字段。我正在寻找针对此问题的设计解决方案。

我有两个想法,但它们都有缺陷。

智能的“阅读所有”包装

我可以创建智能包装器,以确保显式读取所有数据。

像这样:

def export():
    checker = AllReadChecker.new(table_row)

    name    = checker.get('name')
    surname = checker.get('surname')
              checker.ignore('age') # explicitly ignore the "age" field

    result = [name, surname] # or whatever

    checker.check_now() # check all is read

    return result

因此,checker断言if是否table_row包含另一个未读取的字段。但是,所有这些东西看起来有点沉重,并且(可能)会影响性能。

“检查方法”单元测试

我可以创建一个能够记住最后一个表模式的单元测试,并且每次更改表时都会失败。在那种情况下,程序员会看到类似“不要忘了检查export方法”的信息。要隐藏警告程序员,可以(或不会-这是一个问题)签出export手动(这是另一个问题)通过在其中添加新字段来修复测试。

我还有其他一些想法,但是它们太难实现或难以理解(我不希望该项目成为难题)。


上面的问题只是我不时遇到的更多问题的一个示例。我想绑定一些代码和/或基础结构,因此更改其中的一个代码会立即提醒程序员检查另一个代码和/或基础结构。通常,您有一些简单的工具,例如提取通用逻辑或编写可靠的单元测试,但是我正在寻找更复杂情况的工具:也许我现在知道一些设计模式。


您能否export基于模式生成?
coredump

它不能被无端生成,这就是为什么我应该要求程序员查看代码并做出决定的原因。
Vadim Pushtaev 2015年

将两个字段名列表(export和dont-export)添加到export类,并进行单元测试以检查这两个列表是否一起包含数据库中的全部字段,这是一种解决方案吗?
Sjoerd Job Postmus

尽管可以检查export您是否实际需要的一切,但是您可以自动生成一个测试吗?
biziclop 2015年

1
源代码中的注释太简单了吗?通常情况会因为没有提醒而错过,可以通过注释来解决。
gbjbaanb 2015年

Answers:


11

您对单元测试的想法是正确的,但是您的实现是错误的。

如果export与模式相关且已更改模式,则有两种可能的情况:

  • 无论是export仍然可以工作得很好,因为它是由模式略有变化的影响,

  • 否则就坏了。

在这两种情况下,构建的目标都是跟踪这种可能的回归。一堆测试(可能是集成测试,系统测试,功能测试或其他测试)确保您的export过程与当前模式兼容,而与上一次提交以来它是否更改无关。如果这些测试通过,那就太好了。如果失败,则向开发人员发出信号,表明他可能错过了某些内容,并且清楚地指示了要看的地方。

为什么实施错误?好吧,有几个原因。

  1. 与单元测试无关...

  2. ...,实际上,这甚至不是测试。

  3. 最糟糕的是,修复“测试”实际上需要更改“测试”,即所做的操作与完全无关export

相反,通过对export过程进行实际测试,可以确保开发人员可以修复export


更一般而言,当您遇到某个类别更改总是或通常要求完全不同的类别更改的情况时,这是一个很好的信号,表明您设计错误,并且违反了“单一职责原则”。

当我专门讨论类时,它或多或少也适用于其他实体。例如,数据库模式的更改应自动反映在您的代码中(例如,通过许多ORM使用的代码生成器),或者至少应易于本地化:如果我DescriptionProduct表中添加列,并且不使用ORM或代码生成器,我至少希望在DAL的类中进行单个更改Data.Product,而不需要遍历所有代码库并Product在表示层中找到某些类的出现。

如果您不能合理地将更改限制在一个位置(或者因为您根本无法使用更改,或者因为它需要大量开发),那么您就有可能出现回归的风险。当我更改class A,并且B代码库中某处的class 停止工作时,这是一种回归。

测试降低了回归的风险,而更重要的是,它向您显示了回归的位置。这就是为什么当您知道某个位置的更改会导致代码库的完全不同的部分出现问题时,请确保您有足够的测试,一旦此级别出现回归,就会发出警报。

在所有情况下,都应避免仅依赖注释。就像是:

// If you change the following line, make sure you also change the corresponding
// `measure` value in `Scaffolding.Builder`.

永远行不通。不仅开发人员在大多数情况下不会阅读它,而且它通常会因移开或远离相关行而结束,并且变得难以理解。


是的,“测试”确实不是测试,它是一种陷阱,某些陷阱会If you change the following line...自动起作用,不能简单地忽略。我从未见过有人实际使用过此类陷阱,因此对此表示怀疑。
Vadim Pushtaev

1
问题是该类B不会停止工作,可能会开始不正确地工作。但是我无法预测未来,也无法编写能够预测未来的测试,因此我试图提醒开发人员编写该新测试。
Vadim Pushtaev

@VadimPushtaev:谈到测试,“也许开始工作不正确”和“停止工作” 完全是同一回事。您无法预测未来,但是您应该能够确切了解需求,并测试您的实际产品是否满足了这些需求。
Arseni Mourzenko

是的,那是一回事。我实际上想提醒开发人员考虑新的要求。我只想挥挥手:“您好,您确定不会忘记出口吗?问经理,这是一个普遍的问题。
Vadim Pushtaev 2015年

无论如何,谢谢您,您的回答帮助我整理了思路,现在我有一个计划。
Vadim Pushtaev

3

在我看来,您所做的更改没有得到充分说明。假设您居住的地方没有邮政编码,因此地址表中没有邮政编码列。然后介绍邮政编码,或者您开始​​与居住有邮政编码的客户打交道,并且必须将此列添加到表中。

如果工作项只是说“将邮政编码列添加到地址表”,则可以,导出将被破坏,或者至少不会导出邮政编码。但是用于输入邮政编码的输入屏幕呢?列出所有客户及其地址的报告?添加此列时,有很多事情需要更改。记住这些事情是一项重要的工作-您不应该依靠随机的代码工件来“吸引”开发人员进行记住。

在决定添加有意义的列(即,不仅是某些缓存的总计或非规范化查找或不属于导出,报表或输入屏幕中的其他值)时,创建的工作项应包括所有更改需要-添加列,更新填充脚本,更新测试,更新导出,报告,输入屏幕等。这些可能不会全部分配给同一个人(或由同一个人领取),但必须全部完成。

有时,开发人员选择自己添加列,作为实现一些较大更改的一部分。例如,某人可能已经编写了一个工作项,以将某些内容添加到输入屏幕和报告中,而不考虑其实现方式。如果这种情况经常发生,您将需要确定您的工作项添加器是否需要了解实施细节(以便能够添加所有正确的工作项),或者开发人员是否需要知道工作项-加法器有时会将事情遗漏掉。如果是后者,那么您需要一种“不要仅仅更改架构;停止并考虑还会影响什么的文化”。

如果有很多开发人员并且这种情况不止一次发生,那么我将为团队负责人或其他高级人员设置签入警报,以在架构更改时得到警报。然后,该人可以寻找相关的工作项来应对方案更改的后果,如果缺少工作项,则不仅可以添加它们,还可以教育将其排除在计划之外的人。


2

几乎总是,在创建导出时,我还会创建相应的导入。由于我还有其他测试可以完全填充要导出的数据结构,因此我可以构建一个往返单元测试,将完全填充的原始文件与导出然后导入的副本进行比较。如果它们相同,则导出/导入完成;否则,将完成导出。如果它们不相同,则单元测试将失败,并且我知道导出机制需要更新。


这可以检查是否已保存每个对象并从db重新加载了该对象。它不涉及现有数据库字段没有对应对象字段的情况。
k3b

@ k3b是。至少在我的系统中,如果不再使用某些东西但没有从架构中删除某些东西(通常不在导出机制的范围内),通常会发生这种情况-单元持久性测试会检查对象的每个字段是否仍然存在,但是我不测试未使用的列,因为这对系统功能没有明显的影响。
Pete Kirkham 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.