Запустите testthat test в отдельном сеансе R (как объединить результаты)

Мне нужно протестировать операции загрузки пакетов (для моего многоверсионного пакета ) и я знаю, что выгрузка пространств имен и прочего - опасная работа. Итак, я хочу запускать каждый тест в новом сеансе R. Параллельный запуск моих тестов не отвечает этому требованию, поскольку он будет повторно использовать ведомые устройства, а они испачкаются.

Так что я подумал, что поможет мне. К сожалению, я снова застрял с минимально задокументированными репортерами.

Ниже приводится минимальный пример. Помещено в файл test-mytest.R.

      test_that('test 1', {
    expect_equal(2+2, 5)
})

reporter_in <- testthat::get_reporter()

# -- 1 --

reporter_out <- callr::r(

    function(reporter) {
        
        reporter <- testthat::with_reporter(reporter, {

           testthat::test_that("test inside", {
              testthat::expect_equal('this', 'wont match')
           })
       })
    },
    args = list(reporter = reporter_in),
    show = TRUE
)

# -- 2 --
testthat::set_reporter(reporter_out)

# -- 3 --
test_that('test 2', {
    expect_equal(2+2, 8)
})

Я вызвал этот тестовый файл, используя:

      # to be able to check the outcome, work with a specific reporter
summary <- testthat::SummaryReporter$new()
testthat::test_file('./tests/testthat/test-mytest.R', reporter = summary)

Что, кажется, делает то, что я хочу, но если посмотреть на результаты ...

      > summary$end_reporter()

== Failed ===============================================================================================
-- 1. Failure (test-load_b_pick_last_true.R:5:5): test 1 ------------------------------------------------
2 + 2 (`actual`) not equal to 5 (`expected`).

  `actual`: 4
`expected`: 5

== DONE =================================================================================================

... возвращается только первый тест.

Как это работает:

  • Выполняется обычный тест.
  • Используемый репортер получен ( -- 1 --)
  • используется для вызова теста, который включает в себя тест.
  • Внутри звонка пробовал пользоваться, но практически идентично.
  • В callr::r вызов возвращает репортера (пробовал с get_reporter(), но with_reporter также возвращает репортера (невидимо))

Теперь возвращенный репортер выглядит нормально, но при установке его в качестве фактического репортера с set_reporter, похоже, что он не перезаписывает фактический репортер.

Обратите внимание, что на -- 2 --, то reporter_out содержит оба результата теста.

Вопрос

Я не совсем уверен, что от него ожидаю, но, в конце концов, я хочу, чтобы результаты были добавлены в исходный репортер (( summary или) reporter_in то есть, если это не какая-то копия).

1 ответ

Один обходной путь, который я могу придумать, состоит в том, чтобы переместить фактическое выполнение теста за пределы вызова, но собрать тестовые наборы внутри. Я думаю, что это удобно, пока вы можете поместить эти вспомогательные функции (см. подробный пример) , вы можете писать тесты с небольшими накладными расходами.

Однако он не отвечает, как работать с объектом «репортер»...

Простой пример:

      test_outcome <- callr::r(
    function() {
        # devtools::load_all()
        list(
           check1 = mypackage::sum(5,5),  # some imaginary exported functions sum and name.
           check2 = mypackage::name()
        )
    }
)
test_that('My test case', {
    expect_equal(test_outcome$check1, 10)
    expect_equal(test_outcome$check2, 'Siete')
})

Подробный пример

Обратите внимание, что из .add_testк .exp_trueявляются только определениями функций, которые в свой пакетлучше включить в ваш пакет, чтобы они были доступны при загрузке с помощью devtools::load_all(). load_allтакже по умолчанию загружает неэкспортируемые функции.

      test_outcome <- callr::r(
    function() {
        # devtools::load_all()

        # Defining helper functions
        tst <- list(desc = 'My first test', tests = list())

        .add_test <- function(type, A, B) {
            # To show at least something about what is actually tested when returning the result, we can add the actual `.exp_...` call to the test.
            call <- as.character(sys.call(-1))

            tst$tests[[length(tst$tests) + 1]] <<- list(
                type = type, a = A, b = B,
                # (I couldn't find a better way to create a nice call string)
                call = paste0(call[1], '(', paste0(collapse = ', ', call[2:length(call)]), ')'))
        }
        .exp_error <- function(expr, exp_msg) {
            err_msg <- ''
            tryCatch({expr}, error = function(err) {
                err_msg <<- err$message
            })
            .add_test('error', err_msg, exp_msg)
        }
        .exp_match <- function(expr, regex) {
            .add_test('match', expr, regex)
        }
        .exp_equal <- function(expr, ref) {
            .add_test('equal', expr, ref)
        }
        .exp_false <- function(expr) {
            .add_test('false', expr, FALSE)
        }
        .exp_true <- function(expr) {
            .add_test('true', expr, TRUE)
        }

        # Performing the tests
        .exp_match('My name is Siete', 'My name is .*')
        .exp_equal(mypackage::sum(5,5), 10)  # some imaginary exported functions sum and name.
        .exp_match(mypackage::name(), 'Siete')
    
        .exp_false('package:testthat' %in% search())

        return(tst)
    },
    show = TRUE)

# Performing the actual testthat tests:
.run_test_batch <- function(test_outcome) {
    test_that(test_outcome$desc, {
        for (test in test_outcome$tests) {

            # 'test' is a list with the fields 'type', 'a', 'b' and 'call'.
            # Where 'type' can contain 'match', 'error', 'true', 'false' or 'equal'.
            if (test$type == 'equal') {
                with(test, expect_equal(a, b, label = call))

            } else if (test$type == 'true') {
                expect_true( test$a, label = test$call)

            } else if (test$type == 'false') {
                expect_false(test$a, label = test$call)

            } else if (test$type %in% c('match', 'error')) {
                with(test, expect_match(a, b, label = call))
            }
        }
    })
}

.run_test_batch(test_outcome)

При перемещении функций в ваш пакет вам также понадобится следующая функция инициализации.

      tst <- new.env(parent = emptyenv())
tst$desc = ''
tst$tests = list()

.initialize_test <- function(desc) {
    tst$desc = desc
    tst$tests = list()
}

Это работает следующим образом:

  • Создается пустой список: tst
  • Позвонив .exp_...функции, тесты добавляются в этот список
  • Список с тестами возвращает функция в callr::r
  • Затем мы перебираем список и выполняем каждый тест
Другие вопросы по тегам