mockery::mock 和 mockery::stub 不能正常使用准引用?
mockery::mock and mockery::stub do not work properly with quasiquotation?
我编写了一个从 aws s3-bucket 获取单个文件的导入函数。
该函数本身是 aws.s3::s3read_using()
的包装器,它将读取函数作为其第一个参数。
为什么要环绕 aws.s3::s3read_using()
?因为我需要做一些特殊的错误处理并希望包装函数做一些 Recall()
达到极限......但那是另一回事了。
既然我已经成功构建并测试了我的包装函数,我想做另一个包装:
我想在我的包装器上迭代 n 次以将下载的文件绑定在一起。我现在很难将 'reading_function' 交给 aws.s3::s3read_using()
的 FUN
参数。
我可以通过简单地使用 ...
来做到这一点 - 但是!
我想向包装器的用户说明,他需要指定该参数。
所以我决定使用 rlangs rlang::enexpr()
来捕获参数并通过 !!
将其交给我的第一个包装器 - 在 return 中再次捕获该参数rlang::enexpr()
并最终通过 rlang::expr(aws.s3::s3read_using(FUN = !!reading_fn, object = s3_object))
将其移交给 aws.s3::s3read_using()
这工作非常顺利。我的问题是使用 testthat
和 mockery
测试该函数构造
这里是一些简单的代码:
my_workhorse_function <- function(fn_to_work_with, value_to_work_on) {
fn <- rlang::enexpr(fn_to_work_with)
# Some other magic happens here - error handling, condition-checking, etc...
out <- eval(rlang::expr((!!fn)(value_to_work_on)))
}
my_iterating_function <- function(fn_to_iter_with, iterate_over) {
fn <- rlang::enexpr(fn_to_iter_with)
out <- list()
for(i in seq_along(iterate_over)) {
out[[i]] <- my_workhorse_function(!!fn, iterate_over[i])
}
return(out)
}
# Works just fine
my_iterating_function(sqrt, c(9:16))
现在开始测试:
# Throws an ERROR: 'Error in `!fn`: invalid argument type'
test_that("my_iterating_function iterates length(iterate_over) times over my_workhorse_function", {
mock_1 <- mockery::mock(1, cycle = TRUE)
stub(my_iterating_function, "my_workhorse_function", mock_1)
expect_equal(my_iterating_function(sqrt, c(9:16)), list(1,1,1,1,1,1,1,1))
expect_called(mock_1, 8)
})
我使用了一个解决方法,但感觉不对,尽管它有效:
# Test passed
test_that("my_iterating_function iterates length(iterate_over) times over my_workhorse_function", {
mock_1 <- mockery::mock(1, cycle = TRUE)
stub(my_iterating_function, "my_workhorse_function",
function(fn_to_work_with, value_to_work_on) {
fn <- rlang::enexpr(fn_to_work_with)
out <- mock_1(fn, value_to_work_on)
out})
expect_equal(my_iterating_function(sqrt, c(9:16)), list(1,1,1,1,1,1,1,1))
expect_called(mock_1, 8)
})
我使用的是 R: 4.1.1
版本
我正在使用 testthat(3.1.1)
、mockery(0.4.2)
、rlang(0.4.12)
的版本
我认为您使这里的事情复杂化了,尽管我可能没有完全理解您的最终目标。您可以直接通过参数传递函数而不会出现任何问题。您上面的示例代码可以很容易地简化为(保持循环只是为了匹配您的 test_that()
调用):
library(testthat)
library(mockery)
my_workhorse_function <- function(fn_to_work_with, value_to_work_on) {
fn_to_work_with(value_to_work_on)
}
my_iterating_function <- function(fn_to_iter_with, iterate_over) {
out <- list()
for(i in seq_along(iterate_over)) {
out[[i]] <- my_workhorse_function(fn_to_iter_with, iterate_over[i])
}
return(out)
}
# Works just fine
my_iterating_function(sqrt, c(9:16))
#> [[1]]
#> [1] 3
#>
#> ...
test_that("my_iterating_function iterates length(iterate_over) times over my_workhorse_function", {
mock_1 <- mockery::mock(1, cycle = TRUE)
stub(my_iterating_function, "my_workhorse_function", mock_1)
expect_equal(my_iterating_function(sqrt, c(9:16)), list(1,1,1,1,1,1,1,1))
expect_called(mock_1, 8)
})
#> Test passed
您可以直接将 FUN
传递给所有嵌套函数。你用 enexpr()
包装的函数在你显式调用它们之前永远不会首先被评估。当用户提供表达式而不仅仅是函数时,您通常使用 enexpr
。
我编写了一个从 aws s3-bucket 获取单个文件的导入函数。
该函数本身是 aws.s3::s3read_using()
的包装器,它将读取函数作为其第一个参数。
为什么要环绕 aws.s3::s3read_using()
?因为我需要做一些特殊的错误处理并希望包装函数做一些 Recall()
达到极限......但那是另一回事了。
既然我已经成功构建并测试了我的包装函数,我想做另一个包装:
我想在我的包装器上迭代 n 次以将下载的文件绑定在一起。我现在很难将 'reading_function' 交给 aws.s3::s3read_using()
的 FUN
参数。
我可以通过简单地使用 ...
来做到这一点 - 但是!
我想向包装器的用户说明,他需要指定该参数。
所以我决定使用 rlangs rlang::enexpr()
来捕获参数并通过 !!
将其交给我的第一个包装器 - 在 return 中再次捕获该参数rlang::enexpr()
并最终通过 rlang::expr(aws.s3::s3read_using(FUN = !!reading_fn, object = s3_object))
aws.s3::s3read_using()
这工作非常顺利。我的问题是使用 testthat
和 mockery
这里是一些简单的代码:
my_workhorse_function <- function(fn_to_work_with, value_to_work_on) {
fn <- rlang::enexpr(fn_to_work_with)
# Some other magic happens here - error handling, condition-checking, etc...
out <- eval(rlang::expr((!!fn)(value_to_work_on)))
}
my_iterating_function <- function(fn_to_iter_with, iterate_over) {
fn <- rlang::enexpr(fn_to_iter_with)
out <- list()
for(i in seq_along(iterate_over)) {
out[[i]] <- my_workhorse_function(!!fn, iterate_over[i])
}
return(out)
}
# Works just fine
my_iterating_function(sqrt, c(9:16))
现在开始测试:
# Throws an ERROR: 'Error in `!fn`: invalid argument type'
test_that("my_iterating_function iterates length(iterate_over) times over my_workhorse_function", {
mock_1 <- mockery::mock(1, cycle = TRUE)
stub(my_iterating_function, "my_workhorse_function", mock_1)
expect_equal(my_iterating_function(sqrt, c(9:16)), list(1,1,1,1,1,1,1,1))
expect_called(mock_1, 8)
})
我使用了一个解决方法,但感觉不对,尽管它有效:
# Test passed
test_that("my_iterating_function iterates length(iterate_over) times over my_workhorse_function", {
mock_1 <- mockery::mock(1, cycle = TRUE)
stub(my_iterating_function, "my_workhorse_function",
function(fn_to_work_with, value_to_work_on) {
fn <- rlang::enexpr(fn_to_work_with)
out <- mock_1(fn, value_to_work_on)
out})
expect_equal(my_iterating_function(sqrt, c(9:16)), list(1,1,1,1,1,1,1,1))
expect_called(mock_1, 8)
})
我使用的是 R: 4.1.1
版本
我正在使用 testthat(3.1.1)
、mockery(0.4.2)
、rlang(0.4.12)
我认为您使这里的事情复杂化了,尽管我可能没有完全理解您的最终目标。您可以直接通过参数传递函数而不会出现任何问题。您上面的示例代码可以很容易地简化为(保持循环只是为了匹配您的 test_that()
调用):
library(testthat)
library(mockery)
my_workhorse_function <- function(fn_to_work_with, value_to_work_on) {
fn_to_work_with(value_to_work_on)
}
my_iterating_function <- function(fn_to_iter_with, iterate_over) {
out <- list()
for(i in seq_along(iterate_over)) {
out[[i]] <- my_workhorse_function(fn_to_iter_with, iterate_over[i])
}
return(out)
}
# Works just fine
my_iterating_function(sqrt, c(9:16))
#> [[1]]
#> [1] 3
#>
#> ...
test_that("my_iterating_function iterates length(iterate_over) times over my_workhorse_function", {
mock_1 <- mockery::mock(1, cycle = TRUE)
stub(my_iterating_function, "my_workhorse_function", mock_1)
expect_equal(my_iterating_function(sqrt, c(9:16)), list(1,1,1,1,1,1,1,1))
expect_called(mock_1, 8)
})
#> Test passed
您可以直接将 FUN
传递给所有嵌套函数。你用 enexpr()
包装的函数在你显式调用它们之前永远不会首先被评估。当用户提供表达式而不仅仅是函数时,您通常使用 enexpr
。