0

I have the following R function within a software package, which outputs Sys.time() to the output for users to know how the algorithm is proceeding:

func = function(num1, num2){
    result = num1 + num2
    return(paste0(' ', as.character(Sys.time()), ':  with result: ', result))
}

An example of using this function is as follows:

> func(2, 2)
[1] " 2018-03-11 07:24:05:  with result: 4"
> 

I need to test this function. Normally, I would use the testthat package:

https://cran.r-project.org/web/packages/testthat/index.html

Question: How does one set the Sys.time() in order to test this function? Are there other methods available to test this?

If there was no Sys.time(), this process would be simple:

library(testthat)
expect_equal(func(2, 2), 4)
expect_equal(func(2, -2), 0)
expect_error(func('a', 'b'))
EB2127
  • 1,788
  • 3
  • 22
  • 43
  • How about rewriting the function so that it returns an object of class {whateveryouwant} that contains the result and the time and make a custom print function class {whateveryouwant}. Then when testing you can ignore the time aspect and directly get at the result. Otherwise you'll be regexing the result and that will just be a headache later. – Dason Mar 13 '18 at 16:52
  • @Dason "Otherwise you'll be regexing the result and that will just be a headache later." I'm not sure why. You could regex after the last `:` and check whether 4 exists – EB2127 Mar 13 '18 at 17:07
  • You just answered your own question. Can you not also do something like this? `expect_equal(func(2, 2), paste0(' ', as.character(Sys.time()), ': with result: ', 4))` This way you're also testing whether your function returned the time correctly – acylam Mar 13 '18 at 17:53
  • @EB2127 Then later down the road when they decide they want to change the format their tests break. Separating out the actual data from the way they get formatted is a good first step in making decent tests here. – Dason Mar 13 '18 at 18:07
  • Plus if somebody wants to actually use the output for anything other than viewing in the console it just makes a lot more sense to return the output in an easily consumable format... – Dason Mar 13 '18 at 18:09
  • @useR, in rare cases there the time of evaluation may be 1 second apart, and the tests will fail. – alan ocallaghan Mar 13 '18 at 18:35

2 Answers2

4

I think you should rewrite your function so it returns the result and the time in a list. Then create a custom print function that does the formatting. This way you can still programmatically work with the result directly without having to use regular expressions to extract the result. Here is an example.

func <- function(num1, num2){
  result <- num1 + num2
  time <- Sys.time()
  output <- list(result = result, time = time)
  class(output) <- "MyClass"
  return(output)
}

print.MyClass <- function(obj, ...){
  text <- paste0(' ', as.character(obj$time), ': with result: ', obj$result)
  print.default(text)
}

And using this...

> func(2, 2)
[1] " 2018-03-13 13:26:22: with result: 4"
> o <- func(2,2)
> o$result
[1] 4
> o$time
[1] "2018-03-13 13:26:27 CDT"
> 
> expect_equal(func(2, 2)$result, 4)
> expect_equal(func(2, 2)$result, 5) # should give the required error...
Error: func(2, 2)$result not equal to 5.
1/1 mismatches
[1] 4 - 5 == -1

Note the advantages of easier testing here. You also don't have to change the tests that the result is calculated correctly if/when you decide to change the format inside your print function.

Dason
  • 60,663
  • 9
  • 131
  • 148
1

You could use something like this.

func <- function(num1, num2){
    result = num1 + num2
    return(paste0(' ', as.character(Sys.time()), ':  with result: ', result))
}

library(testthat)

uses stringr

expect_equal(as.numeric(stringr::str_extract(func(2, 2), "[0-9]*\\.*[0-9]*$")), 4)
expect_equal(as.numeric(stringr::str_extract(func(2, -2), "[0-9]*\\.*[0-9]+$")), 0)
expect_equal(as.numeric(stringr::str_extract(func(15, 21.3), "[0-9]*\\.*[0-9]+$")), 36.3)

uses base r

expect_equal(as.numeric(regmatches(func(2, 2), regexpr("[0-9]*\\.*[0-9]*$", func(2, 2)))), 4)
expect_equal(as.numeric(regmatches(func(2, -2), regexpr("[0-9]*\\.*[0-9]*$", func(2, -2)))), 0)
expect_equal(as.numeric(regmatches(func(15, 21.3), regexpr("[0-9]*\\.*[0-9]*$",func(15, 21.3)))), 36.3)

or test the internals of function exactly, but that depends on what is exactly in your function internals.

expect_equal(func(2, 2), paste0(' ', as.character(Sys.time()), ': with result: ', 4))
expect_equal(func(2, -2), paste0(' ', as.character(Sys.time()), ': with result: ', 0))
expect_equal(func(15, 21.3), paste0(' ', as.character(Sys.time()), ': with result: ', 36.3))
phiver
  • 23,048
  • 14
  • 44
  • 56
  • 1
    I still say that rewriting the function so it just returns the result and the time (say in a list) would be a much cleaner approach. Then `expect_equal(func(2,2)$result, 4)` could give you what you want instead of having to parse all the results from the returned string. – Dason Mar 13 '18 at 18:20
  • "test the internals of function exactly, but that depends on what is exactly in your function internals" This is clever. I hadn't thought of this... – EB2127 Mar 13 '18 at 22:40