The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
Property based testing in R, inspired by QuickCheck. This
package builds on the property based testing framework provided by hedgehog
and is designed to seamlessly integrate with testthat
.
You can install the released version of quickcheck
from
CRAN with:
install.packages("quickcheck")
And the development version from GitHub with:
# install.packages("remotes")
::install_github("armcn/quickcheck") remotes
The following example uses quickcheck
to test the
properties of the base R +
function. Here
is an introduction to the concept of property based testing, and an
explanation of the mathematical properties of addition can be found here.
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 passed π
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 passed πΈ
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)
)
})#> Test passed π
Here we test the properties of the distinct
function from the dplyr
package.
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 passed π
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 passed π
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) {
<- bind_rows(a, b)
unique_rows distinct(unique_rows) %>% expect_equal(unique_rows)
}
)
})#> Test passed π
Many generators are provided with quickcheck
. Here are a
few examples.
integer_(len = 10) %>% show_example()
#> [1] -833 5111 -8831 -3495 -1899 1051 9964 2473 9557 -2465
character_alphanumeric(len = 10) %>% show_example()
#> [1] "y5Ph" "8" "B8" "3vOcYf" "qr" "o"
#> [7] "5rW2nHdrA" "88" "umU" "vJpqr"
posixct_(len = 10, any_na = TRUE) %>% show_example()
#> [1] "1652-02-25 11:34:40 LMT" "1683-08-15 05:26:47 LMT"
#> [3] "2339-08-19 19:19:07 PDT" "0244-05-09 12:26:30 LMT"
#> [5] "0756-11-24 03:23:10 LMT" "0660-04-16 21:21:08 LMT"
#> [7] "2993-05-14 04:45:47 PDT" NA
#> [9] "1301-04-09 00:40:00 LMT" NA
list_(a = constant(NULL), b = any_undefined()) %>% show_example()
#> $a
#> NULL
#>
#> $b
#> [1] -Inf
flat_list_of(logical_(), len = 3) %>% show_example()
#> [[1]]
#> [1] TRUE
#>
#> [[2]]
#> [1] TRUE
#>
#> [[3]]
#> [1] TRUE
tibble_(a = date_(), b = hms_(), rows = 5) %>% show_example()
#> # A tibble: 5 x 2
#> a b
#> <date> <time>
#> 1 1271-08-16 22:32:16.108893
#> 2 2788-05-31 20:37:31.119791
#> 3 1246-05-10 09:14:29.411623
#> 4 2434-06-08 16:01:39.498445
#> 5 1074-10-19 04:07:18.552658
tibble_of(double_bounded(-10, 10), rows = 3, cols = 3) %>% show_example()
#> # A tibble: 3 x 3
#> ...1 ...2 ...3
#> <dbl> <dbl> <dbl>
#> 1 0 2.55 5.81
#> 2 4.42 8.87 -5.43
#> 3 9.45 7.02 -3.97
any_tibble(rows = 3, cols = 3) %>% show_example()
#> # A tibble: 3 x 3
#> ...1 ...2 ...3
#> <list> <list> <date>
#> 1 <named list [2]> <time [2]> 1628-11-24
#> 2 <named list [2]> <time [7]> 2989-06-25
#> 3 <named list [2]> <fct [4]> 2175-02-14
quickcheck
is meant to work with hedgehog
,
not replace it. hedgehog
generators can be used by wrapping
them in from_hedgehog
.
library(hedgehog)
<-
is_even function(a) a %% 2 == 0
<-
gen_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()
)
})#> Test passed π
Any hedgehog
generator can be used with
quickcheck
but they canβt be composed together to build
another generator. For example this will work:
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()
}
)
})#> Test passed π
But this will cause an error:
test_that("composing hedgehog with quickcheck generators fails", {
tibble_of(from_hedgehog(gen_powers_of_two)) %>% expect_error()
})#> Test passed π₯
A quickcheck
generator can also be converted to a
hedgehog
generator which can then be used with other
hedgehog
functions.
<-
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()
)
})#> Test passed π
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.
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()
)
})#> Test passed π
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.
test_that("runif generates random numbers between a min and max value", {
repeat_test(
property = function() {
<- runif(1, min = 0, max = 10)
random_number expect_true(random_number >= 0 && random_number <= 10)
}
)
})#> Test passed π
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.