Skip to content

R package for generative property-based testing with automatic doc generation

Notifications You must be signed in to change notification settings

alekrutkowski/gentest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gentest -- R package for generative property-based testing with automatic doc generation

# API

## Testing

  • gentest -- evaluates a given function for all the combinations of the arguments provided as vectors/lists; the results are presented in a data frame (including possible errors and warnings)

## Auto-Documentation

  • docgen -- automatically generates the .Rd doc/help file from the gentest's result; it helps keeping your documentation up-to-date and in sync with the test results
  • Name -- use it to tag the return values of your own generator function in a human-readable way, useful when docgen is used

## Built-in generators of atomic vectors

  • Bool
  • SingleCharacter
  • String
  • NumericInRange
  • IntegerInRange

## Modifiers

  • Scalar -- randomly selects a random element from a vector (i.e. making the length of a vector == 1).
  • withNA -- injects a fraction of NAs into a vector
  • withZero -- injects a fraction of zeros (0) into a vector (ensuring that zeros are included in the tests)

## Wrappers

  • List -- a wrapper for lists
  • DataFrame -- a wrapper for data frames
  • %|% -- a binary operator to combine different generators in a list; to be read as "or"
  • AsOne -- the vector wrapped by AsOne is used as one argument value in an evaluations instead of element-by-element (as scalars)
  • Replicate -- produces a vector (list) of values generated by a given generator or expression multiple times repeatedly
  • Anything -- a convenience wrapper around a few most typical generators

## "Safety brakes"

Terminators if something goes wrong in tests:

  • stopIfErr
  • stopIfWarn
  • stopIfErrOrWarn

# Examples

library(gentest)
# A trivial example to demonstrate
# the data frame generated by `gentest`
res1 <- gentest(paste,
                x = c('a','b','c'),
                y = 1:2,
                z = list(100.5, 'Zzzzz'),
                sep = '_')
res1
##    x y     z sep   x..type y..type   z..type sep..type   ..value ..warning ..error ..value..type
## 1  a 1 100.5   _ character integer   numeric character a_1_100.5        NA      NA     character
## 2  b 1 100.5   _ character integer   numeric character b_1_100.5        NA      NA     character
## 3  c 1 100.5   _ character integer   numeric character c_1_100.5        NA      NA     character
## 4  a 2 100.5   _ character integer   numeric character a_2_100.5        NA      NA     character
## 5  b 2 100.5   _ character integer   numeric character b_2_100.5        NA      NA     character
## 6  c 2 100.5   _ character integer   numeric character c_2_100.5        NA      NA     character
## 7  a 1 Zzzzz   _ character integer character character a_1_Zzzzz        NA      NA     character
## 8  b 1 Zzzzz   _ character integer character character b_1_Zzzzz        NA      NA     character
## 9  c 1 Zzzzz   _ character integer character character c_1_Zzzzz        NA      NA     character
## 10 a 2 Zzzzz   _ character integer character character a_2_Zzzzz        NA      NA     character
## 11 b 2 Zzzzz   _ character integer character character b_2_Zzzzz        NA      NA     character
## 12 c 2 Zzzzz   _ character integer character character c_2_Zzzzz        NA      NA     character
library(magrittr) # for the pipe operator: %>%
tagNA <- function(x) # Helper function used below
    if (x %>% is.na)
        x %>% gentest:::addClass('NA') else x
# Assume we want to test the function
# `mean(x, na.rm)` paying attention to the
# combinations of arguments when NA is returned:
res2 <- gentest(mean,
                tagNA, # this function is applied to the value returned by `mean`
                x = NumericInRange(-1e6,1e6) %|%
                    String(string_length=3) %|% # max 3 chars to fit the table when printed below
                    (NumericInRange(-1e6,1e6) %>% withNA),
                na.rm = T %|% F)
# Now lets' generate file 'mean.Rd' in sub-directory 'man'
# of the current working directory:
docgen(res2, 'Type-checked mean()')
## Saved the doc file in the current working directory as:
## "man/mean.Rd"

 

See the help file below (as rendered by GitHub):


<title> R: Type-checked mean() </title>
mean R Documentation

Type-checked mean()

Usage

mean(x, ...) 

Arguments

x

NumericInRange(-1e+06,1e+06) or
String(string\_length = 3) or
NumericInRange(-1e+06,1e+06) withNA(in 10% of observations)

na.rm

TRUE or
FALSE

Value

Returned value x na.rm
numeric NumericInRange(-1e+06,1e+06) FALSE
numeric NumericInRange(-1e+06,1e+06) TRUE
numeric NumericInRange(-1e+06,1e+06) withNA(in 10% of observations) TRUE
numeric NA NumericInRange(-1e+06,1e+06) withNA(in 10% of observations) FALSE
numeric NA String(string\_length = 3) FALSE
numeric NA String(string\_length = 3) TRUE

 

# Using your own generator with docgen

A generator can be any function. If you want to use the result of the gentest to create automatically the .Rd doc/help file, it's best (though not mandatory) to tag the return value of your generator with a human-readable name through the function Name:

# Assume you have a generator function that
# generates one of the 3 characters:
# either A or B or C
A_or_B_or_C <- function()
    c('A','B','C') %>%
    # add a human-readable name via Name()
    # if you want to use that generator in
    # `docgen`
    Name('A or B or C')

res3 <- gentest(paste0,
                # `arg1` could be also specified as:
                # 'A' %|% 'B' %|% 'C'
                arg1 = A_or_B_or_C(),
                arg2 = NumericInRange(1,10))
docgen(res3)
## Saved the doc file in the current working directory as:
## "man/paste0.Rd"

 

See the help file below (as rendered by GitHub):


<title> R: paste0 </title>
paste0 R Documentation

paste0

Usage

paste0(..., collapse = NULL) 

Arguments

arg1

A or B or C

arg2

NumericInRange(1,10)

Value

character


 

docgen generates the documentation only for those combinations of arguments that do not return an error:

res4 <- gentest(`+`,
                x = 1:3,
                y = list(101,102,'abc'))
res4
##   x   y x..type   y..type                                           ..value ..warning      ..error               ..value..type
## 1 1 101 integer   numeric                                               102        NA           NA                     numeric
## 2 2 101 integer   numeric                                               103        NA           NA                     numeric
## 3 3 101 integer   numeric                                               104        NA           NA                     numeric
## 4 1 102 integer   numeric                                               103        NA           NA                     numeric
## 5 2 102 integer   numeric                                               104        NA           NA                     numeric
## 6 3 102 integer   numeric                                               105        NA           NA                     numeric
## 7 1 abc integer character non-numeric argument to binary operator, ..f(...)        NA Error in.... simpleError error condition
## 8 2 abc integer character non-numeric argument to binary operator, ..f(...)        NA Error in.... simpleError error condition
## 9 3 abc integer character non-numeric argument to binary operator, ..f(...)        NA Error in.... simpleError error condition
# 'character' as one of the possible classes/types for
# argument `y` is NOT included in the doc file:
docgen(res4)
## Saved the doc file in the current working directory as:
## "man/+.Rd"

 

See the help file below (as rendered by GitHub):


<title> R: + </title>
+ R Documentation

+

Usage

`+`(e1, e2) 

Arguments

x

integer

y

numeric

Value

numeric


 

# More complex (non-atomic) objects (data frames, lists)
# are also supported:
myfun <- function(df)
    within(df, {
        b <- as.character(a + 1)
        y <- !x
    })
res5 <- gentest(myfun,
                df = Replicate(DataFrame(a = NumericInRange(0, 1e6) %>% withZero,
                                         x = Bool())))
docgen(res5)
## Saved the doc file in the current working directory as:
## "man/myfun.Rd"

 

See the help file below (as rendered by GitHub):


<title> R: myfun </title>
myfun R Documentation

myfun

Usage

myfun(df) 

Arguments

df

data.frame(a = NumericInRange(0,1e+06) withZero(in 10% of observations), x = Bool)

Value

data.frame(a = numeric, x = logical, y = logical, b = character)


About

R package for generative property-based testing with automatic doc generation

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages