R

To evaluate student solutions written in R, you need to write tests using testthat.

All test files should have the test- prefix.

Basics

Let's start with a basic exercise. Your student is to implement a hello_world() function that returns the string "Hello World". For the sake of simplicity, it doesn't print anything.

Here is an example solution:

exercise.R

hello_world <- function() {
'hello world'
}

And this is an example test of the student's code:

test-exercise.R

setwd("..")
source("exercise.R", chdir = TRUE)
library(testthat)
test_that("returns hello world string", {
expect_equal(hello_world(), 'hello world')
})

Let's examine the above test code.

setwd("..")

setwd specifies a working directory. This allows us to source the files without specifying the path.

source("exercise.R", chdir = TRUE)

source causes R to accept the input from the named file, so the tests know what function to run.

library(testthat)

It is used to load a testthat package, that we use to run the tests.

test_that This is a function provided by `testthat and is used to group individual tests together. The argument is a name of the test. See the reference here

expect_equal This is a function provided by testthat compares the result of the code to an expected result. See a list of the expectations in the reference here

Out test has a test case "returns hellow world string" that asserts equality of "Hello world" string and the students' function output.

Testing Outputs

Let's change the previous example a little bit and ask students to print "Hello World" using several prints rather than returning the string.

Here is the example solution:

exercise.R

hello_world <- function() {
print("Hello", quote=FALSE)
print("World", quote=FALSE)
}

And this is an example test of the student's code:

test-exercise.R

setwd("..")
source("exercise.R", chdir = TRUE)
library(testthat)
test_that("prints hello world string", {
expected <- "[1] Hello\n[1] World"
actual <- capture_output(hello_world())
expect_equal(actual, expected)
})

There can also be an alternative test for this exercise

test-exercise.R

setwd("..")
source("exercise.R", chdir = TRUE)
library(testthat)
test_that("prints hello world string", {
expected <- c("[1] Hello", "[1] World")
actual <- capture_output_lines(hello_world())
expect_equal(actual, expected)
})

There are a few differences between this exercise and the previous one:

  • We use print() function to print the argument. We are using quote=FALSE argument to remove the quote from printting, print prints quotes the output by default.

  • To verify the printed data in the first test, we use the capture_output function that is provided by testthat package. This function captures the output and returns a single string.

  • To verify the printed data in the second test, we use a capture_output_lines function that returns a character vector with one entry for each line like this.

  • In the second test we use the c() function to form a vector.

Testing matrixes

exercise.R

make_matrix <- function() {
data <- matrix(1:4, 2, 2)
data
}

And this is an example test of the student's code:

test-exercise.R

setwd("..")
source("exercise.R", chdir = TRUE)
library(testthat)
test_that("can check a matrix", {
actual <- make_matrix()
expected <- matrix(1:4, 2, 2)
expect_is(actual, "matrix")
expect_equal(actual, expected)
})

In the exercise we create the following 2x2 matrix:

[,1] [,2]
[1,] 1 3
[2,] 2 4

And in the test we:

  • ensure that the function returned a matrix

  • ensure that the returned matrix matches the expected one

Data loading from file

exercise.R

read_data <- function() {
data <- read.csv('data.csv')
data
}

data.csv

Name, Test1, Test2, Test3
Test Student, 60, 73, 79

test-exercise.R

setwd("..")
source("exercise.R", chdir = TRUE)
library(testthat)
test_that("can load from csv file", {
actual <- read_data()
expected <- data.frame("Name" = "Test Student", "Test1" = 60, "Test2" = 73, "Test3" = 79)
expect_is(actual, 'data.frame')
expect_equal(colnames(actual), colnames(expected))
expect_equal(dim(actual), dim(expected))
expect_equal(dim(merge(actual, expected)), dim(actual))
})

Let's examine the exercise:

We are using read.csv to read the data from csv-file. It returns a data frame.

Let's examine the unfamiliar parts of the test code.

data.frame("Name" = "Test Student", "Test1" = 60, "Test2" = 73, "Test3" = 79)

Here we create a data frame to test the result of our function against.

You can not compare two data frames directly, so this test has multiple steps

expect_is(actual, 'data.frame')

We check that the result of our function is of correct type data.frame.

expect_equal(colnames(actual), colnames(expected))

We check that the column names of the result dataframe match the column names of the expected data frame.

expect_equal(dim(actual), dim(expected))

We check that the dimensions of the result dataframe match the dimensions of the expected data frame.

expect_equal(dim(merge(actual, expected)), dim(actual))

You could use expect_equal(actual, expected) for small dataframes instead, but for the large ones we'll have to merge the expected data frame with the result data frame and check that the dimensions of result is the same.

Technical specs

  • The supported version of R language is 3.6.3.

  • We use testthat 2.3.1.

  • The list of bundled packages:

    • gdata 2.18.0

    • data.table 1.12.8

    • tidyverse 1.3.0

      • blob 1.2.1

      • DBI 1.1.0

      • dplyr 0.8.4

      • focats 0.4.0

      • ggplot2 3.2.1

      • glue 1.3.1

      • haven 2.2.0

      • hms 0.5.3

      • httr 1.4.1

      • jsonlite 1.6.1

      • magrittr 1.5

      • purr 0.3.3

      • readr 1.3.1

      • readxl 1.3.1

      • rvest 0.3.5

      • stringr 1.4.0

      • tibble 2.1.3

      • tidyr 1.0.2

      • lubridate 1.7.4

Limitations

  • We do not support remotes and devtools libraries

  • We do not support install.packages

  • We do not support loading packages via http or https