Automated testing in R

Noam Finkelstein

Please interupt with questions / comments!

Outline

  1. Introduction: What is Automated testing?
  2. Practical testing in R: How to use testthat
  3. Automated testing best practices

What is Automated Testing

- Code that runs and checks itself - Run functions with a variety of inputs and check outputs - Replaces reloading source and checking outputs manually
Function:

    magic = function(a, b) {
        # do some important work
        return(a + b)    
    }
Test:

    test_that('test magic adds two numbers', {
        # given 
        num1 = 4
        num2 = 5

        # when
        result = magic(num1, num2)

        # then
        expect_equal(result, 9)
    })

Why test?

  1. Improve reliability
  2. Facilitates refactoring
  3. Improves code design
  4. Makes large projects manageable

Testing Framework

  • testthat is the best testing framework in R
  • tightly coupled to devtools
  • fits nicely with the rest of the tidyverse
  • --(if you are into that kind of thing)
## Testing Framework Two parts: - Test Runner (runs test functions) - Expectations / Assertions
## Installation Get it now! ``` > > install.packages('testthat') > ```
#### testthat-starter Starter kit for playing with automated tests: ``` git clone https://github.com/n-s-f/testthat-starter.git ```
{ { coding break } }

Testing Best Practices

How to write a test Function:

    magic = function(a, b) {
        # do some important work
        return(a + b)    
    }
Test:

    test_that('test magic adds two numbers', {
        # given 
        num1 = 4
        num2 = 5

        # when
        result = magic(num1, num2)

        # then
        expect_equal(result, 9)
    })

Parts of a test

- Setup: # given

- Action: # when

- Assertion: # then

Levels of Testing

- Unit Testing

- Component Testing

- Integration Testing

Test Driven Development

- Write tests and code at the same time

- Tight feedback loop

- Encourages clear thinking and good design


Mocking

Two use cases:
  1. Isolate code for testing
  2. Remove calls to slow functions or external resources

Mocking

Remember: unit tests test one unit

Mocking helps isolate code so a test is not running too much code.

Mocking


test_that('something involving slow function', {
    with_mock(slow_function=function(...) {return(5)}, {
        result = function_that_calls_slow_function()
        expect_equal(result, 123)
    })
})

Testability

Functions! (not function-free scripts)

Small functions

(like, really really small)


Testability

Pass data into functions so it can be tested with different inputs

Good:

magic = function(col_name, some_data) {
    ## do some important work
    return(1)
}

get_data = function(data_location) {
    # find the dataz
}

data = get_data('the place')
magic('the column', data)
You can pass in arbitrary data for testing purposes
Less good:

magic = function('col_name') {
    data = get_data_from_somewhere_hardcoded()
    ## do some important work
    return(1)
})
Cannot test `magic` with arbitrary data.

Functional Programming

Everything is a function

Easier to test inputs and outputs

You can go much deeper...

open source plug

Proctor

- test runner


Mockery

- stubbing and mocking library

## Additional Resources

[Hadley Wickham's Chapter on Testing](http://r-pkgs.had.co.nz/tests.html)


[Gary Bernhardt's Talk on Testing with Boundaries](https://www.destroyallsoftware.com/talks/boundaries)

Questions?