R 编程中的单元测试
单元测试基本上是测试和帮助编写健壮代码的小功能。对于健壮的代码,我们指的是在更改时不会轻易破坏的代码,可以简单地重构,可以在不破坏其余部分的情况下进行扩展,并且可以轻松测试。当涉及到动态类型的脚本语言时,单元测试非常有用,因为编译器无法帮助您显示可以使用无效参数调用函数的位置。
单元测试如何工作?
一个简单的函数做了什么,它接受一些输入 x 并返回一个输出 y。在单元测试中,我们验证函数的精确性和正确性是在调用函数时针对特定的 x 值返回 y 的预期值。通常,测试不同的 (x, y) 对。如果我们的代码有副作用,例如读取/写入文件、访问某些数据库等,那么单元测试如何工作。在这种情况下,测试的准备工作更加复杂。它可以只包含一堆模拟访问数据库的函数对象。这影响了可能需要抽象层的编程风格。在某些情况下,需要在执行测试之前生成输入文件,并在测试之后检查输出文件。单元测试背后的基本思想很简单,你编写一个脚本来自动评估你的代码片段并检查它是否符合预期的行为。现在让我们看一些示例,以便更好地理解单元测试的实际含义以及它是如何工作的。
R中的实现
在 R 编程中, testthat包帮助我们在代码中实现单元测试。要安装testthat包,只需在他的 R 控制台中运行以下代码。
if (!require(testthat)) install.packages('testthat')
测试使用 test_that()函数来创建一个测试并使用对代码单元测试的期望。期望允许我们断言函数返回的值与我们应该得到的值相匹配。
test_that("Message to be displayed",
{ expect_equal(function_f(input x), expected_output_y)
expect_equivalent(function_f(input x), expected_output_y)
expect_identical(function_f(input x), expected_output_y)
.
.
.
})
testthat包中有 20 多个期望。 expect_error(), expect_warning(), expect_message(), expect_condition() skip(), skip_if_not(), skip_if(), skip_if_not_installed(), skip_if_offline(), skip_on_cran(), skip_on_os(), skip_on_travis(), skip_on_appveyor(), skip_on_ci(), skip_on_covr(), skip_on_bioc(), skip_if_translated() expect_snapshot_output(), expect_snapshot_value(), expect_snapshot_error(), expect_snapshot_condition()Argument
Expectations
expect_lt(), expect_lte(), expect_gt(), expect_gte() Is returned value less or greater than specified value? expect_equal(), expect_identical() Is an object equal to a reference value? Does code throw an error, warning, message, or other condition? expect_invisible(), expect_visible() Does expression return visibly or invisibly? Skip a test. expect_length() Does a vector have the specified length? expect_match() Does string match a regular expression? expect_named() Does object have names? expect_setequal(), expect_mapequal() Do two vectors contain the same values? expect_output() Does code print output to the console? expect_reference() Do two names point to the same underlying object? Snapshot testing. expect_vector() Does the object have vector properties? expect_silent() Is the code silent? expect_type(), expect_s3_class(), expect_s4_class() Does the object inherit from a S3 or S4 class, or is it a base type? expect_true(), expect_false() Is the object true/false? verify_output() Verify output
示例:定义一个函数factorial ,它接受一个数值 n 并返回它的阶乘。
R
# create a recursive program that
# calculates the factorial of number n
factorial <- function(n)
{
if(n == 0)
{
return(1)
}
else
{
return(n * factorial(n - 2))
}
}
R
# import testthat package
library(testthat)
# use expect_that to create tests
expect_that
(
"Factorial of number $n",
{
expect_equal(factorial(5), 120)
expect_identical(factorial(2), 2)
expect_equal(factorial(8), 40320)
}
)
R
# create a recursive program that
# calculates the factorial of number n
factorial <- function(n)
{
if(n == 0)
{
return(1)
}
else
{
# notice we used (n-2) instead
# of (n-1) in our previous code
return(n * factorial(n - 1))
}
}
# import testthat package
library(testthat)
# use expect_that to create tests
expect_that
(
"Factorial of number $n",
{
expect_equal(factorial(5), 120)
expect_identical(factorial(2), 2)
expect_equal(factorial(8), 40320)
}
)
现在,让我们对函数阶乘执行单元测试并测试其准确性并调试我们的程序。
R
# import testthat package
library(testthat)
# use expect_that to create tests
expect_that
(
"Factorial of number $n",
{
expect_equal(factorial(5), 120)
expect_identical(factorial(2), 2)
expect_equal(factorial(8), 40320)
}
)
输出:
Error: Test failed: 'Factorial computed correctly'
* factorial(5) not equal to 120.
1/1 mismatches
[1] 15 - 120 == -105
* factorial(8) not equal to 40320.
1/1 mismatches
[1] 384 - 40320 == -39936
测试给出了错误,这意味着我们的函数没有返回预期的结果。我们故意写错了代码。在阶乘函数中,我们使用的递归方法有一个错误。让我们消除该错误并再次测试我们的函数。
R
# create a recursive program that
# calculates the factorial of number n
factorial <- function(n)
{
if(n == 0)
{
return(1)
}
else
{
# notice we used (n-2) instead
# of (n-1) in our previous code
return(n * factorial(n - 1))
}
}
# import testthat package
library(testthat)
# use expect_that to create tests
expect_that
(
"Factorial of number $n",
{
expect_equal(factorial(5), 120)
expect_identical(factorial(2), 2)
expect_equal(factorial(8), 40320)
}
)
# no error
Note: If your source code and packages are not in the same directory you have to run a line of code with function source( ) to run tests.
source("your_file_path") # This is only needed if your project is not a package
组织文件和测试非常重要。我们应该有一个名为 R 的文件夹,其中包含所有 R 代码文件,以及一个名为 tests/testthat 的文件夹,所有测试脚本都将存放在其中。从 R 控制台,您可以在一个文件中运行所有测试
test_file("./path/to/file")
和一个文件夹中的所有测试
test_dir("./path/to/folder")
上述两个函数都接受一个特殊的参数报告器,它有几个选项可供选择
- 进步 这是默认值
- 最小的 最少的报告
- 地点 显示测试运行的文件、行和列(失败或其他),
- 调试 允许您以交互方式调试失败的测试等等。
test_dir("./path/to/folder", reporter=c("minimal", "location"))
如果您想要一个没有错误、格式良好的代码,那么单元测试是必要的。