【问题标题】:mockery::mock and mockery::stub do not work propperly with quasiquotation?mockery::mock 和 mockery::stub 不能与 quasiquotation 正常工作?
【发布时间】:2022-01-14 08:36:51
【问题描述】:

我编写了一个从 aws s3-bucket 获取单个文件的导入函数。 该函数本身是一个包装器 arround aws.s3::s3read_using(),它将读取函数作为其第一个参数。

我为什么要环绕 aws.s3::s3read_using() ?因为我需要做一些特殊的错误处理,并且希望包装函数能做一些Recall() 达到极限......但那是另一回事了。

现在我已经成功构建并测试了我的包装功能,我想再做一个包装:

我想在我的包装器上迭代 n 次以将下载的文件绑定在一起。我现在很难将“reading_function”交给aws.s3::s3read_using()FUN 参数。

我可以通过简单地使用 ... 来做到这一点 - 但是! 我想向包装器的 USER 说明,他需要指定该参数。

所以我决定使用 rlangs rlang::enexpr() 来捕获参数并通过 !! 将其移交给我的第一个包装器 - 作为回报,它会再次使用 rlang::enexpr() 捕获该参数并将其移交 - 最后 -到aws.s3::s3read_using() 通过rlang::expr(aws.s3::s3read_using(FUN = !!reading_fn, object = s3_object))

效果非常好,非常流畅。我的问题是使用testthatmockery 测试该函数构造

下面是一些大体上简化的代码:

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)
})

我使用了一个workarround,但感觉不对,尽管它有效:

# 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) 的版本

【问题讨论】:

    标签: r unit-testing mockery testthat quasiquotes


    【解决方案1】:

    我认为您在这里使事情复杂化,尽管我可能没有完全理解您的最终目标。您可以直接通过参数传递函数,没有任何问题。您上面的示例代码可以很容易地简化为(保持循环以匹配您的 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

    【讨论】:

    • 非常感谢您提醒我们基本知识 :D @caldwellst - 我真的倾向于把事情复杂化 - 有时就是这么简单。现在我什至可以用 purrr::map 函数替换循环,因为我无法让 !! 运行它——还有很多东西要学!
    • 不用担心,是的,只使用map() 并删除my_iterating_function() 会更干净。如果这解决了您的问题,您可以接受答案,谢谢!
    猜你喜欢
    • 1970-01-01
    • 2014-03-21
    • 2017-04-14
    • 2021-08-22
    • 2016-10-26
    • 2014-12-24
    • 2013-05-26
    • 2019-04-21
    • 2013-03-24
    相关资源
    最近更新 更多