Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/armcn/quickcheck
Property Based Testing in R
https://github.com/armcn/quickcheck
functional-programming property-based-testing r rstats
Last synced: about 1 month ago
JSON representation
Property Based Testing in R
- Host: GitHub
- URL: https://github.com/armcn/quickcheck
- Owner: armcn
- License: other
- Created: 2021-12-23T22:37:37.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2023-10-11T22:32:00.000Z (over 1 year ago)
- Last Synced: 2024-10-14T13:18:37.938Z (3 months ago)
- Topics: functional-programming, property-based-testing, r, rstats
- Language: R
- Homepage: https://armcn.github.io/quickcheck
- Size: 3.1 MB
- Stars: 24
- Watchers: 1
- Forks: 1
- Open Issues: 5
-
Metadata Files:
- Readme: README.Rmd
- License: LICENSE
Awesome Lists containing this project
README
---
output: github_document
---```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```# quickcheck
[![CRAN status](https://www.r-pkg.org/badges/version/quickcheck)](https://CRAN.R-project.org/package=quickcheck)
[![R-CMD-check](https://github.com/armcn/quickcheck/workflows/R-CMD-check/badge.svg)](https://github.com/armcn/quickcheck/actions)
[![Codecov test coverage](https://codecov.io/gh/armcn/quickcheck/branch/main/graph/badge.svg)](https://app.codecov.io/gh/armcn/quickcheck?branch=main)
[![metacran downloads](https://cranlogs.r-pkg.org/badges/quickcheck)](https://cran.r-project.org/package=quickcheck)# Overview
Property based testing in R, inspired by [QuickCheck](https://en.wikipedia.org/wiki/QuickCheck). This package builds on the property based testing framework provided by [`hedgehog`](https://github.com/hedgehogqa/r-hedgehog) and is designed to seamlessly integrate with [`testthat`](https://testthat.r-lib.org).
## Installation
You can install the released version of `quickcheck` from [CRAN](https://CRAN.R-project.org) with:
```{r, eval=FALSE}
install.packages("quickcheck")
```And the development version from [GitHub](https://github.com/) with:
```{r, eval=FALSE}
# install.packages("remotes")
remotes::install_github("armcn/quickcheck")
```# Usage
The following example uses `quickcheck` to test the properties of the base R `+` function. [Here](https://fsharpforfunandprofit.com/posts/property-based-testing/) is an introduction to the concept of property based testing, and an explanation of the mathematical properties of addition can be found [here](https://www.khanacademy.org/math/cc-sixth-grade-math/cc-6th-factors-and-multiples/properties-of-numbers/a/properties-of-addition).
```{r}
library(testthat)
library(quickcheck)test_that("0 is the additive identity of +", {
for_all(
a = numeric_(len = 1),
property = function(a) expect_equal(a, a + 0)
)
})test_that("+ is commutative", {
for_all(
a = numeric_(len = 1),
b = numeric_(len = 1),
property = function(a, b) expect_equal(a + b, b + a)
)
})test_that("+ is associative", {
for_all(
a = numeric_(len = 1),
b = numeric_(len = 1),
c = numeric_(len = 1),
property = function(a, b, c) expect_equal(a + (b + c), (a + b) + c)
)
})
```Here we test the properties of the [`distinct`](https://dplyr.tidyverse.org/reference/distinct.html)
function from the [`dplyr`](https://dplyr.tidyverse.org/index.html) package.```{r}
library(dplyr, warn.conflicts = FALSE)test_that("distinct does nothing with a single row", {
for_all(
a = any_tibble(rows = 1L),
property = function(a) {
distinct(a) %>% expect_equal(a)
}
)
})test_that("distinct returns single row if rows are repeated", {
for_all(
a = any_tibble(rows = 1L),
property = function(a) {
bind_rows(a, a) %>%
distinct() %>%
expect_equal(a)
}
)
})test_that("distinct does nothing if rows are unique", {
for_all(
a = tibble_of(integer_positive(), rows = 1L, cols = 1L),
b = tibble_of(integer_negative(), rows = 1L, cols = 1L),
property = function(a, b) {
unique_rows <- bind_rows(a, b)
distinct(unique_rows) %>% expect_equal(unique_rows)
}
)
})
```## Quickcheck generators
Many generators are provided with `quickcheck`. Here are a few examples.
### Atomic vectors
```{r}
integer_(len = 10) %>% show_example()
character_alphanumeric(len = 10) %>% show_example()
posixct_(len = 10, any_na = TRUE) %>% show_example()
```### Lists
```{r}
list_(a = constant(NULL), b = any_undefined()) %>% show_example()
flat_list_of(logical_(), len = 3) %>% show_example()
```### Tibbles
```{r}
tibble_(a = date_(), b = hms_(), rows = 5) %>% show_example()
tibble_of(double_bounded(-10, 10), rows = 3, cols = 3) %>% show_example()
any_tibble(rows = 3, cols = 3) %>% show_example()
```## Hedgehog generators
`quickcheck` is meant to work with `hedgehog`, not replace it. `hedgehog` generators
can be used by wrapping them in `from_hedgehog`.```{r}
library(hedgehog)is_even <-
function(a) a %% 2 == 0gen_powers_of_two <-
gen.element(1:10) %>% gen.with(function(a) 2^a)test_that("is_even returns TRUE for powers of two", {
for_all(
a = from_hedgehog(gen_powers_of_two),
property = function(a) is_even(a) %>% expect_true()
)
})
```Any `hedgehog` generator can be used with `quickcheck` but they can't be composed
together to build another generator. For example this will work:```{r}
test_that("powers of two and integers are both numeric values", {
for_all(
a = from_hedgehog(gen_powers_of_two),
b = integer_(),
property = function(a, b) {
c(a, b) %>%
is.numeric() %>%
expect_true()
}
)
})
```But this will cause an error:
```{r}
test_that("composing hedgehog with quickcheck generators fails", {
tibble_of(from_hedgehog(gen_powers_of_two)) %>% expect_error()
})
```A `quickcheck` generator can also be converted to a `hedgehog` generator which can
then be used with other `hedgehog` functions.```{r}
gen_powers_of_two <-
integer_bounded(1L, 10L, len = 1L) %>%
as_hedgehog() %>%
gen.with(function(a) 2^a)test_that("is_even returns TRUE for powers of two", {
for_all(
a = from_hedgehog(gen_powers_of_two),
property = function(a) is_even(a) %>% expect_true()
)
})
```## Fuzz tests
Fuzz testing is a special case of property based testing in which the only
property being tested is that the code doesn't fail with a range of inputs.
Here is an example of how to do fuzz testing with `quickcheck`. Let's say we want
to test that the `purrr::map` function won't fail with any vector as input.```{r}
test_that("map won't fail with any vector as input", {
for_all(
a = any_vector(),
property = function(a) purrr::map(a, identity) %>% expect_silent()
)
})
```## Repeat tests
Repeat tests can be used to repeatedly test that a property holds true for many
calls of a function. These are different from regular property based tests
because they don't require generators. The function `repeat_test` will call
a function many times to ensure the expectation passes in all cases. This kind
of test can be useful for testing functions with randomness.```{r}
test_that("runif generates random numbers between a min and max value", {
repeat_test(
property = function() {
random_number <- runif(1, min = 0, max = 10)
expect_true(random_number >= 0 && random_number <= 10)
}
)
})
```