【问题标题】:Environmental problems using testthat环境问题使用 testthat
【发布时间】:2021-07-22 04:04:15
【问题描述】:

我对当前在我的单元测试中表现出来的环境有一些微妙的问题。我的基本结构是这样的

  • 我有一个主函数main,它有很多参数
  • wrapper 是一个包装函数(众多函数之一),仅与 main 的选定参数有关
  • helper 是所有包装函数都使用的中间辅助函数

我使用evalmatch.call() 在包装器和主函数之间平滑移动。 我现在的问题是,当我逐行运行测试时,我的测试工作正常,但不使用 test_that()

这是一个显示问题的 MWE。如果您手动单步执行测试中的行,则测试通过。但是,评估整个 test_that() 块时测试失败,因为无法找到其中一个参数。

library(testthat)

wrapper <- function(a, b) {
  fun_call <- as.list(match.call())
  ret <- helper(fun_call)
  return(ret)
}

helper <- function(fun_call) {
  fun_call[[1]] <- quote(main)
  fun_call <- as.call(fun_call)
  fun_eval <- eval(as.call(fun_call))
  return(fun_eval)
}

main <- function(a, b, c = 1) {
  ret <- list(a = a, b = b, c = c)
  return(ret)
}

test_that("Test", {
  a <- 1
  b <- 2
  x <- wrapper(a = a, b = b)
  y <- list(a = 1, b = 2, c = 1)
  expect_equal(x, y)
})

很有信心,我怀疑我需要修改eval 使用的默认环境(即parent.frame()),但我不知道该怎么做。

【问题讨论】:

  • 在包装器主体中定义帮助器可能会起作用。

标签: r testthat nse


【解决方案1】:

您想在父环境而不是本地函数环境中评估您的调用。将您的助手更改为

helper <- function(fun_call) {
  fun_call[[1]] <- quote(main)
  fun_call <- as.call(fun_call)
  fun_eval <- eval.parent(fun_call, n=2)
  return(fun_eval)
}

这是假设 helper 总是在 wrapper 中被调用,而 wrapper 是从其他定义参数的地方调用的。

在这种情况下尚不清楚您是否真的需要所有这些非标准评估。您也可以考虑像

这样的解决方案
wrapper <- function(a, b) {
  helper(mget(ls()))
}
helper <- function(params) {
  do.call("main", params)
}

这里wrapper 只是将它的所有参数值捆绑到一个列表中。然后您可以将参数列表传递给helperdo.call 将把该列表作为参数传递给您的main 函数。这将在您调用它时评估wrapper 的参数,您不必担心执行环境。

【讨论】:

  • 这似乎工作正常,但我不知道为什么! eval.parent() 中的默认值为n=1eval.parent(expr, n)eval(expr, parent.frame(n)) 的缩写。所以eval.parent(expr, 1)eval(expr, parent.frame(1)),但是eval 中的默认环境是parent.frame() --- 而parent.frame() 的默认环境是n=1!所以,tl;博士:eval.parent(expr) 不是和eval(expr) 一样吗? (显然不是,但我不明白为什么。)
  • 另外,我刚刚意识到,如果在测试中设置a2 &lt;- 1,然后设置x &lt;- wrapper(a = a2, b = b),就会出现问题。所以看起来这个解决方案要求输入与参数的命名方式相同?
  • @hejseb,首先,重要的是要注意,当您将参数传递给函数时,它们会在调用环境中进行评估,但是当您有默认参数值时,它们会在不同的函数环境中执行.因此,parent.frame 在传递给函数时的行为与传递给默认参数值时的行为不同。如果总是从 weapper 中调用 helper,那么您可以这样做 fun_eval &lt;- eval.parent(fun_call, n=2) 或者在传递给 helper 之前评估 wrapper 中的参数会更容易。您推迟评估是否有原因?
  • 谢谢,我在这里学到了很多东西。 wrapper 总是调用helper,所以可以安全地假设。但是,我不会说我故意延迟评估——这只是我一直在经历的事情。最初,我想要一堆包装器,它们都只会修改main 的某些参数。然后我最终使用match.call() 来避免大量硬编码的包装器,并且因为所有包装器的结构都与我将这些东西放入helper 的方式相同。
  • 我还可以补充一点,在我的情况下,wrapper 也有默认值,我需要跟踪那些已被覆盖的值。我使用formals() 实现了这一点,并将其传递给helper,所以我相信中间函数是必要的(尽管它带来了一些意想不到的并发症!)。
猜你喜欢
  • 2013-11-28
  • 2022-01-18
  • 2019-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-22
  • 2023-03-27
相关资源
最近更新 更多