testthat ex 4: the split by sign function

The following function accepts a numeric vector, and returns a list of up to three elements. Negative inputs appear in the element named -1, zero inputs appear in the element 0, and positive inputs appear in the element 1.

split_by_sign <- function(x)
{
  split(x, sign(x))
}

Write some unit tests for this function.

# your tests here
test_that(
  "split_by_sign, with inputs -5:5, returns a list with elements -1, 0, 1",
  {
    expected <- list(`-1` = -5:-1, `0` = 0, `1` = 1:5)
    actual <- split_by_sign(-5:5)
    expect_equal(actual, expected)
  }
)

The usual tests for failures.

 test_that(
  "split_by_sign, with a missing input, throws an error",
  {
    expect_error(
      split_by_sign(),
      'argument "x" is missing, with no default'
    )
  }
)
test_that(
  "split_by_sign, a character input, throws an error",
  {
    expect_error(
      split_by_sign("x"),
      "non-numeric argument to binary operator"
    )
  }
)
## Error: Test failed: 'split_by_sign, a character input, throws an error'
## Not expected: split_by_sign("x") does not match 'non-numeric argument to binary operator'. Actual value: "Error in sign(x) : non-numeric argument to mathematical function\n".

What happens if not all the sign categories are present?

test_that(
  "split_by_sign, with inputs 1:10, returns a list with elements -1, 0, 1",
  {
    expected <- list(`-1` = numeric(), `0` = numeric(), `1` = 1:10)
    actual <- split_by_sign(1:10)
    expect_equal(actual, expected)
  }
)
## Error: Test failed: 'split_by_sign, with inputs 1:10, returns a list with elements -1, 0, 1'
## Not expected: actual not equal to expected
## Names: 1 string mismatch
## Length mismatch: comparison on first 1 components
## Component 1: Numeric: lengths (0, 10) differ.

Based upon your experience writing these tests, can you improve the function to make it easier to test and to use?

I think it would be easier if the elements (-1, 0, 1) were always present in the return value. We can enforce this by converting the return value from sign to be a factor.

# modify this function
split_by_sign2 <- function(x)
{
  split(x, factor(sign(x), levels = -1:1))
}
# now include some tests
test_that(
  "split_by_sign2, with inputs 1:10, returns a list with elements -1, 0, 1",
  {
    expected <- list(`-1` = numeric(), `0` = numeric(), `1` = 1:10)
    actual <- split_by_sign2(1:10)
    expect_equal(actual, expected)
  }
)