1 Prerequisites

1.1 Software

1.1.1 R packages

  • devtools - really don’t know how people managed to develop R packages without it
  • testthat - for automatic testing
  • covr - for tests coverage reports
  • dplyr - we will use it in our example function
install.packages(c('devtools', 'testthat', 'covr'))

1.2 Accounts

  • GitHub - git hosting with many useful features (issue tracker, wiki, external tools, e.g. continues integration systems)
  • Travis - continues integration system working with GitHub (so get a GitHub account first)
  • Coveralls - code coverage reporting system working with GitHub (so get a GitHub account first)

2 Create a project

2.1 Create new GitHub repository

Log into GitHub and create a new repository (click the plus sign in the top-right corner and choose New repository…)

  • Give it a name,
  • Choose it to be public,
  • Check Initialize this repository with a README
  • Choose R from Add .gitignore
  • Choose MIT License (or any other) from Add a license
  • Click Create repository

  • copy your repository clone URL - it will be needed in the next step

2.2 Create and configure RStudio project

2.2.1 Create RStudio project from the GitHub repository

  • Open RStudio
  • Choose menu File->New Project…
  • Choose Version Control
  • Choose Git
  • Paste your repository clone URL (see previous chapter) into Repository URL
  • Choose a directory
  • Click Create Project

2.2.2 Configure your RStudio project

  • Choose menu Tools->Project Options…
  • Go to General
    • Choose No in all cases (we are developing a package, so there should be no automatically created session data there)
  • Go to Build Tools
    • In Project build tools choose Package
    • Check Generate documentation with Roxygen
      • Make sure all entries in the Automatically roxygenize when running: are checked
  • Close window by clicking OK (RStudio will reload your project)
  • Create file .Rbuildignore in the root directory of your project
    (it contains a list of files which should be omitted during package build)
    Fill it with:

    ^.*\.Rproj$
    ^\.Rproj\.user$
    ^.travis.yml$
  • Add a line *.Rproj to file .gitignore
    (we don’t want to include our RStudio project file in the repository)

2.3 Initialize an R package

  • Initialize package skeleton
    (this will generate an R directory and a DESCRIPTION file)
    (we are using rstudio = FALSE parameter, because RStudio project files are already created)

    devtools::setup(rstudio = FALSE)
  • Edit the DECRIPTION file:
    • Fill in Title and Description
      (title text should be in title case, e.g. My Sample Project instead of My sample project)
      (description text should contain a full stop at the end)
    • Fill in Authors@R (replace First and Last and a sample email with your name, surname and email, leave role as it is)
    • Put your license name followed by + file LICENSE in the License field (compare screenshot below)
    • If you plan to use functions from other packages, put their names (and, if required, versions) as a comma-separated list in the Import field (compare screenshot below)
  • Go to tab Build and click Check (or simply hit CTRL+SHIFT+E)
    A lot of messages will be displayed but the final one should be R CMD check succeeded
    Moreover you should see no more then one warning (the one we can expect is checking PDF version of manual … WARNING and is connected with the fact we do not have LaTeX installed on our computer)
    Congratulations! You have just built your first R package (an empty one, but anyway)

2.4 Set up testing and continues integration

  • Set up directory structure for tests (directory tests with some content will be created)

    devtools::use_testthat()
  • Enable Coveralls for your repository
  • Add the covr package to the Suggests: section of you DESCRIPTION file
  • Set up a continues integration with code coverage checks
    • Create file .travis.yml in the root directory of your project
      Fill it with:
      (adjust the notification email)

      # http://docs.travis-ci.com/user/languages/r/
      language: r
      
      cache: packages
      
      r:
        - release
      
      after_success:
        - Rscript -e 'library(covr);coveralls()'
      
      warnings_are_errors: false
      
      notifications:
        email:
          - your@email
      • if your package depends on packages available on GitHub but not on CRAN, add a section:

        r_github_packages:
          - user1/repoName1
          - user2/repoName2
    • Log in to Travis at https://travis-ci.org/
    • Go to https://travis-ci.org/profile/
    • Turn on Travis for your new repository
      • If you can not see your repository on the list, you may refresh the list with the Sync button
  • Add Travis and Coveralls badges to your package README.md file:
    (adjust your login and repository name in URLs) (you can also copy badges code from Travis/Coveralls web sites)

    # sampleProject
    
    [![Travis-CI Build Status](https://travis-ci.org/zozlak/sampleProject.png?branch=master)](https://travis-ci.org/zozlak/sampleProject)
    [![Coverage Status](https://coveralls.io/repos/zozlak/sampleProject/badge.svg?branch=master&service=github)](https://coveralls.io/github/zozlak/sampleProject?branch=master)

2.5 Commit your changes

  • Open the Git tab in the RStudio (see the screenshot)
  • Check all files in the Staged column
  • Click the Commit button
  • Provide a commit description in the Commit message text field
  • Click the Commit button
  • Close the window summing up your commit.
  • Push your commit to the GitHub by clicking the Push button (if for some reason RStudio is unable to push the commit for you, please do it externally using your favorite git shell)

3 Create a simple function

Now lets create a simple function which will take two arguments and return average value of the first one grouped according to values of the second one

3.1 Create a file with function source code

Create file myFunction.R in the R directory with a following content:

myFunction = function(values, groups){
  result = data.frame(value = values, group = groups) %>%
    group_by_('group') %>%
    summarize_(avg = ~mean(value, na.rm = TRUE))
  return(result)
}

Remarks:

  • you can use any file name you want but it is easier to find your function if a name of the file is the same as function name
  • you can put many functions into one file but I find it easier to manage my source code if I create one file per function

3.2 Provide documentation

We will use Roxygen syntax.
The easiest way to introduce it will be an example, so lets look at the myFunction.R after adding documentation in a Roxygen syntax:

#' Computes means by groups
#' @description
#' You can put extended description here
#' @details
#' If your function is complicated, you may consider adding a details section
#' @param values numeric vector of values
#' @param groups vector of groups
#' @return data.frame with groups names in first column and average values per group in the second one
#' @export
#' @import dplyr
myFunction = function(values, groups){
  result = data.frame(value = values, group = groups) %>%
    group_by(group) %>%
    summarize(avg = mean(value, na.rm = TRUE))
  return(result)
}

Remarks:

  • @description and @details sections are optional
  • @param has syntax @param argumentName argument description
  • @return has no formal syntax (but I prefer to denote returned value type followed by detailed description)
  • @export means your function should be visible to package users (in opposite to functions which are used only internally)
  • @import has syntax @import packageName and means that you are using functions from a given R package in your function
    • instead of using @import you can explicitly declare namespace for functions from other package (e.g. write dplyr::group_by() instead of simply group_by())
    • if you want to submit your package to CRAN, take a while to read more on this topic, e.g. in Hadley’s publications here and here as well as in the Writing R Extensions

3.3 Check if function works

  • Hit CTRL+SHIFT+L to install and load package
    (it is something like install.packages(myPackage) followed by library(myPackage) but of course your package is installed from local source and not from CRAN)
    (you can also click the Build & Reload button in the Build tab)
  • Run your function on sample data:

    myFunction(1:10, rep(1:2, each = 5))
    ## Source: local data frame [2 x 2]
    ## 
    ##   group   avg
    ##   (int) (dbl)
    ## 1     1     3
    ## 2     2     8
  • Check automatically created documentation:

    ?myFunction

4 Create a simple test

4.1 User-defined tests

We will use the testthat library to prepare tests.

  • Create file tests/testthat/test-myFunction.R:

    test_that('my first test', {
      expect_equal(
        myFunction(1:10, rep(1:2, each = 5)),
        data.frame(group = c(1, 2), avg = c(3, 8))
      )
    })
    Remarks:
    • Each file containing test should be put in the tests/testthat directory and its nave have to begin with test-
      • its up to you, how you divide your tests among files
    • Each test file contains one or more group of tests. A group is denoted by a test_that() function call which takes to arguments - group name and the R code performing tests.
    • In each group you are using different expect…()_ functions to check if your package behaves as expected
      (technically we call such single tests assertions or expectations)
  • Run your tests by hitting CTRL+SHIFT+T
    (this will run all tests in your project)
    (of course you can also run single tests manually by marking their source and hitting CTRL+ENTER)
  • You should get something like:

    Loading sampleProject
    Loading required package: testthat
    Loading required package: dplyr
    
    Attaching package: ‘dplyr’
    
    Następujące obiekty zostały zakryte z ‘package:stats’:
    
        filter, lag
    
    Następujące obiekty zostały zakryte z ‘package:base’:
    
        intersect, setdiff, setequal, union
    
    Loading required package: ggplot2
    Testing sampleProject
    1
    1. Failure (at test-myFunction.R#2): my first test -----------------------------
    myFunction(1:10, rep(1:2, each = 5)) not equal to data.frame(group = c(1, 2), avg = c(3, 8))
    Incompatible type for column group: x integer, y numeric
  • Lets fix our test according to the error message (by providing the group variable as an integer data type):

    test_that('my first test', {
      expect_equal(
        myFunction(1:10, rep(1:2, each = 5)),
        data.frame(group = c(1L, 2L), avg = c(3, 8))
      )
    })

5 R package tests

Besides tests provided by you R can perform his own tests on your package code.
Among them there are as useful ones as checking for undefined variables and functions (a curse of R developers).
As these are more advanced topics and your package will still work even R package tests rise some NOTEs or even WARNINGs on it you can skip this section if you find it to difficult (but if you want to put your package in CRAN at some point you will have master them anyway).

6 See how it works

At the moment we should have an R project configured to work with Travis and Coveralls with a sample function and tests.
No lets try to see a continues integration at work.