rating complaints privileges learning raises critical advance
64.63333 66.60000 53.13333 56.36667 64.63333 74.76667 42.93333
The aim of this talk is to have a lunch time learning style presentation on the unit testing withing R. The 2 main aims of this presentation:
Provide an introduction to unit testing and the benefits having it in your toolbox can bring.
Provide some examples of unit testing and sign-post to additional reading material.
A unit test is a test that checks an individual component (the unit) is performing is expecting. In R this is effectively testing a function in most cases 1
When developing functions you’ll check your function works as expected - by running the function and checking the output matches your expectations. And that’s effectively what unit testing is, just formalisng and structuring the process!
Save time with less testing ⌚
Identify bugs early 🐛
Pinpoint exactly where errors occur 🔍
Provides confidence in your code to yourself and others 😎
Encourages better code design 📝
Learning something new 🏫
The components of a Unit Test:
Expectation - are we expecting a character or a data.frame? A certain number of rows? A specific value?
Calculated value - what your function actually returned.
Expected Value - what you expected your function to return.
If the calculated value
Imagine we wanted a simple function to calculate the mean values from a each coumn of a data.frame.
How would we check to make sure this works?
rating complaints privileges learning raises critical advance
64.63333 66.60000 53.13333 56.36667 64.63333 74.76667 42.93333
Run the function using the attitude1 dataset.
It runs returning no errors or warnings
It returns values in the correct syntax (i.e. numbers)
It’s returning a value for each column
The values are what we expect
Load the testthat 📦
Choose the expect_* function for your expectation from the testthat 📦
We’ll choose expect_silent() (checks for no errors or warnings)
If test passes we get an invisible response when running outside of testing environment.
Our first test checked the function run, but it didn’t check the actual values. So although our code runs it could still return the wrong values and we’d be none the wiser!
If we read the potential expect_* functions we could use expect_identical()! For this we need the expected argument - i.e. what values to we expect to see returned from the function.
If we look at ?expect_identical it checks for strict equality and checks the attributes are also equal. So in this case our although the values are the same, calculated vector (from our function)
The expect_equal function relaxes this requirement so we can use this. Or alternatively update our expected value so it’s also named.
If we’re programming for a specific dataset(s) often we only check that specific dataset and when developing we want to make our code re-usable. That means the user defines the inputs to our functions and it may behave in ways we didn’t expect based on their inputs.
Edge cases are the extreme ends of valid inputs (including empty values and NAs), so we should consider these when writing our function.
flowchart LR
A[Create R Package] --> B[Write\Update Function]
B --> C[Write\Update Unit Tests]
C --> D[Run Unit test]
D-->E{Pass}
D-->F{Fail}
F-.->B
F-.->C
Keep tests simple and specific so it doesn’t slow your development cycle down ⏩
Writing testable code, leads to modularised code, which leads to less errors and easier to maintain 👍
You may need to set a seed for consistent results with random number generation 🎲
Think of negative testing too - does my function return an error with the wrong inputs or does it silently continue? 🤫
Put everything back the way you found it 🔎
Create temporary files
Revert options and environmental variables to original
Test code coverage (covr 📦)
CI/CD (e.g. GitHub actions)
Test Driven Development (TDD)
📦 testthat website
📖 R Packages: Testing Basics
📃 Coding club blog
📹 R Ladies YouTube video