tests/testthat/test-yoda_test_linter.R

test_that("yoda_test_linter skips allowed usages", {
  expect_lint("expect_equal(x, 2)", NULL, yoda_test_linter())
  # namespace qualification doesn't matter
  expect_lint("testthat::expect_identical(x, 'a')", NULL, yoda_test_linter())
  # two variables can't be distinguished which is expected/actual (without
  #   playing quixotic games trying to parse that out from variable names)
  expect_lint("expect_equal(x, y)", NULL, yoda_test_linter())
})

test_that("yoda_test_linter blocks simple disallowed usages", {
  expect_lint(
    "expect_equal(2, x)",
    rex::rex("Tests should compare objects in the order 'actual', 'expected'"),
    yoda_test_linter()
  )
  expect_lint(
    "testthat::expect_identical('a', x)",
    rex::rex("Tests should compare objects in the order 'actual', 'expected'"),
    yoda_test_linter()
  )
  expect_lint(
    "expect_setequal(2, x)",
    rex::rex("Tests should compare objects in the order 'actual', 'expected'"),
    yoda_test_linter()
  )
  # complex literals are slightly odd
  expect_lint(
    "expect_equal(2 + 1i, x)",
    rex::rex("Tests should compare objects in the order 'actual', 'expected'"),
    yoda_test_linter()
  )
})

test_that("yoda_test_linter ignores strings in $ expressions", {
  # the "key" here shows up at the same level of the parse tree as plain "key" normally would
  expect_lint('expect_equal(x$"key", 2)', NULL, yoda_test_linter())
  expect_lint('expect_equal(x@"key", 2)', NULL, yoda_test_linter())
})

# if we only inspect the first argument & ignore context, get false positives
local({
  pipes <- pipes(exclude = c("%<>%", "%$%"))
  linter <- yoda_test_linter()
  patrick::with_parameters_test_that(
    "yoda_test_linter ignores usage in pipelines",
    expect_lint(sprintf("foo() %s expect_identical(2)", pipe), NULL, linter),
    pipe = pipes,
    .test_name = names(pipes)
  )
})

test_that("yoda_test_linter throws a special message for placeholder tests", {
  expect_lint(
    "expect_equal(1, 1)",
    rex::rex("Avoid storing placeholder tests like expect_equal(1, 1)"),
    yoda_test_linter()
  )
})

# TODO(michaelchirico): Should this be extended to RUnit tests? It seems yes,
#   but the argument names in RUnit (inherited from base all.equal()) are a bit
#   confusing, e.g. `checkEqual(target=, current=)`. From the name, one might
#   reasonably conclude 'expected' comes first, and 'actual' comes second.
# TODO(michaelchirico): What sorts of combinations of literals can be included?
#   e.g. expect_equal(c(1, 2), x) is a yoda test; is expect_equal(c(x, 1), y)?
#   clearly it's not true for general f() besides c(). What about other
#   constructors of literals? data.frame(), data.table(), tibble(), ...?
# TODO(michaelchirico): The logic could also be extended to "tests" inside regular
#   code, not just test suites, e.g. `if (2 == x)`, `while(3 <= x)`,
#   `stopifnot('a' == foo(y))`.

Try the lintr package in your browser

Any scripts or data that you put into this service are public.

lintr documentation built on Nov. 7, 2023, 5:07 p.m.