单元测试指南

介绍

单元测试写起来很简单,很容易调用,并且在整个软件开发过程中带来很大的好处,从早期的探索代码,到长期建立的项目的后期维护。对于尝试单元测试的人来说,单元测试往往是必不可少的。这里我们将解释如何编写单元测试,如何运行它们,以及如何将它们编织到标准的Bioconductor构建过程中。我们希望单元测试将成为软件开发的标准部分,并成为Bioconductor包的组成部分。

我们推荐运行要么testthat.从CRAN写入单元测试的包。逃跑是一个R.实施敏捷软件开发'xunit'工具(另请参阅JUnit它们都试图用各自的语言鼓励健壮有用的软件的快速发展。它的灵感也来自xUnit系列的测试包,以及许多创新的ruby测试库,如rspec.暴躁的培根黄瓜

[回到顶部]

动机

为什么要打扰单元测试?

想象一下,你需要一个函数divideBy拍摄两个参数,您可能会定义这样:

<- function(dividend, divisor) {if (divisor == 0) return(NA) dividend / divisor}

当您开发此功能时,您很可能以各种方式测试它,使用不同的参数,检查结果,直到最终满足它正确执行。但是,除非您采用某种软件测试协议,否则您的测试不太可能成为代码的组成部分。它们可能会分散在不同的文件中,或者它们可能不存在于文件中的重新运行代码,就像ad hoc命令行函数调用时,您有时会记住。

我们提出的更好的方法是使用轻巧,正式化单元测试。这只需要很少的惯例和实践:

这是一个跑步测试divideBy

test_divideby < -  function(){checkequals(divideby(4,2),2)核检查(is.na(divideby(4,0)))checkequalsnumeric(divideby(4,1.2345),3.24,宽容= 1.0e-4)}

同样的测试起诉测试

test_that(“divideby正常工作”,{heippe_equal(divideby(4,2),2)预期(is.na(divideby(4,0)))预期(divideby(4,1.2345),3.24,宽容= 1.0e-4)})

采用这些实践将花费你很少。大多数开发2021欧洲杯体育投注开户人员发现这些做法简化和缩短了开发时间。另外,他们创造了一个可执行合同- 对您的代码应该做的简明和可证实的描述。经验丰富的单位测试程序员将创建这样的测试函数来伴随他们编写的每个函数,方法和类。(但不要让这种吓到你。甚至为您的包装添加一个测试是值得的,因为下面解释了。)

2021欧洲杯体育投注开户当向开发人员推荐单元测试时,他们往往会反对,认为为现有代码创建单元测试将是一项冗长而乏味的工作,而且他们的生产率将受到影响。

然而,单元测试最好是编写的当你开发时代码,而不是在您的包裹写完之后。用一些轻量级的正式实践替换您的非正式测试,您将看到您的直接和长期的生产力增加。

考虑到软件的每个单元(每个函数、方法或类)都被设计来完成一项工作,为特定的输入返回特定的输出,或者导致某些特定的副作用。单元测试指定了这些行为,并提供了一种单一的机制——一个或多个驻留在标准目录结构中的一个或多个文件中的测试函数——以确保目标函数、方法或类完成其工作。有了这样的保证,程序员(和他们的合作者)就可以充满信心地继续在更大的程序中使用它。当出现错误,或者需要并添加新特性时,就向现有集合添加新测试。您的代码会变得越来越强大,越来越健壮,但是仍然可以很容易地自动验证。

一些支持者表明单元测试的好处进一步扩展:该代码设计本身提高。他们认为,通过其测试的函数的操作定义鼓励清洁设计,“担忧的分离”,以及边缘案件的合理处理。

最后,单元测试可以是采用零碎。向包中添加单个测试,即使只是针对一个次要特性的测试,您和您的用户都将受益。当您发现自己对几个月前编写的代码感到困惑时,可以随着bug的出现、新特性的添加而添加更多的测试。很快,单元测试将成为您的标准实践的一部分,您的包将拥有越来越完整的测试集。

[回到顶部]

决定使用哪个测试框架

runit和testthat都是强大的测试解决方案,这些解决方案都是包装开发的伟大工具,您可以选择为您的包装使用,主要归结为个人偏好。然而,这是每个人的优势和弱点的简短列表。

RUnit优势

RUnit弱点

Testthat优势

testthat缺点

[回到顶部]

逃避用法

为代码添加测试

需要三件事:

  1. 创建包含函数的文件test_dividesby.对于您要测试的每个功能,使用运行- 提供检查功能。
  2. 在其他目录中添加一些小的(和特殊的)文件。
  3. 确保这一点运行BiocGenerics包是可用的。

步骤二和步骤三在构建过程的约定

这些都是运行检查方法:

独舍(表达式,表达式-B)核核心(条件)CheckequalsNumeric(A,B,容差)

在典型的测试功能中,正如您所看到的那样test_divideby.,您可以调用您的程序的函数或方法之一,然后调用适当的运行检查函数,确保结果正确。运行报告失败,如果有的话,有足够的上下文追踪错误。

运行可以测试发生异常(错误)

Checkexception(expr,msg)

但是,测试特定例外通常方便,例如,在该功能中产生警告“异常条件”f <-函数(){警告("异常条件");1}

obs <- tryCatch(f(),警告=条件消息)checkIdentical("异常条件",obs)

采用错误= ......测试特定错误。

[回到顶部]

构建过程的约定

写入单元测试很容易,尽管必须正确设置Biocucontaint包,以便R CMD检查“myppackage”查找并运行您的测试。我们采取一些痛苦来描述如何建立,以及幕后发生的事情。(见下一节当您只想测试代码的一小部分时使用的简单技术)。

标准命令R CMD检查“myppackage”来源并运行您的所有R文件mypackage / tests /目录。历史上,有时仍然,R.软件包开发人员将2021欧洲杯体育投注开户自己的发明代码和风格放在一个或多个文件中测试目录。

运行被添加到这个已经存在的结构和练习大约2005年,并且添加可以令人困惑,从找到和执行您的测试功能的间接方式开始。(但遵循这些步骤,一切都应该很好。张贴Bioc-devel.如果你遇到任何困难。)

有两个步骤:

  1. 创建文件mypackage / tests / runtests.r有这些内容:

    BiocGenerics::: testPackage(“MyPackage”)
  2. 创建任意数量的文件MyPackage /本月/单元测试/对于您的单元测试功能。您可以在该目录中的一个文件中所有测试中的测试,也可以在多个文件中分发。所有文件必须遵循此正则表达式中指定的命名约定:

    模式= " ^ test_ . * \ \ r $”

    因此,对于我们的例子来说,一个好的选择应该是mypackage / inst / unittests / test_divideby.r或者如果是划分功能是您写的几个家庭酿造的算术函数之一,您提供的测试,可以是更具描述性的文件名(我们始终推荐的练习)mypackage / inst / unittests / test_homebrewarromet.r

[回到顶部]

在开发期间使用测试

R CMD检查“myppackage”

将运行所有测试。但是在开发类或调试方法或函数时,您可能希望一次只运行一个测试,并且当安装早期版本的包时,您将在其中进行本地探索更改。假设您遵循了上面推荐的目录结构和命名约定,那么您当前的工作目录是Inst,这是您所做的:

库(Runit)库(MyPackage)源('../ r / divideby.r')源('unittests / test_divideby.r')test_divideby()[1] true

失败的测试报告如下:

交叉式错误(分数(4,2),3):平均相对差异:0.5

[回到顶部]

摘要:最小的设置

最小BioconductorunitTest设置只需要此单线添加到MyPackage /描述文件

建议:逃避,生物根系

两个文件,mypackage / tests / runtests.r

BiocGenerics::: testPackage(“MyPackage”)

mypackage / inst / unittests / test_divideby.r

test_divideby < -  function(){checkequals(divideby(4,2),2)核检查(is.na(divideby(4,0)))checkequalsnumeric(divideby(4,1.2345),3.24,宽容= 1.0e-4)}

记住,你单元测试/ test_XXXX.R文件或文件,可以有任何名称,只要它们开始test_

[回到顶部]

睾丸用法

哈德利威克姆,Testthat的主要作者有一个全面的章节用TestThat测试在他的r包书中。还有一篇文章测试:从测试开始在r-joupgn。

为包装的TestThat基础架构设置的最简单方法是使用devtools :: fumer_testthat()

然后,您可以自动重新加载您的代码和测试并重新运行它们devtools::测试()

[回到顶部]

从运行到测试的转换

如果您有一个现有的Runit项目,您希望转换为使用TestThat,您需要在包结构中更改以下内容。

  1. devtools :: fumer_testthat()可以用来设置测试的测试结构。
  2. 测试文件存储在测试/ testthat.而不是本月/单元测试并应该从测试。理查德棉花润滑冰本包可用于以编程方式转换Runit测试以测试格式。
  3. 你需要添加建议:testthat.到你的描述文件而不是建议:逃避,生物根系

[回到顶部]

测试覆盖范围

测试覆盖范围是指由单元测试测试的包代码的百分比。具有较高覆盖范围的包具有较低的含有错误的机会。

我们的构建系统为每个软件包计算测试覆盖率(使用Covr.包)并在包登陆页面上的“测试覆盖”屏蔽中报告结果。此屏蔽中显示的值是百分比(0到100的数字)或“未知”,这可能意味着:

如果测试花费太长时间来实现完整的测试覆盖率,请参见长期测试。在实施长期测试之前,我们强烈建议在邮件列表中达到生物核磁体团队,以确保正确使用和理由。

[回到顶部]

额外资源

一些值得阅读的网络资源:

[回到顶部]