Testing And Packaging

Unit Testing

The best way to check whether a programmer’s code is functioning properly is by writing unit tests. The basic workflow is to write the code, write tests, and check if the code passes the tests. Let’s see how can do this with a simple example using the testthat package.

library(testthat)
setwd("/Users/ethen/Desktop/northwestern/russ/3_tests_packages")

compute_square_root <- function(number) {
    # an overly simple wrapper function around sqrt
    # for illustration purpose
    return( sqrt(number) )
}

test_that('Test sqrt: positive numeric', {
    expected <- 2
    actual <- compute_square_root(4)
    expect_equal(expected, actual)
})

Hopefully the syntax should be pretty straightforward. We use the test_that function to conduct a single unit test, and we first give a short description of what the test is about. Then inside this function we need to define two things: the result we expect, and the actual result that is returned by the function we wish to test. In the example above, we wish to test the function compute_square_root, we know that the square root of 4 is 2. Thus we would use expect_equal to say the result of the compute_square_root(4) should equal to 2.

There’re a bunch of different functions apart from expect_equal that allow us to test for a variety of situations. For example: When we like to test whether taking the square root of a negative value will generate a warning.

# outputs a warning
compute_square_root(-4)
## Warning in sqrt(number): NaNs produced
## [1] NaN
# test that the function will actually generate a warning
test_that('Test sqrt: negative numeric', {
    expect_warning(compute_square_root(-4))
})

For more options, please refer to the documentation of testthat.

Packages

Developing a package makes it easier for us to our code with colleagues or even simply prevent us from copying and pasting code that we wish to use across multiple projects. To start writing a package, the easiest way is to load up Rstudio and start a new project. We can do that by going under the File menu and choose New Directory > R package and to keep it simple the package name will simply be myFirstPackage. After doing so, we’ll see a bunch new files and folders created inside our package folder. Note that our working directory is now set to where the package sits.

The most important folder for now is the R folder, which is the folder that will hold our .R source code files. Apart from that, Rstudio will also open up a sample hello.R script, which we can delete. Here we’ll simply be editing it.

Documentations

Usually, we would write comments to describe what our function does, what are its inputs and outputs. roxygen2 is a package that turns these comments into documentation. For example:

#' Function to compute the square root of a number
#'
#' @description This function is a wrapper around the sqrt function that
#' computes the square root of a number using a loop
#'
#' @param num the number whose square root is computed
#' 
#' @return square root of the input number
#'
#' @details The function only takes in positive values
#'
#' @references None
#'
#' @examples
#' library(myFirstPackage)
#' num <- 4
#' compute_square_root(num)
#' @export
compute_square_root <- function(number) {
    return( sqrt(number) )
}

The first difference with standard comments is that roxygen2 type comments start with the #' symbol instead of simply the # symbol. The fist line is a short description of what the function does. Then, we can supply different keywords to it. Common ones include:

  • @description for a longer description of what the function does
  • @param that explains what each and every parameter represents
  • @return explains the value that the function returns
  • @details for more elaboration on the functionality of the function
  • @references if we have any
  • @examples for a concrete example of how to use the function
  • @export for making the function accessible to the user. Sometimes it makes sense to not use @export if we want to have helper functions that are used by our other functions inside the package, and we don’t want to make these functions accessible to the users.

After writing out all of these text before our function, we can then use the roxygenise() function from the roxygen2 package to create the documentation files for our package.

roxygen2::roxygenise()

We can now test building our package by either by clicking on the button named Build and Reload button which we can find inside the Build pane or by using the following keyboard shortcut: CTRL-SHIFT-B in Rstudio. After doing so, we can now load the library use the function inside and search for the help page of the function.

library(myFirstPackage)
compute_square_root(4)
## [1] 2
# search for the documentation
?compute_square_root

Testing Packages

Now that we know the basics of creating a package, let’s apply what we know about unit testing and start testing out package. We’ll simply call devtools::use_testthat() which will create the necessary folders to hold our tests as well as creating a testthat.R file with the code that gets called to run our tests. To elaborate on the necessary folders that the function created, what it’s doing is creating a folder called tests in the root of our package and inside this tests folder create another folder, called testthat. The testthat folder will hold our unit tests. For example, inside the folder, we create a script called test_square_root.R and put the following code in it:

library(testthat)
library(myFirstPackage)

test_that('Test sqrt: positive numeric', {
    expected <- 2
    actual <- compute_square_root(4)
    expect_equal(expected, actual)
})

test_that('Test sqrt: negative numeric', {
    expect_warning(compute_square_root(-4))
})

Now we can run our test by either going under the Build pane in Rstudio and click on Test Package or by simply clicking the keyboard shortcut CTRL-SHIFT-T. After doing so, we should see the following output:

output

output

Notice the two . above DONE. This means that two unit tests passed. If a unit test does not pass, then the . will be replaced by a number 1 and it will also output the test cases that failed and its corresponding message.

Checking Coverage of Unit Tests

To check if our tests has covered all the functions in our package, we can run the following code:

library(covr)
cov <- package_coverage()
shine(cov)

The line shine(cov) launches an interactive app inside our viewer pane:

coverage

coverage

In our case, the coverage shows 100 percent since we did fully tested our function compute_square_root function inside the hello.R script. If we did not fully tested all our functions, then we can click in each of the files to see which line/part of the code did we not test.

Github

Now that we created the skeleton of the package, we can share it by uploading it to Github. After doing so any R user can install your package with just executing the following command in R:

devtools::install_github("username/packagename")

# e.g.
devtools::install_github("ethen8181/myFirstPackage")

For a more detailed introduction please refer to R packages, especially the section about package’s metadata if the goal is to share the package with other users.

R Session Information

devtools::session_info()
## Session info --------------------------------------------------------------
##  setting  value                       
##  version  R version 3.2.4 (2016-03-10)
##  system   x86_64, darwin13.4.0        
##  ui       X11                         
##  language (EN)                        
##  collate  en_US.UTF-8                 
##  tz       America/Chicago             
##  date     2017-01-13
## Packages ------------------------------------------------------------------
##  package        * version date       source        
##  assertthat       0.1     2013-12-06 CRAN (R 3.2.0)
##  bookdown         0.1     2016-07-13 CRAN (R 3.2.5)
##  crayon           1.3.2   2016-06-28 CRAN (R 3.2.5)
##  devtools         1.12.0  2016-06-24 CRAN (R 3.2.5)
##  digest           0.6.9   2016-01-08 CRAN (R 3.2.3)
##  evaluate         0.9     2016-04-29 cran (@0.9)   
##  formatR          1.4     2016-05-09 cran (@1.4)   
##  highr            0.6     2016-05-09 cran (@0.6)   
##  htmltools        0.3.5   2016-03-21 CRAN (R 3.2.4)
##  httpuv           1.3.3   2015-08-04 CRAN (R 3.2.0)
##  knitr            1.14    2016-08-13 CRAN (R 3.2.4)
##  magrittr         1.5     2014-11-22 CRAN (R 3.2.0)
##  memoise          1.0.0   2016-01-29 CRAN (R 3.2.3)
##  mime             0.4     2015-09-03 CRAN (R 3.2.0)
##  miniUI           0.1.1   2016-01-15 CRAN (R 3.2.3)
##  myFirstPackage * 0.1.0   2017-01-12 local         
##  questionr        0.5     2016-03-15 CRAN (R 3.2.4)
##  R6               2.1.2   2016-01-26 CRAN (R 3.2.3)
##  Rcpp             0.12.5  2016-05-14 cran (@0.12.5)
##  rmarkdown        1.1     2016-10-16 CRAN (R 3.2.4)
##  rmdformats       0.3     2016-09-05 CRAN (R 3.2.5)
##  rstudioapi       0.6     2016-06-27 CRAN (R 3.2.5)
##  shiny            0.14.2  2016-11-01 CRAN (R 3.2.5)
##  stringi          1.0-1   2015-10-22 CRAN (R 3.2.0)
##  stringr          1.0.0   2015-04-30 CRAN (R 3.2.0)
##  testthat       * 1.0.2   2016-04-23 CRAN (R 3.2.5)
##  tibble           1.2     2016-08-26 CRAN (R 3.2.5)
##  withr            1.0.2   2016-06-20 CRAN (R 3.2.5)
##  xtable           1.8-2   2016-02-05 CRAN (R 3.2.3)
##  yaml             2.1.13  2014-06-12 CRAN (R 3.2.0)

Ethen Liu

2017-01-13