单元测试指南

简介

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

我们推荐RUnittestthat包从CRAN编写单元测试。RUnit是一个R实施敏捷软件开发“XUnit”工具(参见JUnitPyUnit),它们都试图用各自的语言鼓励快速开发健壮有用的软件。它还从xUnit测试包家族以及许多创新的ruby测试库中获得灵感,比如rspec暴躁的培根而且黄瓜

回到顶部

动机

为什么要进行单元测试呢?

假设你需要一个函数divideBy取两个参数,你可以这样定义:

divideBy <- function(股利,除数){if(股利== 0)return(NA)股利/除数}

在开发这个函数的过程中,您很可能会以各种方式对它进行测试,使用不同的参数,检查结果,直到最终您对它的正常执行感到满意。但是,除非您采用某种软件测试协议,否则您的测试不太可能成为代码的组成部分。它们可能分散在不同的文件中,或者它们可能根本不作为文件中可重新运行的代码存在,就像您有时记得执行的临时命令行函数调用一样。

我们建议,一个更好的方法是使用轻量级的,正式的单元测试。这只需要很少的约定和实践:

下面是一个RUnit测试divideBy

test_divideBy <- function() {checkEquals(divideBy(4,2), 2) checkTrue(是。na(divideBy(4, 0))) checkEqualsNumeric(divideBy(4, 1.2345), 3.24,公差=1.0e-4)}

而等效的测试是:

test_that("divideBy工作正常",{expect_equal(divideBy(4,2), 2) expect_true(是。na(divideBy(4, 0)) expect_equal(divideBy(4, 1.2345), 3.24,公差= 1.0e-4)})

采用这些实践的成本非常低。大多数开发bob电竞体育官网人员发现这些实践简化并缩短了开发时间。此外,他们还创建了一个可执行的合同-对你的代码应该做什么的简洁且可验证的描述。有经验的单元测试程序员将创建这样一个测试函数来伴随他们编写的每个函数、方法和类。(但不要让这吓到你。即使在包中添加一个测试也是值得的,原因如下。)

bob电竞体育官网当向开发人员推荐单元测试时,开发人员通常会反对,因为他们认为为现有代码创建单元测试将是一项冗长而乏味的工作,并且他们的生产力将受到影响。

但是,最好编写单元测试随着你的发展代码,而不是在包编写之后。用一些轻量级的正式实践替换您的非正式测试,您将看到您的即时和长期生产力的提高。

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

一些支持者认为单元测试的好处还可以延伸:代码设计本身可以得到改进。他们认为,通过测试对函数进行可操作的定义可以鼓励简洁的设计、“关注点分离”以及对边缘情况的合理处理。

最后,单元测试可以采用零碎的.在您的包中添加一个测试,即使只是一个小功能的测试,您和您的用户都将受益。当您发现自己对几个月前编写的代码感到困惑时,在出现bug时,在添加新功能时,在继续进行时添加更多的测试。很快,单元测试将成为标准实践的一部分,您的包将拥有越来越完整的测试集。

回到顶部

决定使用哪个测试框架

RUnit和test都是健壮的测试解决方案,是包开发的好工具,您选择使用哪个包很大程度上取决于个人喜好。然而,这里有一个简短的优点和缺点的列表。

RUnit优势

RUnit弱点

Testthat优势

Testthat弱点

回到顶部

RUnit用法

为代码添加测试

有三件事是必须的:

  1. 创建一个包含样式的函数的文件test_dividesBy对于要测试的每个函数,使用RUnit-提供的检查函数。
  2. 在其他目录中添加一些小的(特殊的)文件。
  3. 确保RUnit而且BiocGenerics软件包是可用的。

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

这些是RUnit检查方法:

checkEquals(表达式- a,表达式- b) checkTrue(条件)checkEqualsNumeric(a, b,公差)

在一个典型的测试函数中,你可以看到test_divideBy,调用程序的函数或方法之一,然后调用适当的函数或方法RUnit检查函数以确保结果正确。RUnit报告失败(如果有),并提供足够的上下文来跟踪错误。

RUnit是否可以测试异常(错误)发生

checkException (expr、味精)

但是测试特定的异常通常是很方便的,例如,在函数中生成一个警告“异常条件”F <- function(){警告(“异常情况”);1}

obs <- tryCatch(f(), warning=conditionMessage) checkIdentical("异常情况",obs)

使用错误=…测试特定的错误。

回到顶部

构建过程的约定

编写单元测试很容易,但必须正确设置Bioconductor包以便R CMD检查MyPackage找到并运行测试。我们煞费苦心地描述了事情应该如何设置,以及幕后发生了什么。(见下一节当您只想测试代码的一小部分时,可以使用简单的技术)。

标准命令R CMD检查MyPackage源代码并运行在您的文件中找到的所有R文件MyPackage /测试/目录中。从历史上看,有时仍然如此,R包开发人员将他们bob电竞体育官网自己发明和风格的测试代码放入其中的一个或多个文件中测试目录中。

RUnit大约在2005年被添加到这个已经存在的结构和实践中,这些添加可能会令人困惑,首先是发现和执行测试函数的间接方式。(但是遵循这些步骤,一切都应该很好。职位bioc-devel如果你遇到任何困难。)

有两个步骤:

  1. 创建文件MyPackage /测试/ runTests。R内容如下:

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

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

    因此,对于我们的例子,一个好的选择是MyPackage /本月/单元测试/ test_divideBy。R或者如果dividesBy函数是您编写的几个自定义算术函数之一,并且您为其提供了测试,可能需要一个更具描述性的文件名(我们总是推荐这种做法)MyPackage /本月/单元测试/ test_homeBrewArithmetic。R

回到顶部

在开发过程中使用测试

R CMD检查MyPackage

会给你做所有的测试但是,在开发类或调试方法或函数时,您可能希望一次只运行一个测试,并且在安装包的早期版本时这样做,您正在针对该版本进行本地探索性更改。假设你已经遵循了上面推荐的目录结构和命名约定,你当前的工作目录是inst,下面是你要做的:

library(RUnit) library(MyPackage) source('../R/ dividebby .R') source('unitTests/ test_dividebby .R') test_divideBy() [1] TRUE

失败的测试报告如下所示:

checkEquals(divideBy(4,2), 3)中的错误:平均相对差:0.5

回到顶部

总结:最小的设置

最小的生物导体unitTest设置只需要在MyPackage /描述文件

建议:RUnit, BiocGenerics

还有两个文件,MyPackage /测试/ runTests。R

BiocGenerics::: testPackage(“MyPackage”)

而且MyPackage /本月/单元测试/ test_divideBy。R

test_divideBy <- function() {checkEquals(divideBy(4,2), 2) checkTrue(是。na(divideBy(4, 0))) checkEqualsNumeric(divideBy(4, 1.2345), 3.24,公差=1.0e-4)}

记住单元测试/ test_XXXX。R文件(或多个文件)可以有任何名称,只要它们以test_

回到顶部

Testthat用法

哈德利·维克汉姆,测试的主要作者,有一个全面的章节使用testthat进行测试在他的R包书中。还有一篇文章testthat:从测试开始在R-Journal上。

设置包的基础设施正在使用的测试的最简单方法devtools: use_testthat ()

然后,您可以自动重新加载代码和测试,并使用devtools::测试()

回到顶部

从RUnit到测试的转换

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

  1. devtools: use_testthat ()可以用来设置测试结构的测试。
  2. 测试文件存储在测试/ testthat而不是本月/单元测试首先应该测验.理查德·棉花的runittotesthat包可用于以编程方式将RUnit测试转换为该格式的测试。
  3. 你需要加上建议:testthat到你的描述文件而不是建议:RUnit, BiocGenerics

回到顶部

测试覆盖率

测试覆盖率指单元测试所测试的包代码的百分比。具有较高覆盖率的包包含错误的几率较低。

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

如果测试花费的时间太长而无法实现完全测试覆盖,请参见长时间测试.在实施长时间测试之前,我们强烈建议通过邮件列表联系生物导体团队,以确保正确使用和合理。

回到顶部

额外的资源

一些值得一读的网页资源:

回到顶部