此方法是否需要单元测试?如果您的回答是“是”,请通过阅读整个问题确保您理解我的思考过程。
public void UpdateChildSomethings(int parentId, string newVal, int bulkSize) {
var skip = 0;
List<Child> children = null;
while ((children = _.GetChildrenFromDB(parentId, skip, bulkSize)).Count > 0) {
var alteredChildren = AlterChildren(children, newValue); // Note: AlterChildren is fully tested separately.
_.BulkUpdateDB(alteredChildren);
skip += bulkSize;
}
}
首先,我是一个重型单元测试仪。我经常这样做,而且我做得很好。但是根据我的经验,我已经获得了意见,可能需要有人代替我,或者向我提供文档以支持或反对我。
开放免责声明:如果我有一个经过明显测试的方法(例如下面的Alter
和AlterChildren
),并且其中包含保护子句,那么我可能最终会测试保护子句,如果这些测试的覆盖率不超过100%。但是除此之外...
让我们用这种方法开始我的问题:
public void UpdateSomething(int id, string newVal) {
var actualSomething = _.GetFromDB(id);
var alteredSomething = Alter(actualSomething, newVal);
_.UpdateDB(id, alteredSomething);
}
此方法是否需要单元测试?由于多种原因,我个人会拒绝,至少目前不会。特别是如果Alter()
经过了大量测试。从数据库获取和更新数据库的操作对于单元测试没有任何价值,并且无论如何都会被模拟。
假设您遵循我的心态并同意不应该测试该方法,那么该方法呢?
public void UpdateSomething(int id, string newVal) {
var actualSomething = _.GetFromDB(id);
if (actualSomething == null) return;
var alteredSomething = Alter(actualSomething, newVal);
_.UpdateDB(id, alteredSomething);
}
我添加了一个“警卫条款”。这不是业务逻辑或计算。它是确定代码流和提前返回的代码。如果要对此进行单元测试,那么我实际上将在测试GetFromDB的结果,因此将测试模拟。就我而言,测试模拟不是一个有用的测试。
但是,假设您仍然遵循我的思想,并同意基于外部数据的保护条款对单元测试是一种浪费,那么这种方法呢?
public void UpdateChildSomethings(int parentId, string newVal, int bulkSize) {
var skip = 0;
List<Child> children = null;
while ((children = _.GetChildrenFromDB(parentId, skip, bulkSize)).Count > 0) {
var alteredChildren = AlterChildren(children, newValue);
_.BulkUpdateDB(alteredChildren);
skip += bulkSize;
}
}
为清楚起见,我将其重构为分解while
子句
/// Uses parentId to retrieve applicable children in chunks of bulkSize.
/// children are processed separately.
/// Passes processed children to the DB to be updated.
public void UpdateChildSomethings(int parentId, string newVal, int bulkSize) {
var skip = 0;
List<Child> children = null;
while (true) {
children = _.GetChildrenFromDB(parentId, skip, bulkSize);
if (children.Count == 0) break;
var alteredChildren = AlterChildren(children, newValue);
_.BulkUpdateDB(alteredChildren);
skip += bulkSize;
}
}
乍一看,这看起来足以测试,但是您要测试什么?再次假设AlterChildren()
已经过大量测试,剩下要做的唯一测试就是GetChildrenFromDB()的结果,该结果被模拟。再次测试模拟。这里要做的唯一一行是skip += bulkSize
。 +=
运算符在那里将测试什么?我仍然看不到重点。
那么,这是我最复杂的示例,应该对它进行单元测试吗?
答案 0 :(得分:3)
这里讨论的代码似乎不包含任何业务逻辑。我认为您的意思是:尽管它不包含业务逻辑并且相当琐碎,但是应该对此进行测试吗?
测试“机械”(与业务逻辑相对)没有错。没有理由只能测试业务逻辑。 UpdateSomething
为应用程序的其他部分提供服务。您对正确执行该服务感兴趣。
我不太清楚“保护条款”与任何其他逻辑之间的区别。行为与应用程序的功能有关。
您质疑是否要测试基于外部数据的逻辑。我也不认为这是标准。
这些事情使编写测试的可能性更大:代码易于测试;虫子的代价很高;质量对于这段代码很重要;测试不会引起额外的维护工作;该测试不需要对生产代码进行太多更改。
按照这样的具体标准行事。
更新:
关于您是否要测试我在“让我们以这种方法开始我的问题”下给出的最简单的代码示例,为什么/否,您是否可以更新您的答案或评论?
好吧,我不能这么说,因为我不知道这对您的测试有多重要。如果您有其他隐式执行此测试的测试,那么我猜我倾向于不对其进行测试。我个人并不热衷于为琐碎的事情编写测试,但是我想这是个人经验的问题。的确,我认为您在问题中提出的标准与该决定完全没有内在联系。该决定应根据我提出的标准做出。这意味着我缺乏做出决定的知识。
在我的职业生涯中,我一次又一次地发现按规则进行编程是行不通的。编程就像国际象棋一样-无限复杂。没有一套规则可以为您做出充分的决策。而是开发一个启发式和模式的思维工具箱,以指导您进行具体案例研究。最后,您必须基于整体的具体情况而不是基于严格的规则来决定。这就是为什么我说“这些事情使测试更有可能”,而不是“您应该在……时进行测试”。
这就是为什么诸如“测试吸气剂和设置器”或“不做不测试吸气剂和设置器”之类的规则都是错误的!有时您会测试它们,有时却没有测试。
答案 1 :(得分:0)
您的代码是计算和交互的组合。此类测试的代码对单元测试的价值常常令人怀疑a)由于要进行模拟,b)由于单元测试对实现细节的依赖性。一种经常有用的方法是以一种将计算和交互分离为不同功能/方法的方式来修改代码。然后,使用单元测试对具有计算功能的函数进行测试,而无需或至少不需要模拟,并且使用集成测试对交互主导的函数进行测试。
在您的示例中,计算和交互之间的可能分离如下所示:
public int BulkUpdateChildren(int parentId, string newVal, int skip, int bulkSize) {
List<Child> children = _.GetChildrenFromDB(parentId, skip, bulkSize);
if (children.Count > 0) {
var alteredChildren = AlterChildren(children, newValue);
_.BulkUpdateDB(alteredChildren);
}
return children.Count;
}
public void UpdateChildSomethings(int parentId, string newVal, int bulkSize) {
var skip = 0;
var updated;
do {
updated = BulkUpdateChildren(parentId, newVal, skip, bulksize);
skip += updated;
} while (updated == bulksize);
}
新方法BulkUpdateChildren
包含所有与依赖项的交互-最好使用集成测试来测试。 UpdateChildSomethings
中剩下的是计算主导的,测试它只需要在SUT本身中模拟一个方法,即对BulkUpdateChildren
的调用。因此,单元测试UpdateChildSomethings
更容易,并且可以集中于以下问题:在所有可能的情况下skip
的值是否正确更新以及循环是否按预期终止。
BulkUpdateChildren
中只剩下很少的逻辑,即检查是否找到任何子级,以及返回值的“计算”。如果方法AlterChildren
和BulkUpdateDB
能够处理空列表,那么甚至不需要检查零个子代(除非出于性能原因)。当退出该检查时,该代码将几乎只包含交互:
public int BulkUpdateChildren(int parentId, string newVal, int skip, int bulkSize) {
List<Child> children = _.GetChildrenFromDB(parentId, skip, bulkSize);
var alteredChildren = AlterChildren(children, newValue);
_.BulkUpdateDB(alteredChildren);
return children.Count;
}