您如何为难以预测结果的代码编写单元测试?


124

我经常使用非常数值/数学的程序,其中函数的确切结果很难预先预测。

在尝试将TDD与此类代码一起应用时,我经常发现编写被测代码比编写该代码的单元测试要容易得多,因为我知道找到预期结果的唯一方法是应用算法本身(无论头,纸上或通过计算机)。感觉不对,因为我正在有效地使用被测代码来验证单元测试,而不是相反。

当难以预测被测代码的结果时,是否存在用于编写单元测试和应用TDD的已知技术?

难以预测结果的(真实)代码示例:

返回weightedTasksOnTime给定每天workPerDay在(0,24]范围内完成的工作量,当前时间initialTime> 0和任务列表的函数taskArray;每个任务的完成属性时间time> 0,到期日due和重要性值importance;返回[0,1]范围内的标准化值,表示due如果每个任务按taskArray,给出的顺序完成,则可以在其日期之前完成的任务的重要性initialTime

实现此功能的算法相对简单:对中的任务进行迭代taskArray。对于每个任务,添加timeinitialTime。如果新时间< due,则添加importance到累加器。时间是通过反向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)
}

我相信可以通过删除workPerDay和规范化要求来简化上述问题,同时保持其核心,从而得出:

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
}

这个问题解决了被测代码不是现有算法的重新实现的情况。如果代码是重新实现的,则其本质上很容易预测结果,因为该算法的现有可信实现充当自然的测试预言。


4
您能否提供一个函数的简单示例,其结果难以预测?
罗伯特·哈维

62
FWIW您没有测试算法。大概是正确的。您正在测试实施。作为并行结构,手工锻炼通常会很好。
克里斯蒂安H


7
在某些情况下,无法对算法进行合理的单元测试-例如,如果算法的执行时间为数天/月。解决NP问题时可能会发生这种情况。在这些情况下,提供正式证明代码正确的方法可能更为可行。
绿巨人

12
我在非常棘手的数字代码中看到的东西是仅将单元测试视为回归测试。编写函数,为几个有趣的值运行它,手动验证结果,然后编写单元测试以捕获预期结果的回归。编码恐怖?好奇别人的想法。
Chuu

Answers:


251

您可以用难以测试的代码测试两件事。首先,堕落案件。如果您的任务数组中没有元素,或者只有一个或两个元素,但是其中一个元素已过期,该怎么办?比实际问题更简单,但仍然可以手动计算的事情。

第二个是健全性检查。这些是您在不知道答案是否正确的地方进行的检查,但是您肯定会知道答案是否错误。这些是时间必须前进,值必须在合理范围内,百分比之和必须等于100等的事情。

是的,这不像完整测试那样好,但是您会惊讶于您经常花很多时间检查健全性检查和简化案例,这揭示了完整算法的问题。


54
认为这是非常好的建议。首先编写这类单元测试。在开发软件时,如果发现错误或错误答案-将它们添加为单元测试。当您找到绝对正确的答案时,请在某种程度上进行相同的操作。随着时间的推移建立它们,尽管开始时您不知道它们将是什么,但您(最终)将有一套非常完整的单元测试...
Algy Taylor

21
在某些情况下(虽然可能不是这样),另一件事可能会有所帮助,就是编写一个逆函数并测试在链接时输入和输出是否相同。
Cyber​​spark

7
健全性检查通常可以使用QuickCheck
jk

10
我推荐的另一类测试是检查输出中意外变化的一些测试。您可以使用代码本身来“欺骗”这些代码以生成预期的结果,因为这些代码的目的是通过标记旨在作为输出中性更改的某些内容无意中影响了算法行为,从而帮助维护人员。
丹·尼利

5
@iFlo不确定您是否在开玩笑,但是逆逆已经存在。值得意识到的是,测试失败可能是逆函数中的一个问题
lucidbrot

80

我曾经为难以预测的输出编写针对科学软件的测试。我们大量使用了“变形关系”。从本质上讲,即使您不知道确切的数字输出,也知道一些有关软件应如何工作的知识。

针对您的情况的一个可能的示例:如果您减少每天可以做的工作量,那么您可以做的工作总量最多将保持不变,但可能会减少。因此,请针对多个值运行函数,workPerDay并确保关系成立。


32
变质关系是基于属性的测试的一个特定示例,通常对于此类情况是有用的工具
Dannnno

38

其他答案对于开发边缘或错误情况的测试有很好的想法。对于其他人,使用算法本身并不理想(显然),但仍然有用。

它将检测算法(或它所依赖的数据)是否已更改

如果更改是偶然的,则可以回滚提交。如果更改是故意的,则需要重新访问单元测试。


6
根据记录,这类测试通常根据目的被称为“回归测试”,并且基本上是任何修改/重构的安全网。
Pac0

21

您为任何其他类型的代码编写单元测试的方式相同:

  1. 找到一些有代表性的测试用例,然后进行测试。
  2. 查找极端情况,然后进行测试。
  3. 查找错误条件,然后进行测试。

除非您的代码包含一些随机元素或不确定性(即在相同的输入下不会产生相同的输出),否则它是可单元测试的。

避免产生副作用或受到外力影响的功能。纯函数更易于测试。


2
对于非确定性算法,您可以保存RNG的种子,也可以使用固定序列或低差异确定性序列(例如Halton序列)
Windra

14
@PaintingInAir如果无法验证算法的输出,算法甚至不正确吗?
WolfgangGroiss

5
Unless your code involves some random element这里的技巧是使您的随机数生成器成为注入的依赖项,因此您可以将其替换为数字生成器,以提供所需的确切结果。这使您能够再次进行精确测试-将生成的数字也计为输入参数。not deterministic (i.e. it won't produce the same output given the same input)由于单元测试应从受控情况开始,因此只有具有随机元素(您可以随后注入)才可以是不确定的。我在这里没有其他可能性。
平坦

3
@PaintingInAir:要么。我的评论适用于快速执行或快速测试编写。如果您需要三天的时间来手动计算一个示例(假设您使用的是不使用代码的最快方法),则需要三天的时间。相反,如果您将预期的测试结果基于实际的代码本身,则说明该测试正在损害自己。这就像在做if(x == x),这是毫无意义的比较。您需要两个结果(实际:来自代码;期望:来自您的外部知识)彼此独立。
平坦

2
即使它不是确定性的,只要符合规格并且可以测量一致性(例如随机分布和散布),它仍然可以进行单元测试。它可能只需要大量样本即可消除异常风险。
mckenzm

17

由于发表评论而更新

为了简洁起见,原始答案已删除-您可以在编辑历史记录中找到它。

PaintingInAir对于上下文:作为一名企业家和学者,我设计的大多数算法都不是我本人所要求的。问题中给出的示例是无导数优化器的一部分,该优化器可最大化任务排序的质量。关于我如何在内部描述示例函数的需要,“我需要一个目标函数来最大化按时完成任务的重要性”。但是,此请求与单元测试的实施之间似乎仍然有很大的差距。

首先,使用TL; DR可以避免冗长的回答:

这样想:
一位顾客进入麦当劳,然后要求一个汉堡,上面放生菜,西红柿和洗手液作为浇头。该命令已下达给厨师,后者完全按照要求制作汉堡。顾客收到这个汉堡,吃掉它,然后向厨师抱怨这不是一个美味的汉堡!

这不是厨师的错-他只是在做客户明确要求的事情。检查所请求的订单是否真是美味不是厨师的工作。厨师简单地创建顾客订购的食物。订购他们认为美味的东西是顾客的责任

同样,质疑算法的正确性也不是开发人员的工作。他们唯一的工作就是按照要求实施算法。
单元测试是开发人员的工具。它确认汉堡与订单相符(在离开厨房之前)。它不会(也不应)试图确认订购的汉堡确实好吃。

即使您既是客户又是厨师,两者之间仍然存在有意义的区别:

  • 我没有正确准备这顿饭,不好吃(=烹调错误)。即使您喜欢牛排,烧焦的牛排也永远不会好吃。
  • 我已经准备好了饭,但是我不喜欢它(=客户错误)。如果您不喜欢牛排,即使您将牛排煮得完美无暇,也永远不会喜欢吃牛排。

这里的主要问题是,您并没有在客户与开发人员(以及分析师之间)分离,尽管该角色也可以由开发人员代表。

您需要区分测试代码和测试业务需求。

例如,客户希望它像[this]那样工作。但是,开发人员误解了,他编写了执行[that]的代码。

因此显影剂将写单元测试测试,如果[即]按预期方式工作。如果他正确地开发了该应用程序,那么即使该应用程序没有执行客户期望的[this]他的单元测试也会通过

如果要测试客户的期望(业务需求),则需要在一个单独的步骤(以及以后的步骤)中完成。

一个简单的开发工作流程向您显示何时应运行这些测试:

  • 客户说明他们要解决的问题。
  • 分析人员(或开发人员)在分析中将其写下来。
  • 开发人员编写执行分析所描述的代码。
  • 开发人员测试其代码(单元测试)以查看他是否正确地遵循了分析
  • 如果单元测试失败,则开发人员将返回到开发阶段。这将无限循环,直到单元测试全部通过为止。
  • 现在已经有了经过测试(确认并通过)的代码库,开发人员可以构建应用程序。
  • 该应用程序已提供给客户。
  • 现在,客户测试给定的应用程序是否确实解决了他试图解决的问题(QA测试)

您可能想知道,当客户和开发人员是同一个人时,分别进行两个测试的意义何在。由于没有从开发人员到客户的“交接”,因此测试是一个接一个地运行的,但是它们仍然是独立的步骤。

  • 单元测试是一种专用工具,可帮助您验证开发阶段是否完成。
  • 使用该应用程序可以完成质量检查。

如果您想测试算法本身是否正确,那不属于开发人员的工作。这是客户的关注点,客户将使用该应用程序对其进行测试。

作为企业家和学者,您可能会在这里错过一个重要的区别,这突出了不同的职责。

  • 如果应用程序不遵循客户最初的要求,那么随后对代码的更改通常是免费的;因为这是开发人员错误。开发人员犯了一个错误,必须支付纠正错误的费用。
  • 如果应用程序执行了客户最初的要求,但是客户现在改变了主意(例如,您决定使用其他更好的算法),则代码库的更改将由客户承担,因为这不是开发人员的错,即客户要求的东西不同于他们现在想要的东西。改变主意是客户的责任(成本),因此,开发人员需要花费更多的精力来开发以前未曾同意的东西。

我很高兴看到有关“如果您自己想出算法的话”情况的详细说明,因为我认为这是最有可能出现问题的情况。尤其是在没有提供“如果A则B,否则C”示例的情况下。(ps我不是拒绝投票的人)
PaintingInAir

@PaintingInAir:但是我无法对此进行详细说明,因为这取决于您的情况。如果您决定创建此算法,则显然可以提供特定功能。谁要求你这样做?他们如何描述他们的要求?他们是否告诉您在某些情况下需要发生什么?(此信息就是我在答案中称为“分析”的信息)。无论您收到什么(导致您创建算法的)解释,都可以用来测试算法是否按要求工作。简而言之,可以使用除代码/自行创建的算法以外的任何内容
平坦

2
@PaintingInAir:将客户,分析师和开发人员紧密耦合是危险的;因为您倾向于跳过一些基本步骤,例如定义问题的开始。我相信这就是您在这里所做的。您似乎想测试算法的正确性,而不是测试算法是否正确实现。但这不是您的方式。可以使用单元测试来测试实现。测试算法本身是使用(经过测试的)应用程序并对其结果进行事实检查的问题-此实际测试超出了代码库的范围(应该如此)。
平的

4
这个答案已经是巨大的。强烈建议尝试寻找一种方法来重新格式化原始内容,以便在不想将其丢弃时可以将其集成到新答案中。
jpmc26 '18 -10-8

7
另外,我不同意你的前提。测试可以并且绝对应该揭示代码何时根据规范生成了错误的输出。对于测试来说,验证某些已知测试用例的输出是有效的。同样,厨师应该比接受“洗手液”作为有效的汉堡成分更了解,而且雇主几乎可以肯定已经对厨师进行了有关可用成分的教育。
jpmc26 '18 -10-8

9

性能测试

有时,“性能测试”比传统的基于示例的单元测试更好地提供了数学功能。例如,假设您正在为诸如整数“乘”函数之类的单元测试编写代码。虽然函数本身看起来非常简单,但是如果它是乘法的唯一方法,那么如何在没有函数本身逻辑的情况下彻底测试它呢?您可以使用具有预期输入/输出的巨型表,但这是有限的并且容易出错。

在这些情况下,您可以测试函数的已知属性,而不用查找特定的预期结果。对于乘法,您可能知道,将负数与正数相乘应得出负数,而将两个负数相乘应得出正数,依此类推。使用随机值,然后检查所有属性是否均保留测试值是测试此类功能的好方法。通常,您需要测试多个属性,但通常可以标识一组有限的属性,这些属性共同验证函数的正确行为,而不必知道每种情况的预期结果。

我所见过的关于属性测试的最佳介绍之一就是F#中的这一篇。希望语法不会妨碍理解该技术的解释。


1
我建议在示例乘法中添加一些更具体的内容,例如生成随机四重奏(a,b,c)并确认(ab)(cd)产生(ac-ad)-(bc-bd)。乘法运算可能会被打破,并且仍然会遵循(负数乘以负会产生正数)规则,但是分布规则会预测特定的结果。
超级猫

4

编写代码然后查看结果是否“看起来正确”是很诱人的,但是,正如您正确理解的那样,这不是一个好主意。

当算法困难时,您可以做很多事情来简化结果的手动计算。

  1. 使用Excel。设置一个电子表格,为您执行部分或全部计算。保持足够简单,以便您可以查看步骤。

  2. 将您的方法分成较小的可测试方法,每个方法都有自己的测试。当您确定较小的零件可以工作时,请使用它们手动进行下一步。

  3. 使用聚合属性进行完整性检查。例如,假设您有一个概率计算器;您可能不知道单个结果应该是什么,但是您知道它们都必须相加100%。

  4. 蛮力。编写一个程序,生成所有可能的结果,并检查没有哪个结果比您的算法生成的结果更好。


对于3.,请在此处考虑一些舍入错误。您的总额可能达到100,000001%或类似的接近但不精确的数字。
平坦

2
我不太确定4。如果您能够为所有可能的输入组合生成最佳结果(然后将其用于测试确认),那么您就已经具有计算最佳结果的固有能力,因此不会不需要您要测试的第二部分代码。届时,最好使用现有的最佳结果生成器,因为它已经被证明可以工作。(如果尚未证明它可以工作,那么您就不能依靠它的结果来对您的测试进行事实检查)。
平的

6
@flater通常您还有其他要求以及蛮力无法满足的正确性。例如性能。
伊万

1
@flater我不愿意使用您的排序,最短路径,象棋引擎等。但是id会整日在您的舍入错误中允许赌场全天赌博
Ewan

3
@flater当您进入国王典当结束游戏时会辞职吗?仅仅因为不能将整个游戏强行强行使用,并不意味着个人位置就不能做到。仅仅因为您用蛮力正确地建立了通往一个网络的最短路径,并不意味着您知道所有网络中的最短路径
Ewan

2

TL; DR

转到“比较测试”部分,获取其他答案中没有的建议。


开端

首先测试应该被算法拒绝workPerDay的案例(例如零或负数)和琐碎的案例(例如空tasks数组)。

之后,您要首先测试最简单的情况。对于tasks输入,我们需要测试不同的长度。测试0、1和2个元素(此测试中2个属于“许多”类别)应该足够了。

如果您可以找到可以进行心理计算的输入,那就是一个很好的开始。我有时使用的一种技术是从所需的结果开始,然后再重新工作(在规范中)到应产生该结果的输入。

比较测试

有时,输出与输入之间的关系并不明显,但是当更改一个输入时,您会在不同输出之间具有可预测的关系。如果我正确理解了示例,那么添加任务(不更改其他输入)将永远不会增加按时完成的工作的比例,因此我们可以创建一个调用该函数两次的测试-一次执行一次,一次执行不执行额外任务-并断言两个结果之间的不平等。

后备

有时,我不得不求助于长长的注释,以按照规范对应的步骤显示手动计算的结果(这种注释通常比测试用例长)。最坏的情况是,您必须与另一种语言或其他环境的早期实现保持兼容性。有时,您只需要使用诸如标记标签测试数据即可/* derived from v2.6 implementation on ARM system */。这不是很令人满意,但是可以作为移植时的保真度测试或短期拐杖接受。

提醒事项

测试的最重要属性是它的可读性-如果输入和输出对于阅读器是不透明的,则该测试的价值非常低,但是如果帮助阅读器理解它们之间的关系,则该测试有两个目的。

不要忘记对不精确的结果(例如浮点数)使用适当的“近似等于”。

避免过度测试-仅在测试涵盖其他测试未达到的某些内容(例如边界值)时,才添加测试。


2

这种难以测试的功能没有什么特别之处。这同样适用于使用外部接口的代码(例如,第三方应用程序的REST API,它不受您的控制,当然也不能由您的测试套件进行测试;或者使用第三方库,您不确定该第三方应用程序返回值的确切字节格式)。

这是一种非常有效的方法,可以简单地对一些合理的输入运行算法,查看其作用,确保结果正确,然后将输入和结果封装为测试用例。您可以在少数情况下执行此操作,从而获得多个示例。尝试使输入参数尽可能不同。在进行外部API调用的情况下,您将对实际系统进行几次调用,使用某种工具对其进行跟踪,然后将其模拟到单元测试中以查看您的程序如何做出反应-就像只选择一些运行您的任务计划代码,手动验证它们,然后在测试中对结果进行硬编码。

然后,很明显,引入一些空的情况,例如(在您的示例中)空任务列表;像这样的东西。

您的测试套件可能不如可以轻松预测结果的方法那么出色。但仍比没有测试套件(或只是烟雾测试)好100%。

但是,如果您的问题是难以确定结果是否正确,那将是完全不同的问题。例如,假设您有一个检测任意大数是否为质数的方法。您几乎不能将任何随机数扔给它,然后只要看一下结果是否正确即可(假设您无法决定头脑中或纸上的素数)。在这种情况下,实际上您无能为力-您需要获取已知的结果(即,一些大的素数),或使用不同的算法(甚至可能是不同的团队)来实现功能-NASA似乎很喜欢),并希望如果任何一种实现都存在错误,至少该错误不会导致相同的错误结果。

如果这是您的常规情况,那么您必须与需求工程师进行认真的交谈。如果他们不能以容易(或根本不可能)检查您的方式提出您的要求,那么您何时知道您是否完成了?


2

其他答案也不错,因此,我将尝试谈谈他们迄今为止集体错过的一些问题。

我已经编写(并经过全面测试)软件来使用合成孔径雷达(SAR)进行图像处理。它本质上是科学/数字的(涉及很多几何,物理和数学)。

几个技巧(用于一般的科学/数字测试):

1)使用逆。什么fft[1,2,3,4,5]?不知道。什么ifft(fft([1,2,3,4,5]))啊 应该是[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)测试一个空是否 taskArray 返回0(已知断言)。

2)生成随机输入,使得 task.time > 0task.due > 0 task.importance > 0 对于所有 task S,并且置结果大于 0 (粗断言,随机输入)。您无需发疯并生成随机种子,您的算法还不够复杂,无法保证它。大约有0的机会获得回报:只需保持测试简单即可。

3)测试是否 task.importance == 0 对于所有 task s,则结果为 0 (已知断言)

4)其他答案也涉及到这一点,但这对您的特定案例可能很重要:如果您要让团队以外的用户使用API​​,则需要测试退化的案例。例如,如果workPerDay == 0,请确保抛出一个可爱的错误,告知用户无效输入。如果您不是在制作API,而是仅用于您和您的团队,则可以跳过此步骤,而只在简写的情况下拒绝调用它。

HTH。


1

将断言测试合并到单元测试套件中,以对算法进行基于属性的测试。除了编写检查特定输出的单元测试外,还要编写旨在通过触发主代码中的断言失败而失败的测试。

许多算法的正确性证明都依赖于在算法的整个阶段中保持某些属性。如果您可以通过查看函数的输出来明智地检查这些属性,则仅进行单元测试就足以测试您的属性。否则,基于断言的测试可让您在每次算法采用该属性时测试实现是否维护了一个属性。

基于断言的测试将暴露由于数字不稳定等问题而导致的算法缺陷,编码错误和实现失败。许多语言都具有在编译时或在解释代码之前剥离断言的机制,以便在生产模式下运行时,断言不会导致性能下降。如果您的代码通过了单元测试,但在实际情况下失败了,则可以将断言作为调试工具重新打开。


1

这里的其他一些答案非常好:

  • 测试基础,边缘和角落情况
  • 执行健全性检查
  • 执行比较测试

...我还要添加其他一些策略:

  • 分解问题。
  • 在代码之外证明算法。
  • 测试[外部证明]算法是否按设计实现。

分解使您可以确保算法的组件能够实现您期望的功能。通过“良好”分解,您还可以确保将它们正确粘合在一起。一个伟大的分解概括和简化算法,你的程度可以预测的手不够好,写全面的测试结果(简化,通用算法(或多个))。

如果您无法分解到这个程度,请以足以让您和您的同级,利益相关者和客户满意的任何方式在代码之外证明算法。然后,只需分解足以证明您的实现与设计匹配即可。


0

这似乎是一个理想主义的答案,但它有助于识别不同类型的测试。

如果严格的答案对实现很重要,则应在描述算法的要求中确实提供示例和预期答案。这些要求应进行小组审查,如果您没有获得相同的结果,则需要确定原因。

即使您扮演分析师和实施者的角色,您实际上也应该创建需求并在编写单元测试之前就对它们进行审查,因此在这种情况下,您将知道预期的结果并可以相应地编写测试。

另一方面,如果您要实现的部分不是业务逻辑的一部分或不支持业务逻辑答案,则可以运行测试以查看结果是什么,然后修改测试以达到预期的效果这些结果。最终结果已经根据您的要求进行了检查,因此如果它们是正确的,那么提供这些最终结果的所有代码都必须在数字上正确,并且在这一点上,单元测试更多的是检测边缘故障情况和将来的重构更改,而不是证明给定的算法产生正确的结果。


0

我认为有时遵循该过程是完全可以接受的:

  • 设计一个测试用例
  • 使用您的软件获得答案
  • 用手检查答案
  • 编写回归测试,以便该软件的未来版本将继续提供此答案。

这在任何情况下都是一种合理的方法,在这种情况下,手工检查答案的正确性要比从第一原理上手工计算答案容易。

我认识一些人,他们编写用于渲染打印页面的软件,并进行测试以检查在打印页面上是否设置了正确的像素。唯一明智的方法是编写代码以呈现页面,通过肉眼检查其外观是否良好,然后将结果捕获作为回归测试以用于将来的版本。

仅仅因为您在书中读到一种特殊的方法鼓励您首先编写测试用例,并不意味着您总是必须那样做。规则有待打破。


0

其他答案答案已经具有在无法在测试功能之外确定特定结果时测试外观的技术。

我在其他答案中没有发现的其他功能是通过某种方式自动生成测试:

  1. “随机”输入
  2. 跨数据范围迭代
  3. 从边界集构建测试用例
  4. 以上全部。

例如,如果函数采用三个参数,每个参数的允许输入范围为[-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)中还有一些随机数}

简而言之:有时可以通过数量来弥补质量差的问题。

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.