在尝试将TDD与此类代码一起使用时,我经常发现在下面的代码下编写代码测试比编写该代码的单元测试要容易得多,因为我知道找到预期结果的唯一方法是应用算法本身(无论是在我的头上,在纸上还是在计算机上)。感觉不对,因为我有效地使用了测试中的代码来验证我的单元测试,而不是相反。
是否存在已知的技术来编写单元测试并在测试结果出现时应用TDD被测试的代码难于预测?
一个难以预测结果的(真实)代码示例:
一个函数
weightedTasksOnTime
假设每天完成大量工作范围(0,24)中的workPerDay
,当前时间initialTime
> 0,以及任务列表taskArray
;每个任务都有完成属性的时间time
> 0,到期日期due
和重要性值importance
;返回范围[ [0,1]表示如果每个任务以due
给出的顺序(从taskArray
开始)完成,则可以在其initialTime
日期之前完成的任务的重要性。实现此功能的算法相对简单:遍历
taskArray
中的任务。对于每个任务,将time
添加到q431 2079q。如果新时间<initialTime
,请将due
添加到累加器。时间是通过反向workPerDay来调整的。返回累加器之前,请除以任务重要性的总和以进行归一化。function weightedTasksOnTime(workPerDay, initialTime, taskArray) {
let simulatedTime = initialTime
let accumulator = 0;
for (task in taskArray) {
simulatedTime += task.time * (24 / workPerDay)
if (simulatedTime < task.due) {
accumulator += task.importance
}
}
return accumulator / totalImportance(taskArray)
}
我相信可以通过消除
importance
和归一化要求,给出:function weightedTasksOnTime(initialTime, taskArray) {
let simulatedTime = initialTime
let accumulator = 0;
for (task in taskArray) {
simulatedTime += task.time
if (simulatedTime < task.due) {
accumulator += task.importance
}
}
return accumulator
}
这个问题解决了被测代码不是现有算法的重新实现的情况。如果代码是重新实现的,则其本质上很容易预测结果,因为该算法的现有受信任实现充当自然的测试Oracle。
#1 楼
您可以用难以测试的代码测试两件事。首先,堕落案件。如果您的任务数组中没有元素,或者只有一个或两个元素,但是其中一个元素已过期,该怎么办?比实际问题更简单,但仍然可以手动计算的元素。第二是健全性检查。这些是您在不知道答案是否正确的地方进行的检查,但是您肯定会知道答案是否错误。这些是诸如时间必须向前移动,值必须在合理范围内,百分比之和必须等于100之类的东西。
是的,这不像完整测试那样好,但是令您惊讶的是,您经常花很多时间进行健全性检查和简化案例,这揭示了您完整算法的问题。
评论
认为这是非常好的建议。首先编写这类单元测试。在开发软件时,如果发现错误或错误答案-将它们添加为单元测试。当您找到绝对正确的答案时,请在某种程度上进行相同的操作。随着时间的推移建立它们,尽管开始时您不知道它们将是什么,但您(最终)将拥有非常完整的单元测试集。
– Algy Taylor
18-10-8在11:05
在某些情况下(虽然可能不是这样),另一件事可能会有所帮助,就是编写一个逆函数并测试在链接时输入和输出是否相同。
–网络公园
18-10-8在11:47
健全性检查通常通过诸如QuickCheck之类的东西来作为基于属性的测试的良好目标
– jk。
18-10-8在12:45
我建议的另一类测试是检查输出中意外变化的一些测试。您可以使用代码本身来“欺骗”这些代码以生成预期的结果,因为这些代码的目的是通过标记某些意图作为输出中性更改的内容无意中影响了算法行为,从而帮助维护人员。
–丹在火光中摆弄
18-10-8在12:58
@iFlo不确定您是否在开玩笑,但是逆逆已经存在。值得意识到的是,测试失败可能是逆函数中的问题
–lucidbrot
18-10-8在14:55
#2 楼
我曾经为难以预测的输出编写针对科学软件的测试。我们大量使用了“变形关系”。本质上,即使您不知道确切的数值输出,您也知道一些有关软件应如何工作的信息。一个可能的例子:如果减少工作量,您每天可以做的事那么您可以做的工作总量最多将保持不变,但可能会减少。因此,请为
workPerDay
的多个值运行该函数,并确保关系成立。评论
变质关系是基于属性的测试的特定示例,通常对于此类情况是有用的工具
–丹农
18-10-8在22:29
#3 楼
其他答案对于开发边缘或错误情况的测试有很好的想法。对于其他人,使用算法本身并不理想(显然),但仍然有用。它将检测算法(或它依赖的数据)是否已更改
如果更改是偶然的,您可以回滚提交。如果更改是故意的,则需要重新访问单元测试。
评论
根据记录,这类测试通常根据目的被称为“回归测试”,并且基本上是任何修改/重构的安全网。
– Pac0
18-10-13在14:16
#4 楼
对任何其他类型的代码编写单元测试的方法都相同:找到一些有代表性的测试用例,然后进行测试。
找到边缘用例,然后进行测试。
查找错误条件并进行测试。
,除非您的代码涉及随机元素或不确定性(即,在输入相同的情况下不会产生相同的输出),否则它是可单元测试的。
避免受到外界力量影响的副作用或功能。纯函数更易于测试。
评论
对于非确定性算法,您可以保存RNG的种子或使用固定序列或低差异确定性序列(例如霍尔顿序列
–万德拉
18-10-8在5:49
@PaintingInAir如果无法验证算法的输出,算法甚至会不正确吗?
–WolfgangGroiss
18-10-8在9:04
除非您的代码包含一些随机元素,否则,这里的窍门是使随机数生成器成为注入的依赖项,因此您可以将其替换为数字生成器,以提供所需的确切结果。这使您能够再次进行精确测试-将生成的数字也算作输入参数。不是确定性的(即,在输入相同的情况下不会产生相同的输出)由于单元测试应从受控情况开始,因此只有具有随机元素的情况才可以是不确定性的-然后可以注入。我在这里没有其他可能性。
–更
18-10-8在9:06
@PaintingInAir:要么。我的评论适用于快速执行或快速测试编写。如果您需要三天的时间来手动计算单个示例(假设您使用的是最快的方法而不使用代码),则需要三天的时间。相反,如果您将预期的测试结果基于实际的代码本身,则说明该测试正在损害自己。就像做if(x == x),这是毫无意义的比较。您需要两个结果(实际:来自代码;预期:来自您的外部知识)彼此独立。
–更
18-10-8在9:20
即使它不是确定性的,只要符合规格并且可以测量一致性(例如随机分布和散布),它仍然可以进行单元测试。可能只需要大量样本即可消除异常风险。
–mckenzm
18-10-9在2:46
#5 楼
由于发表评论而进行更新为了简洁起见,原始答案已被删除-您可以在编辑历史中找到它。
PaintingInAir对于上下文:作为一名企业家和学术上,我设计的大多数算法都不是我本人所要求的。问题中给出的示例是无导数优化器的一部分,该优化器可最大化任务排序的质量。在我内部描述示例功能的方式方面:“我需要一个目标功能,以最大限度地提高按时完成任务的重要性”。但是,此请求与单元测试的实现之间似乎仍然有很大差距。
首先,使用TL; DR可以避免冗长的回答:
这样想:
一位顾客进入麦当劳,要求一个以生菜,西红柿和洗手液作为浇头的汉堡。该命令已下达给厨师,后者完全按照要求制作汉堡。顾客收到这个汉堡,吃掉它,然后向厨师抱怨这不是一个美味的汉堡!
这不是厨师的错-他只是在做顾客明确要求的事情。检查所请求的订单是否确实美味不是厨师的工作。厨师简单地创建顾客订购的食物。订购他们认为美味的东西是客户的责任。
同样,质疑算法的正确性也不是开发人员的工作。他们唯一的工作就是按照要求实现算法。
单元测试是开发人员的工具。它确认汉堡与订单相符(在离开厨房之前)。它不会(也不应)试图确认订购的汉堡确实好吃。
即使您既是客户又是厨师,两者之间仍然存在有意义的区别:
我没有正确准备这顿饭,不好吃(=烹调错误)。即使您喜欢牛排,牛排也永远不会好吃。
我已经准备好餐点,但是我不喜欢(代表顾客错误)。如果您不喜欢牛排,即使您将牛排煮得很完美,也永远不会喜欢吃牛排。
这里的主要问题是您不喜欢牛排。将客户与开发人员(和分析师-分开,尽管该角色也可以由开发人员代表)分开。
您需要区分测试代码和测试业务需求。
例如,客户希望它像[this]那样工作。但是,开发人员误解了,他编写了执行该操作的代码。
因此,开发人员将编写单元测试以测试[that]是否按预期工作。如果他正确开发了该应用程序,即使该应用程序没有执行客户期望的[this],他的单元测试也会通过。
如果要测试客户的期望(业务需求) ),则需要在一个单独的步骤(以及后续步骤)中完成。
一个简单的开发工作流程向您显示何时应运行这些测试:
客户解释他们要解决的问题。
分析人员(或开发人员)将其记录在分析中。
开发人员编写执行分析所描述内容的代码。
开发人员对其进行测试代码(单元测试)以查看他是否正确地进行了分析
如果单元测试失败,则开发人员将继续进行开发。这将无限循环,直到单元测试全部通过为止。
现在有了经过测试(确认并通过)的代码库,开发人员便可以构建应用程序。
该应用程序已交付给客户。
客户现在测试所提供的应用程序是否确实解决了他寻求解决的问题(质量检查)。
您可能想知道,当客户和开发人员是同一个人时,分别进行两个测试的意义何在。由于没有从开发人员到客户的“权宜之计”,因此测试会一个接一个地运行,但是它们仍然是单独的步骤。
单元测试是一种专门的工具,可以帮助您验证您的开发阶段是否完成。
通过使用该应用程序进行质量检查。
如果要测试算法本身是否正确,则不属于开发人员的工作。这是客户的关注点,客户将使用该应用程序对其进行测试。
作为企业家和学者,您可能会在这里错过一个重要的区别,突出了不同的职责。
如果应用程序不符合客户最初的要求,那么随后对代码的更改通常是免费的;因为这是开发人员错误。开发人员犯了一个错误,必须支付纠正错误的费用。
如果应用程序执行了客户最初要求的操作,但是客户现在改变了主意(例如,您决定使用其他更好的算法),对代码库的更改由客户承担,因为客户要求的东西不同于他们现在想要的不是开发人员的错。改变主意是客户的责任(成本),因此开发人员需要花费更多的精力来开发以前未曾同意的东西。
评论
我很高兴看到有关“如果您自己提出算法”情况的详细说明,因为我认为这是最有可能出现问题的情况。尤其是在没有提供“如果A则B,否则C”示例的情况下。 (ps。我不是拒绝投票的人)
– JustinLovinger
18-10-8在8:56
@PaintingInAir:但是我无法对此进行详细说明,因为这取决于您的情况。如果您决定创建此算法,则显然可以提供特定功能。谁要求你这样做?他们如何描述他们的要求?他们是否告诉您在某些情况下需要发生什么? (此信息就是我在答案中称为“分析”的信息)。无论您收到什么解释(导致您创建算法),都可以用来测试算法是否按要求工作。简而言之,除了代码/自行创建的算法之外,都可以使用。
–更
18-10-8 9:00
@PaintingInAir:将客户,分析师和开发人员紧密地联系在一起是危险的;因为您容易跳过诸如定义问题开始之类的基本步骤。我相信这就是您在这里所做的。您似乎想测试算法的正确性,而不是测试算法是否正确实现。但这不是您的方式。可以使用单元测试来测试实现。测试算法本身就是使用(经过测试的)应用程序并对其结果进行事实检查的问题-这种实际的测试超出了代码库的范围(应该如此)。
–更
18-10-8在9:15
这个答案已经是巨大的。强烈建议尝试找到一种方法来重新格式化原始内容,以便在不想将其丢弃时可以将其集成到新答案中。
– jpmc26
18-10-8在20:06
另外,我不同意你的前提。测试可以并且绝对应该揭示代码何时根据规范生成了错误的输出。对于测试而言,验证某些已知测试用例的输出是有效的。另外,厨师应该比接受“洗手液”作为有效的汉堡成分更了解,而且雇主几乎可以肯定已经对厨师进行了有关可用成分的教育。
– jpmc26
18-10-8在20:10
#6 楼
属性测试有时候,“性能测试”比传统的基于示例的单元测试更好地服务于数学函数。例如,假设您正在为诸如整数“乘”函数之类的单元测试编写代码。虽然函数本身看起来非常简单,但是如果它是乘法的唯一方法,那么如何在没有函数本身逻辑的情况下进行全面测试呢?您可以使用具有预期输入/输出的巨型表,但这是有限的并且容易出错。
在这种情况下,您可以测试函数的已知属性,而不用查找特定的预期结果。对于乘法,您可能知道,将负数与正数相乘应得出负数,而将两个负数相乘应得出正数,依此类推。使用随机值,然后检查所有属性是否均保留测试值是测试此类功能的好方法。通常,您需要测试多个属性,但是通常可以标识一组有限的属性,这些属性共同验证函数的正确行为,而不必知道每种情况的预期结果。
其中之一我所见过的有关属性测试的最佳介绍是F#中的这一篇。希望语法不会妨碍理解该技术的解释。
评论
我建议在示例乘法中添加一些更具体的内容,例如生成随机四重奏(a,b,c)并确认(a-b)(c-d)产生(ac-ad)-(bc-bd)。乘法运算可能会被打破,并且仍然会遵循(负数乘以负会产生正数)规则,但是分布规则会预测特定的结果。
–超级猫
18-10-11在21:47
#7 楼
编写代码然后查看结果是否“看起来正确”是很诱人的,但是,正如您正确理解的那样,这不是一个好主意。当算法困难时,您可以执行许多操作简化手动计算结果的操作。
使用Excel。设置一个电子表格,为您执行部分或全部计算。保持足够简单,以便您查看步骤。
将您的方法拆分为较小的可测试方法,每个方法都有自己的测试。当您确定较小的零件可以工作时,请使用它们手动进行下一步。
使用聚合属性进行完整性检查。例如,假设您有一个概率计算器;您可能不知道单个结果应该是什么,但是您知道它们都必须加起来等于100%。
蛮力。编写一个程序,生成所有可能的结果,并检查没有哪个结果比您的算法生成的结果更好。
评论
对于3.,请在此处考虑一些舍入错误。您的总额可能达到100,000001%或类似的接近但不精确的数字。
–更
18-10-8在6:23
我不太确定4。如果您能够为所有可能的输入组合生成最佳结果(然后将其用于测试确认),那么您就已经具备了计算最佳结果的能力,因此不会不需要您要测试的第二部分代码。届时,最好使用现有的最佳结果生成器,因为它已经被证明可以工作。 (如果尚未证明它可以工作,那么您就不能依靠它的结果来对您的测试进行事实检查)。
–更
18-10-8在6:25
@flater通常您还有其他要求以及蛮力无法满足的正确性。例如性能。
–伊万
18-10-8在10:37
@flater我不愿意使用您的排序,最短路径,象棋引擎等。但是id整天都在您的舍入错误中允许赌博
–伊万
18-10-8在10:55
@flater当您进入国王典当结束游戏时会辞职吗?仅仅因为不能将整个游戏强行强加,并不意味着个人位置就不能。仅仅因为您强行使用了正确的最短路径即可到达一个网络,并不意味着您知道所有网络中的最短路径
–伊万
18-10-8在11:09
#8 楼
其他答案也不错,因此,我将尝试弥补他们迄今为止共同欠缺的一些观点。我已经编写(并经过全面测试)软件来使用合成孔径雷达(SAR)进行图像处理)。它本质上是科学/数字的(涉及很多几何,物理和数学)。
一些技巧(用于一般科学/数字测试):
1 )使用逆。
fft
是什么?不知道。什么是[1,2,3,4,5]
?应该是ifft(fft([1,2,3,4,5]))
(或接近它,可能会出现浮点错误)。 2D情况也是如此。2)使用已知的断言。如果编写行列式函数,则可能很难说出行列式是100x100随机矩阵的含义。但是您确实知道单位矩阵的行列式为1,即使它是100x100。您还知道函数应该在不可逆矩阵上返回0(例如100x100全部为0)。
3)使用粗断言而不是精确断言。我为上述SAR处理编写了一些代码,该代码将通过生成联系点来注册两个图像,这些联系点会在图像之间创建映射,然后在它们之间进行扭曲以使其匹配。它可以在亚像素级别注册。先验地,很难说出两个图像的配准是什么样的。您如何测试呢?之类的事情:
EXPECT_TRUE(register(img1, img2).size() < min(img1.size(), img2.size()))
由于只能在重叠的部分上进行注册,因此注册的图像必须小于或等于您的最小图像,并且:
scale = 255
EXPECT_PIXEL_EQ_WITH_TOLERANCE(reg(img, img), img, .05*scale)
,因为注册到其自身的图像应靠近自身,但是由于手头的算法,您可能会遇到比浮点错误更多的错误,因此只需检查每个像素是否在+像素可承受范围的5%(0-255是灰度,在图像处理中很常见)。结果至少应与输入大小相同。
您甚至可以只进行烟雾测试(即调用它并确保它不会崩溃)。通常,对于无法(轻松)计算运行测试的先验结果的大型测试,此技术更好。
4)为RNG使用或存储随机数种子。
运行确实需要可重现。但是,错误的是,获得可重复运行的唯一方法是为随机数生成器提供特定的种子。有时随机性测试很有价值。我已经看到/听说过科学代码中的错误会在随机生成的退化案例中出现(在复杂算法中,甚至很难看到退化案例是什么)。不必总是使用相同的种子来调用函数,而是生成一个随机种子,然后使用该种子并记录种子的值。这样,每次运行都有一个不同的随机种子,但是如果发生崩溃,则可以使用已记录的调试种子重新运行结果。我实际上已经在实践中使用了它,它消除了一个错误,所以我想我会提到它。诚然,这只发生过一次,我很肯定这并不总是值得做,所以请谨慎使用此技术。但是,使用相同的种子随机是永远安全的。缺点(而不是一直使用相同的种子):您必须记录测试运行。好处:正确性和错误提示。
您的特殊情况
[1,2,3,4,5]返回0(已知断言)。
2)生成随机输入,以使所有
taskArray
都具有task.time > 0
,task.due > 0
和task.importance > 0
,并断言结果大于task
(粗断言,随机输入)。您无需发疯并生成随机种子,您的算法还不够复杂,无法保证它。 3)测试是否有回报的可能性很简单:
3)测试所有
0
是否为task.importance == 0
,则结果为task
(已知断言)4)其他答案也涉及到这一点,但这对您的特定案例可能很重要:如果您要让团队以外的用户使用API,则需要测试退化的案例。例如,如果
0
,请确保您抛出一个可爱的错误,告诉用户无效输入。如果您不是要使用API,而只是为了您和您的团队,则可以跳过此步骤,而拒绝使用简写的情况进行调用。HTH。
#9 楼
TL; DR转到“比较测试”部分,以获取其他答案中没有的建议。
开始
首先测试应该被算法拒绝的案例(零或负的
workPerDay
(例如)和微不足道的情况(例如,空的tasks
数组)。之后,您要首先测试最简单的情况。对于
tasks
输入,我们需要测试不同的长度。测试0、1和2个元素(此测试中的2个属于“许多”类别)应该足够了。如果可以找到可以进行心理计算的输入,那么这是一个很好的开始。我有时使用的一种技术是从期望的结果开始,然后(在规范中)返回到应该产生该结果的输入。
比较测试
有时输出与输入的关系不是很明显,但是当一个输入更改时,您在不同输出之间具有可预测的关系。如果我正确理解了示例,那么添加任务(不更改其他输入)将永远不会增加按时完成的工作的比例,因此我们可以创建一个调用该函数两次的测试-一次执行一次,一次执行不执行额外任务-并断言两个结果之间的不等式。
Fallbacks
有时我不得不求助于较长的注释,以符合规范的步骤显示手动计算的结果(这样的注释通常比测试用例)。最坏的情况是当您必须与另一种语言或不同环境的早期实现保持兼容性时。有时,您只需要使用
/* derived from v2.6 implementation on ARM system */
之类的标签来标记测试数据。这不是很令人满意,但是可以作为移植时的保真度测试或短期的拐杖。提醒
测试的最重要属性是它的可读性-如果输入和输出对于阅读器是不透明的,则该测试的价值非常低,但是如果帮助阅读器理解它们之间的关系,则该测试有两个目的。
不要忘记对不精确的结果(例如浮点数)使用适当的“近似等于”。
避免过度测试-仅在覆盖某些内容(例如边界值)时才添加测试)是其他测试无法达到的。
#10 楼
这种难以测试的功能没有什么特别之处。这同样适用于使用外部接口的代码(例如,第三方应用程序的REST API,它不受您的控制,并且当然不能由您的测试套件进行测试;或者使用第三方库,您不确定该第三方应用程序返回值的精确字节格式)。这是一种非常有效的方法,可以简单地对一些合理的输入运行算法,查看其作用,确保结果正确,然后封装输入和结果作为测试用例。您可以在少数情况下执行此操作,从而获得多个示例。尝试使输入参数尽可能不同。在进行外部API调用的情况下,您将对实际系统进行几次调用,使用某种工具对其进行跟踪,然后将其模拟到单元测试中以查看您的程序如何做出反应-就像只选择一些运行您的任务计划代码,手动验证它们,然后在测试中对结果进行硬编码。
然后,很明显,出现了一些空的情况,例如(在您的示例中)空任务列表;
您的测试套件可能不如可以轻松预测结果的方法那么出色;但仍比没有测试套件(或只是烟雾测试)好100%。
但是,如果您的问题是难以确定结果是否正确,那将是完全不同的问题。例如,假设您有一个检测任意大数是否为质数的方法。您几乎不能将任何随机数扔给它,然后只要看一下结果是否正确即可(假设您无法确定头脑中或纸上的素数)。在这种情况下,您实际上无能为力-您需要获取已知结果(即,一些大的素数),或使用不同的算法(甚至可能是不同的团队)来实现功能-NASA似乎很喜欢),并希望如果任何一种实现都是错误的,至少该错误不会导致相同的错误结果。
如果这对您来说是正常情况,那么您必须有一个很好的硬谈与您的需求工程师。如果他们不能以容易的方式(或完全可能)来检查您的需求,那么您何时知道您是否完成了?
#11 楼
将断言测试合并到单元测试套件中,以对算法进行基于属性的测试。除了编写检查特定输出的单元测试之外,还编写旨在通过触发主代码中的断言失败而失败的测试。许多算法的正确性证明都依赖于在整个测试阶段中保持某些属性。算法。如果您可以通过查看函数的输出来明智地检查这些属性,则仅进行单元测试就足以测试您的属性。否则,基于断言的测试可让您在每次算法假定算法时就测试实现是否维护属性。
基于断言的测试将暴露算法缺陷,编码错误以及由于诸如此类的问题而导致的实现失败。数值不稳定。许多语言都具有在编译时或在解释代码之前剥离断言的机制,以便在生产模式下运行时,断言不会导致性能下降。如果您的代码通过了单元测试,但在实际情况下失败了,则可以将断言作为调试工具重新打开。
#12 楼
这里的其他一些答案也非常好:测试基本情况,边沿和边角情况
进行健全性检查
进行比较测试
...我还要添加其他一些策略:
分解问题。
在代码外证明算法。
测试[外部已验证]算法是按设计实现的。
分解可让您确保算法的组件能够实现预期的功能。通过“良好”分解,您还可以确保将它们正确粘合在一起。很好的分解概括和简化了算法,使您可以手动预测结果(简化的通用算法),足以编写全面的测试。
分解到那种程度,以足以使您和您的同级,利益相关者和客户满意的任何方式在代码之外证明算法。然后,只需分解足以证明您的实现与设计相匹配即可。
#13 楼
这似乎是一个理想的答案,但是它有助于识别不同类型的测试。如果严格的答案对实现很重要,则应在描述需求的需求中提供示例和预期答案。算法。应该对这些需求进行小组审查,如果您没有得到相同的结果,则需要确定原因。
即使您同时扮演分析师和实施者的角色,您也应该实际创建需求并在编写单元测试之前就对它们进行了审查,因此在这种情况下,您将知道预期的结果并可以相应地编写测试。
另一方面,如果这是您要实现的一部分要么不是业务逻辑的一部分,要么不支持业务逻辑答案,那么可以运行测试以查看结果是什么,然后修改测试以期望得到那些结果,应该没问题。最终结果已经根据您的要求进行了检查,因此如果它们是正确的,那么提供这些最终结果的所有代码都必须在数字上正确,并且在这一点上,单元测试更多的是检测边缘故障情况和将来的重构更改,而不是证明给定的算法产生正确的结果。
#14 楼
我认为有时遵循以下过程是完全可以接受的:设计测试用例
使用您的软件获得答案
用手检查答案
/>编写回归测试,以便将来的软件版本会继续提供此答案。
在任何情况下,手动检查答案的正确性比计算答案都容易,这是一种合理的方法
我认识一些人,他们编写用于渲染打印页面的软件,并进行测试以检查在打印页面上设置的像素是否正确。唯一明智的方法是编写代码以呈现页面,肉眼检查其外观是否良好,然后将结果捕获为回归测试以供将来发行。
因为您阅读了在书中,一种特殊的方法鼓励首先编写测试用例,并不意味着您总是必须那样做。规则有待打破。
#15 楼
其他答案的答案已经具有在无法在测试功能之外确定特定结果时测试外观的技术。我在其他答案中未发现的额外操作是自动生成以某种方式进行测试:
“随机”输入
跨数据范围的迭代
从边界集构造测试用例
以上所有。
例如,如果函数采用三个参数,每个参数的允许输入范围为[-1,1],请测试每个参数的所有组合{-2,-1.01,-1,-0.99,- 0.5,-0.01,0,0.01,0.5,0.99,1,1.01,2,(-1,1)中有些随机数}
总之:有时质量差可以通过数量补贴。
评论
您能否提供一个函数的简单示例,其结果难以预测?FWIW您尚未测试算法。大概是正确的。您正在测试实施。并行构造通常可以很好地解决。
当需要实现来提供示例时,如何编写单元测试的可能重复?
在某些情况下,无法对算法进行合理的单元测试-例如,如果算法的执行时间为数天/月。解决NP问题时可能会发生这种情况。在这种情况下,提供正式证明代码正确的方法可能更为可行。
我在非常棘手的数字代码中看到的东西是仅将单元测试视为回归测试。编写函数,针对几个有趣的值运行该函数,手动验证结果,然后编写单元测试以捕获预期结果的回归。编码恐怖?好奇别人的想法。