├── .github ├── .gitignore ├── dependabot.yml └── workflows │ └── R-CMD-check.yaml ├── revdep ├── problems.md ├── .gitignore ├── email.yml ├── cran.md ├── README.md └── failures.md ├── .gitignore ├── CRAN-SUBMISSION ├── .Rbuildignore ├── cran-comments.md ├── .lintr ├── CITATION.cff ├── MD5 ├── NAMESPACE ├── tests ├── testthat.R └── testthat │ └── test-with_parameters.R ├── .devcontainer └── devcontainer.json ├── DESCRIPTION ├── R ├── patrick-package.R └── with_parameters.R ├── CONTRIBUTING.md ├── NEWS.md ├── man ├── patrick-package.Rd └── with_parameters_test_that.Rd ├── README.md ├── CODE_OF_CONDUCT.md └── LICENSE /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /revdep/problems.md: -------------------------------------------------------------------------------- 1 | *Wow, no problems at all. :)* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/testthat/testthat-problems.rds 2 | 3 | *.Rcheck 4 | *.tar.gz 5 | -------------------------------------------------------------------------------- /revdep/.gitignore: -------------------------------------------------------------------------------- 1 | checks 2 | library 3 | checks.noindex 4 | library.noindex 5 | data.sqlite 6 | *.html 7 | -------------------------------------------------------------------------------- /CRAN-SUBMISSION: -------------------------------------------------------------------------------- 1 | Version: 0.3.0 2 | Date: 2025-01-27 18:29:13 UTC 3 | SHA: c8a66493671a23a8c3b4c1e51691afe3026f30d0 4 | -------------------------------------------------------------------------------- /revdep/email.yml: -------------------------------------------------------------------------------- 1 | release_date: ??? 2 | rel_release_date: ??? 3 | my_news_url: ??? 4 | release_version: ??? 5 | release_details: ??? 6 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | LICENSE 2 | CITATION.cff 3 | CODE_OF_CONDUCT.md 4 | CONTRIBUTING.md 5 | ^cran-comments\.md$ 6 | ^CRAN-RELEASE$ 7 | ^revdep$ 8 | ^\.devcontainer$ 9 | ^\.github$ 10 | .lintr 11 | ^CRAN-SUBMISSION$ 12 | -------------------------------------------------------------------------------- /revdep/cran.md: -------------------------------------------------------------------------------- 1 | ## revdepcheck results 2 | 3 | We checked 12 reverse dependencies (0 from CRAN + 12 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package. 4 | 5 | * We saw 0 new problems 6 | * We failed to check 0 packages 7 | 8 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## R CMD check results 2 | 3 | 0 errors | 0 warnings | 0 notes 4 | 5 | * This is a new release. 6 | * After feedback from CRAN, added CITATION.cff to .Rbuildignore. 7 | * Remove unneeded fields from DESCRIPTION: `Authors` (invalid) and `Maintainer`. 8 | Both should be covered by Authors@R. -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: all_linters( 2 | implicit_integer_linter = implicit_integer_linter(allow_colon=TRUE), 3 | todo_comment_linter(except_regex = rex::rex("TODO(#", one_or_more(digit), ")")), 4 | cyclocomp_linter = NULL 5 | ) 6 | exclusions: list( 7 | "tests/testthat.R" = list( 8 | undesirable_function_linter = Inf, 9 | unused_import_linter = Inf 10 | ) 11 | ) 12 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | abstract: "Patrick extends the 'testthat' package to add parameters to unit tests." 4 | authors: 5 | - family-names: "Quinn" 6 | given-names: "Michael" 7 | title: "Patrick" 8 | 9 | date-released: 2022-10-13 10 | license: Apache-2.0 11 | type: software 12 | url: "https://github.com/google/patrick" 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for more information: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | # https://containers.dev/guide/dependabot 6 | 7 | version: 2 8 | updates: 9 | - package-ecosystem: "devcontainers" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /MD5: -------------------------------------------------------------------------------- 1 | 1a637ebb6b823a4c5125a82c5396d41a *DESCRIPTION 2 | d034d8dbe88f73c8d3f1611d1e9362b3 *NAMESPACE 3 | ac689dffcb87904185fb194029a5c807 *NEWS.md 4 | 374c72807ac9daa0b8df99f8f8410606 *R/patrick-package.R 5 | ccd3bc69e06d4c165310beb1cf09284e *R/with_parameters.R 6 | 9673fc7ef28736ed427540aa862e64af *README.md 7 | e8e0b1907cc6004024260851dda85210 *man/patrick-package.Rd 8 | 8d3df1fe71e69e17e5b245e34d0f1690 *man/with_parameters_test_that.Rd 9 | 7d396051ec7db71be954bcac78dcd191 *tests/testthat.R 10 | a3cda73bf096cf7a2c50dc1b3536e780 *tests/testthat/test-with_parameters.R 11 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(cases) 4 | export(with_parameters_test_that) 5 | importFrom(dplyr,.data) 6 | importFrom(dplyr,bind_rows) 7 | importFrom(dplyr,mutate) 8 | importFrom(glue,glue_data) 9 | importFrom(purrr,modify_depth) 10 | importFrom(purrr,pmap) 11 | importFrom(purrr,pmap_chr) 12 | importFrom(purrr,possibly) 13 | importFrom(rlang,abort) 14 | importFrom(rlang,cnd_muffle) 15 | importFrom(rlang,enquo) 16 | importFrom(rlang,eval_tidy) 17 | importFrom(rlang,warn) 18 | importFrom(testthat,test_that) 19 | importFrom(tibble,add_column) 20 | importFrom(tibble,tibble) 21 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | library(patrick) 16 | library(testthat) 17 | 18 | test_check("patrick") 19 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/rocker-org/devcontainer-templates/tree/main/src/r-ver 3 | { 4 | "name": "R (rocker/r-ver base)", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "ghcr.io/rocker-org/devcontainer/tidyverse:4", 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 12 | // "forwardPorts": [], 13 | 14 | // Use 'postCreateCommand' to run commands after the container is created. 15 | // "postCreateCommand": "R -q -e 'renv::install()'", 16 | 17 | // Configure tool-specific properties. 18 | // "customizations": {}, 19 | 20 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 21 | // "remoteUser": "root" 22 | } 23 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: patrick 2 | Title: Parameterized Unit Testing 3 | Version: 0.3.1 4 | Authors@R: c( 5 | person(given = "Michael", 6 | family = "Quinn", 7 | role = "aut", 8 | email = "michael.quinn@aya.yale.edu"), 9 | person(given = "Michael", 10 | family = "Chirico", 11 | role = c("aut", "cre"), 12 | email = "chiricom@google.com")) 13 | Description: This is an extension of the 'testthat' package that 14 | lets you add parameters to your unit tests. Parameterized unit tests 15 | are often easier to read and more reliable, since they follow the DNRY 16 | (do not repeat yourself) rule. 17 | License: Apache License 2.0 18 | URL: https://github.com/google/patrick 19 | BugReports: https://github.com/google/patrick/issues 20 | Depends: R (>= 4.1.0) 21 | Imports: dplyr, glue, purrr, rlang, testthat (>= 3.2.0), tibble 22 | Config/testthat/edition: 3 23 | Encoding: UTF-8 24 | Roxygen: list(markdown = TRUE) 25 | RoxygenNote: 7.3.3 26 | NeedsCompilation: no 27 | Repository: CRAN 28 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macOS-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v2 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | -------------------------------------------------------------------------------- /revdep/README.md: -------------------------------------------------------------------------------- 1 | # Platform 2 | 3 | |field |value | 4 | |:--------|:----------------------------| 5 | |version |R version 4.4.2 (2024-10-31) | 6 | |os |Ubuntu 24.04.1 LTS | 7 | |system |x86_64, linux-gnu | 8 | |ui |X11 | 9 | |language |(EN) | 10 | |collate |en_US.UTF-8 | 11 | |ctype |en_US.UTF-8 | 12 | |tz |Etc/UTC | 13 | |date |2025-01-24 | 14 | |pandoc |3.6.2 @ /usr/bin/pandoc | 15 | 16 | # Dependencies 17 | 18 | |package |old |new |Δ | 19 | |:-------|:-----|:-----|:--| 20 | |patrick |0.2.0 |0.2.0 |NA | 21 | 22 | # Revdeps 23 | 24 | ## Failed to check (12) 25 | 26 | |package |version |error |warning |note | 27 | |:---------------------|:-------|:-----|:-------|:----| 28 | |bayesplay |? | | | | 29 | |DoubleML |? | | | | 30 | |dwctaxon |? | | | | 31 | |lintr |? | | | | 32 | |mombf |? | | | | 33 | |prqlr |? | | | | 34 | |RAMClustR |? | | | | 35 | |Spectra |? | | | | 36 | |spectralGraphTopology |? | | | | 37 | |statsExpressions |? | | | | 38 | |text2speech |? | | | | 39 | |wav |? | | | | 40 | 41 | -------------------------------------------------------------------------------- /R/patrick-package.R: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #' Parameterized Unit Testing 16 | #' 17 | #' `patrick` (parameterized testing in R is kind of cool!) is a `testthat` 18 | #' extension that lets you create reusable blocks of a test codes. Parameterized 19 | #' tests are often easier to read and more reliable, since they follow the DNRY 20 | #' (do not repeat yourself) rule. To do this, define tests with the function 21 | #' [with_parameters_test_that()]. Multiple approaches are provided for passing 22 | #' sets of cases. 23 | #' 24 | #' This package is inspired by parameterized testing packages in other 25 | #' languages, notably the 26 | #' [`parameterized`](https://github.com/wolever/parameterized) library in 27 | #' Python. 28 | #' @keywords internal 29 | #' @importFrom dplyr bind_rows mutate 30 | #' @importFrom glue glue_data 31 | #' @importFrom purrr modify_depth pmap pmap_chr possibly 32 | #' @importFrom rlang abort cnd_muffle enquo eval_tidy warn 33 | #' @importFrom testthat test_that 34 | #' @importFrom tibble add_column tibble 35 | #' @inherit with_parameters_test_that examples 36 | "_PACKAGE" 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your patches! Before we can take them, we have to jump a 6 | couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an 13 | [individual CLA](https://developers.google.com/open-source/cla/individual). 14 | * If you work for a company that wants to allow you to contribute your work, 15 | then you'll need to sign a 16 | [corporate CLA](https://developers.google.com/open-source/cla/corporate). 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the 25 | [issue tracker](https://github.com/google/patrick). 26 | 1. Please don't mix more than one logical change per submittal, because it 27 | makes the history hard to follow. If you want to make a change that doesn't 28 | have a corresponding issue in the issue tracker, please create one. 29 | 1. Also, coordinate with team members that are listed on the issue in question. 30 | This ensures that work isn't being duplicated and communicating your plan 31 | early also generally leads to better patches. 32 | 1. If your proposed change is accepted, and you haven't already done so, sign a 33 | Contributor License Agreement (see details above). 34 | 1. Fork the desired repo, develop and test your code changes. 35 | 1. Ensure that your code adheres to the existing style in the sample to which 36 | you are contributing. 37 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 38 | 1. Submit a pull request. 39 | 40 | If you are a Googler, it is preferable to first create an internal change and 41 | have it reviewed and submitted, and then create an upstreaming pull request 42 | here. 43 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # patrick (0.3.1) 2 | 3 | * @chiricom is now the maintainer of {patrick}. Thanks @michaelquinn32 4 | for 7 years at the help as package creator and maintainer! 5 | * Minor fix in the test suite to adapt to {testthat} 3.3.0. Thanks @hadley! 6 | * Require R >= 4.1.0, which {testthat} 3.3.0 does as well. 7 | 8 | # patrick (0.3.0) 9 | 10 | * Patrick can build test names as {glue}-formatted strings, e.g. 11 | 12 | ```r 13 | with_parameters_test_that( 14 | "col2hex works for color {color_name}", 15 | { 16 | expect_equal(col2hex(color_name), color_hex) 17 | }, 18 | color_name = c("red", "blue", "black"), 19 | color_hex = c("#FF0000", "#0000FF", "#000000") 20 | ) 21 | ``` 22 | 23 | This also works for supplying such a formatted string as `.test_name`. 24 | 25 | To disable this behavior, use `.interpret_glue = FALSE`. 26 | 27 | Thanks @chiricom! 28 | 29 | # patrick 0.2.0 30 | 31 | ## New features 32 | 33 | * Patrick will try to generate names automatically if not provided. This 34 | also works when cases are provided as a data frame. 35 | 36 | # patrick 0.1.0 37 | 38 | Breaking changes: 39 | 40 | * Setting test names should now happen with `.test_name`, instead of the 41 | implicit `test_name` variable from before. This is now an explicit 42 | argument for the function `with_parameters_test_that()`, and the leading dot 43 | should help distinguish this from values passed as cases. 44 | 45 | # patrick 0.0.4 46 | 47 | Update `patrick` for testthat 3e. 48 | 49 | * Catch warnings for code not being braced. We still produce the right code. 50 | * Make sure patrick uses the right line numbers. 51 | 52 | # patrick 0.0.3 53 | 54 | * Add more examples and tests for how patrick works with data frames. 55 | * Update `with_parameters_test_that()` to use 56 | [data, dots, details](https://design.tidyverse.org/dots-after-required.html#whats-the-pattern) 57 | * Modernize package files: DESCRIPTION and `R/patrick-package.R`. 58 | 59 | # patrick 0.0.2 60 | 61 | * This is a minor update. Tests are compatible with the next version of 62 | `testthat`. 63 | 64 | # patrick 0.0.1 65 | 66 | Welcome to `patrick`, a package for parameterizing tests within testthat. Check 67 | out the README.md file to learn more about this package. 68 | -------------------------------------------------------------------------------- /man/patrick-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/patrick-package.R 3 | \docType{package} 4 | \name{patrick-package} 5 | \alias{patrick} 6 | \alias{patrick-package} 7 | \title{Parameterized Unit Testing} 8 | \description{ 9 | \code{patrick} (parameterized testing in R is kind of cool!) is a \code{testthat} 10 | extension that lets you create reusable blocks of a test codes. Parameterized 11 | tests are often easier to read and more reliable, since they follow the DNRY 12 | (do not repeat yourself) rule. To do this, define tests with the function 13 | \code{\link[=with_parameters_test_that]{with_parameters_test_that()}}. Multiple approaches are provided for passing 14 | sets of cases. 15 | } 16 | \details{ 17 | This package is inspired by parameterized testing packages in other 18 | languages, notably the 19 | \href{https://github.com/wolever/parameterized}{\code{parameterized}} library in 20 | Python. 21 | } 22 | \examples{ 23 | with_parameters_test_that("trigonometric functions match identities:", 24 | { 25 | testthat::expect_equal(expr, numeric_value) 26 | }, 27 | expr = c(sin(pi / 4), cos(pi / 4), tan(pi / 4)), 28 | numeric_value = c(1 / sqrt(2), 1 / sqrt(2), 1), 29 | .test_name = c("sin", "cos", "tan") 30 | ) 31 | 32 | # Run the same test with the cases() constructor 33 | with_parameters_test_that( 34 | "trigonometric functions match identities", 35 | { 36 | testthat::expect_equal(expr, numeric_value) 37 | }, 38 | cases( 39 | sin = list(expr = sin(pi / 4), numeric_value = 1 / sqrt(2)), 40 | cos = list(expr = cos(pi / 4), numeric_value = 1 / sqrt(2)), 41 | tan = list(expr = tan(pi / 4), numeric_value = 1) 42 | ) 43 | ) 44 | 45 | # If names aren't provided, they are automatically generated. 46 | with_parameters_test_that( 47 | "trigonometric functions match identities", 48 | { 49 | testthat::expect_equal(expr, numeric_value) 50 | }, 51 | cases( 52 | list(expr = sin(pi / 4), numeric_value = 1 / sqrt(2)), 53 | list(expr = cos(pi / 4), numeric_value = 1 / sqrt(2)), 54 | list(expr = tan(pi / 4), numeric_value = 1) 55 | ) 56 | ) 57 | # The first test case is named "expr=0.7071068, numeric_value="0.7071068" 58 | # and so on. 59 | 60 | # Or, pass a data frame of cases, perhaps using a helper function 61 | make_cases <- function() { 62 | tibble::tribble( 63 | ~.test_name, ~expr, ~numeric_value, 64 | "sin", sin(pi / 4), 1 / sqrt(2), 65 | "cos", cos(pi / 4), 1 / sqrt(2), 66 | "tan", tan(pi / 4), 1 67 | ) 68 | } 69 | 70 | with_parameters_test_that( 71 | "trigonometric functions match identities", 72 | { 73 | testthat::expect_equal(expr, numeric_value) 74 | }, 75 | .cases = make_cases() 76 | ) 77 | } 78 | \seealso{ 79 | Useful links: 80 | \itemize{ 81 | \item \url{https://github.com/google/patrick} 82 | \item Report bugs at \url{https://github.com/google/patrick/issues} 83 | } 84 | 85 | } 86 | \author{ 87 | \strong{Maintainer}: Michael Chirico \email{chiricom@google.com} 88 | 89 | Authors: 90 | \itemize{ 91 | \item Michael Quinn \email{michael.quinn@aya.yale.edu} 92 | } 93 | 94 | } 95 | \keyword{internal} 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![R-CMD-check](https://github.com/google/patrick/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/google/patrick/actions/workflows/R-CMD-check.yaml) 4 | [![CRAN](https://www.r-pkg.org/badges/version/patrick)](https://cran.r-project.org/package=patrick) 5 | 6 | 7 | # Introducing patrick 8 | 9 | This package is an extension to `testthat` that enables parameterized unit 10 | testing in R. 11 | 12 | ## Installing 13 | 14 | The release version of `patrick` is available on CRAN. Install it in the usual 15 | manner: 16 | 17 | ``` 18 | install.packages("patrick") 19 | ``` 20 | 21 | The development version of `patrick` is currently only available on GitHub. 22 | Install it using `devtools`. 23 | 24 | ``` 25 | devtools::install_github("google/patrick") 26 | ``` 27 | 28 | To use `patrick` as a testing tool within your package, add it to your list of 29 | `Suggests` within your package's `DESCRIPTION`. 30 | 31 | ``` 32 | Suggests: 33 | patrick 34 | ``` 35 | 36 | ## Use 37 | 38 | Many packages within R employ the following pattern when writing tests: 39 | 40 | ``` 41 | test_that("Data is a successfully converted: numeric", { 42 | input <- convert(numeric_data) 43 | expect_type(input, "double") 44 | }) 45 | 46 | test_that("Data is a successfully converted: character", { 47 | input <- convert(character_data) 48 | expect_type(input, "character") 49 | }) 50 | ``` 51 | 52 | While explicit, recycling a test pattern like this is prone to user error and 53 | other issues, as it is a violation of the classic DNRY rule (do not repeat 54 | yourself). `patrick` eliminates this problem by creating test parameters. 55 | 56 | ``` 57 | with_parameters_test_that("Data is successfully converted:", { 58 | input <- convert(test_data) 59 | expect_type(input, type) 60 | }, 61 | test_data = list(numeric_data, character_data), 62 | type = c("double", "character"), 63 | .test_name = type 64 | ) 65 | ``` 66 | 67 | Parameterized tests behave exactly the same as standard `testthat` tests. Per 68 | usual, you call all of your tests with `devtools::test`, and they'll also run 69 | during package checks. Each executes independently and then your test report 70 | will produce a single report. A complete name for each test will be formed using 71 | the initial test description and the strings in the `.test_name` parameter. 72 | 73 | Small sets of cases can be reasonably passed as parameters to 74 | `with_parameters_test_that`. This becomes less readable when the number of cases 75 | increases. To help mitigate this issue, `patrick` provides a case generator 76 | helper function. 77 | 78 | ``` 79 | with_parameters_test_that("Data is successfully converted:", { 80 | input <- convert(test_data) 81 | expect_type(input, type) 82 | }, 83 | cases( 84 | double = list(test_data = numeric_data, type = "double"), 85 | character = list(test_data = character_data, type = "character") 86 | ) 87 | ) 88 | ``` 89 | 90 | More complicated testing cases can be constructed using data frames. This is 91 | usually best handled within a helper function and in a `helper-.R` file. 92 | 93 | ``` 94 | make_cases <- function() { 95 | tibble::tribble( 96 | ~ .test_name, ~ expr, ~ numeric_value, 97 | "sin", sin(pi / 4), 1 / sqrt(2), 98 | "cos", cos(pi / 4), 1 / sqrt(2), 99 | "tan", tan(pi / 4), 1 100 | ) 101 | } 102 | 103 | with_parameters_test_that( 104 | "trigonometric functions match identities", 105 | { 106 | testthat::expect_equal(expr, numeric_value) 107 | }, 108 | .cases = make_cases() 109 | ) 110 | ``` 111 | 112 | If you don't provide test names when generating cases, `patrick` will generate 113 | them automatically from the test data. 114 | 115 | ## Inspiration 116 | 117 | This package is inspired by parameterized testing packages in other languages, 118 | notably the [`parameterized`](https://github.com/wolever/parameterized) library 119 | in Python. 120 | 121 | ## Contributing 122 | 123 | Please read the 124 | [`CONTRIBUTING.md`](https://github.com/google/patrick/blob/master/CONTRIBUTING.md) 125 | for details on how to contribute to this project. 126 | 127 | ## Disclaimer 128 | 129 | This is not an officially supported Google product. 130 | -------------------------------------------------------------------------------- /man/with_parameters_test_that.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/with_parameters.R 3 | \name{with_parameters_test_that} 4 | \alias{with_parameters_test_that} 5 | \alias{cases} 6 | \title{Execute a test with parameters.} 7 | \usage{ 8 | with_parameters_test_that( 9 | desc_stub, 10 | code, 11 | ..., 12 | .cases = NULL, 13 | .test_name = NULL, 14 | .interpret_glue = TRUE 15 | ) 16 | 17 | cases(...) 18 | } 19 | \arguments{ 20 | \item{desc_stub}{A string scalar. Used in creating the names of the 21 | parameterized tests.} 22 | 23 | \item{code}{Test code containing expectations.} 24 | 25 | \item{...}{Named arguments of test parameters. All vectors should have the 26 | same length.} 27 | 28 | \item{.cases}{A data frame where each row contains test parameters.} 29 | 30 | \item{.test_name}{An alternative way for providing test names. If provided, 31 | the name will be appended to the stub description in \code{desc_stub}. If not 32 | provided, test names will be automatically generated.} 33 | 34 | \item{.interpret_glue}{Logical, default \code{TRUE}. If \code{FALSE}, and glue-like 35 | markup in \code{desc_stub} is ignored, otherwise \code{\link[glue:glue]{glue::glue_data()}} is 36 | attempted to produce a more complete test description.} 37 | } 38 | \description{ 39 | This function is an extension of \code{\link[testthat:test_that]{testthat::test_that()}} that lets you pass 40 | a series of testing parameters. These values are substituted into your 41 | regular testing code block, making it reusable and reducing duplication. 42 | } 43 | \details{ 44 | You have a couple of options for passing parameters to you test. You can 45 | use named vectors/ lists. The function will assert that you have correct 46 | lengths before proceeding to test execution. Alternatively you can used 47 | a \code{data.frame} or list in combination with the splice unquote operator 48 | \code{\link[rlang]{!!!}}. Last, you can use the constructor \code{cases()}, which 49 | is similar to building a \code{data.frame} rowwise. If you manually build the 50 | data frame, pass it in the \code{.cases} argument. 51 | \subsection{Naming test cases}{ 52 | 53 | If the user passes a character vector as \code{.test_name}, each instance is 54 | combined with \code{desc_stub} to create the completed test name. Similarly, the 55 | named argument from \code{cases()} is combined with \code{desc_stub} to create the 56 | parameterized test names. When names aren't provided, they will be 57 | automatically generated using the test data. 58 | 59 | Names follow the pattern of "name=value, name=value" for all elements in a 60 | test case. 61 | } 62 | } 63 | \examples{ 64 | with_parameters_test_that("trigonometric functions match identities:", 65 | { 66 | testthat::expect_equal(expr, numeric_value) 67 | }, 68 | expr = c(sin(pi / 4), cos(pi / 4), tan(pi / 4)), 69 | numeric_value = c(1 / sqrt(2), 1 / sqrt(2), 1), 70 | .test_name = c("sin", "cos", "tan") 71 | ) 72 | 73 | # Run the same test with the cases() constructor 74 | with_parameters_test_that( 75 | "trigonometric functions match identities", 76 | { 77 | testthat::expect_equal(expr, numeric_value) 78 | }, 79 | cases( 80 | sin = list(expr = sin(pi / 4), numeric_value = 1 / sqrt(2)), 81 | cos = list(expr = cos(pi / 4), numeric_value = 1 / sqrt(2)), 82 | tan = list(expr = tan(pi / 4), numeric_value = 1) 83 | ) 84 | ) 85 | 86 | # If names aren't provided, they are automatically generated. 87 | with_parameters_test_that( 88 | "trigonometric functions match identities", 89 | { 90 | testthat::expect_equal(expr, numeric_value) 91 | }, 92 | cases( 93 | list(expr = sin(pi / 4), numeric_value = 1 / sqrt(2)), 94 | list(expr = cos(pi / 4), numeric_value = 1 / sqrt(2)), 95 | list(expr = tan(pi / 4), numeric_value = 1) 96 | ) 97 | ) 98 | # The first test case is named "expr=0.7071068, numeric_value="0.7071068" 99 | # and so on. 100 | 101 | # Or, pass a data frame of cases, perhaps using a helper function 102 | make_cases <- function() { 103 | tibble::tribble( 104 | ~.test_name, ~expr, ~numeric_value, 105 | "sin", sin(pi / 4), 1 / sqrt(2), 106 | "cos", cos(pi / 4), 1 / sqrt(2), 107 | "tan", tan(pi / 4), 1 108 | ) 109 | } 110 | 111 | with_parameters_test_that( 112 | "trigonometric functions match identities", 113 | { 114 | testthat::expect_equal(expr, numeric_value) 115 | }, 116 | .cases = make_cases() 117 | ) 118 | } 119 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of 9 | experience, education, socio-economic status, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or reject 41 | comments, commits, code, wiki edits, issues, and other contributions that are 42 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 43 | contributor for other behaviors that they deem inappropriate, threatening, 44 | offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | This Code of Conduct also applies outside the project spaces when the Project 56 | Steward has a reasonable belief that an individual's behavior may have a 57 | negative impact on the project or its community. 58 | 59 | ## Conflict Resolution 60 | 61 | We do not believe that all conflict is bad; healthy debate and disagreement 62 | often yield positive results. However, it is never okay to be disrespectful or 63 | to engage in behavior that violates the project’s code of conduct. 64 | 65 | If you see someone violating the code of conduct, you are encouraged to address 66 | the behavior directly with those involved. Many issues can be resolved quickly 67 | and easily, and this gives people more control over the outcome of their 68 | dispute. If you are unable to resolve the matter for any reason, or if the 69 | behavior is threatening or harassing, report it. We are dedicated to providing 70 | an environment where participants feel welcome and safe. 71 | 72 | Reports should be directed to Michael Chirico [chiricom@google.com], the Project 73 | Steward(s) for `patrick`. It is the Project Steward’s duty to receive and 74 | address reported violations of the code of conduct. They will then work with a 75 | committee consisting of representatives from the Open Source Programs Office and 76 | the Google Open Source Strategy team. If for any reason you are uncomfortable 77 | reaching out the Project Steward, please email opensource@google.com. 78 | 79 | We will investigate every complaint, but you may not receive a direct response. 80 | We will use our discretion in determining when and how to follow up on reported 81 | incidents, which may range from not taking action to permanent expulsion from 82 | the project and project-sponsored spaces. We will notify the accused of the 83 | report and provide them an opportunity to discuss it before any action is taken. 84 | The identity of the reporter will be omitted from the details of the report 85 | supplied to the accused. In potentially harmful situations, such as ongoing 86 | harassment or threats to anyone's safety, we may take action without notice. 87 | 88 | ## Attribution 89 | 90 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 91 | available at 92 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 93 | -------------------------------------------------------------------------------- /revdep/failures.md: -------------------------------------------------------------------------------- 1 | # bayesplay 2 | 3 |
4 | 5 | * Version: 6 | * GitHub: https://github.com/google/patrick 7 | * Source code: NA 8 | * Date/Publication: 2020-10-27 18:50:03 UTC 9 | * Number of recursive dependencies: 0 10 | 11 |
12 | 13 | ## Error before installation 14 | 15 | ### Devel 16 | 17 | ``` 18 | 19 | 20 | 21 | 22 | 23 | 24 | ``` 25 | ### CRAN 26 | 27 | ``` 28 | 29 | 30 | 31 | 32 | 33 | 34 | ``` 35 | # DoubleML 36 | 37 |
38 | 39 | * Version: 40 | * GitHub: https://github.com/google/patrick 41 | * Source code: NA 42 | * Date/Publication: 2020-10-27 18:50:03 UTC 43 | * Number of recursive dependencies: 0 44 | 45 |
46 | 47 | ## Error before installation 48 | 49 | ### Devel 50 | 51 | ``` 52 | 53 | 54 | 55 | 56 | 57 | 58 | ``` 59 | ### CRAN 60 | 61 | ``` 62 | 63 | 64 | 65 | 66 | 67 | 68 | ``` 69 | # dwctaxon 70 | 71 |
72 | 73 | * Version: 74 | * GitHub: https://github.com/google/patrick 75 | * Source code: NA 76 | * Date/Publication: 2020-10-27 18:50:03 UTC 77 | * Number of recursive dependencies: 0 78 | 79 |
80 | 81 | ## Error before installation 82 | 83 | ### Devel 84 | 85 | ``` 86 | 87 | 88 | 89 | 90 | 91 | 92 | ``` 93 | ### CRAN 94 | 95 | ``` 96 | 97 | 98 | 99 | 100 | 101 | 102 | ``` 103 | # lintr 104 | 105 |
106 | 107 | * Version: 108 | * GitHub: https://github.com/google/patrick 109 | * Source code: NA 110 | * Date/Publication: 2020-10-27 18:50:03 UTC 111 | * Number of recursive dependencies: 0 112 | 113 |
114 | 115 | ## Error before installation 116 | 117 | ### Devel 118 | 119 | ``` 120 | 121 | 122 | 123 | 124 | 125 | 126 | ``` 127 | ### CRAN 128 | 129 | ``` 130 | 131 | 132 | 133 | 134 | 135 | 136 | ``` 137 | # mombf 138 | 139 |
140 | 141 | * Version: 142 | * GitHub: https://github.com/google/patrick 143 | * Source code: NA 144 | * Date/Publication: 2020-10-27 18:50:03 UTC 145 | * Number of recursive dependencies: 0 146 | 147 |
148 | 149 | ## Error before installation 150 | 151 | ### Devel 152 | 153 | ``` 154 | 155 | 156 | 157 | 158 | 159 | 160 | ``` 161 | ### CRAN 162 | 163 | ``` 164 | 165 | 166 | 167 | 168 | 169 | 170 | ``` 171 | # prqlr 172 | 173 |
174 | 175 | * Version: 176 | * GitHub: https://github.com/google/patrick 177 | * Source code: NA 178 | * Date/Publication: 2020-10-27 18:50:03 UTC 179 | * Number of recursive dependencies: 0 180 | 181 |
182 | 183 | ## Error before installation 184 | 185 | ### Devel 186 | 187 | ``` 188 | 189 | 190 | 191 | 192 | 193 | 194 | ``` 195 | ### CRAN 196 | 197 | ``` 198 | 199 | 200 | 201 | 202 | 203 | 204 | ``` 205 | # RAMClustR 206 | 207 |
208 | 209 | * Version: 210 | * GitHub: https://github.com/google/patrick 211 | * Source code: NA 212 | * Date/Publication: 2020-10-27 18:50:03 UTC 213 | * Number of recursive dependencies: 0 214 | 215 |
216 | 217 | ## Error before installation 218 | 219 | ### Devel 220 | 221 | ``` 222 | 223 | 224 | 225 | 226 | 227 | 228 | ``` 229 | ### CRAN 230 | 231 | ``` 232 | 233 | 234 | 235 | 236 | 237 | 238 | ``` 239 | # Spectra 240 | 241 |
242 | 243 | * Version: 244 | * GitHub: https://github.com/google/patrick 245 | * Source code: NA 246 | * Date/Publication: 2020-10-27 18:50:03 UTC 247 | * Number of recursive dependencies: 0 248 | 249 |
250 | 251 | ## Error before installation 252 | 253 | ### Devel 254 | 255 | ``` 256 | 257 | 258 | 259 | 260 | 261 | 262 | ``` 263 | ### CRAN 264 | 265 | ``` 266 | 267 | 268 | 269 | 270 | 271 | 272 | ``` 273 | # spectralGraphTopology 274 | 275 |
276 | 277 | * Version: 278 | * GitHub: https://github.com/google/patrick 279 | * Source code: NA 280 | * Date/Publication: 2020-10-27 18:50:03 UTC 281 | * Number of recursive dependencies: 0 282 | 283 |
284 | 285 | ## Error before installation 286 | 287 | ### Devel 288 | 289 | ``` 290 | 291 | 292 | 293 | 294 | 295 | 296 | ``` 297 | ### CRAN 298 | 299 | ``` 300 | 301 | 302 | 303 | 304 | 305 | 306 | ``` 307 | # statsExpressions 308 | 309 |
310 | 311 | * Version: 312 | * GitHub: https://github.com/google/patrick 313 | * Source code: NA 314 | * Date/Publication: 2020-10-27 18:50:03 UTC 315 | * Number of recursive dependencies: 0 316 | 317 |
318 | 319 | ## Error before installation 320 | 321 | ### Devel 322 | 323 | ``` 324 | 325 | 326 | 327 | 328 | 329 | 330 | ``` 331 | ### CRAN 332 | 333 | ``` 334 | 335 | 336 | 337 | 338 | 339 | 340 | ``` 341 | # text2speech 342 | 343 |
344 | 345 | * Version: 346 | * GitHub: https://github.com/google/patrick 347 | * Source code: NA 348 | * Date/Publication: 2020-10-27 18:50:03 UTC 349 | * Number of recursive dependencies: 0 350 | 351 |
352 | 353 | ## Error before installation 354 | 355 | ### Devel 356 | 357 | ``` 358 | 359 | 360 | 361 | 362 | 363 | 364 | ``` 365 | ### CRAN 366 | 367 | ``` 368 | 369 | 370 | 371 | 372 | 373 | 374 | ``` 375 | # wav 376 | 377 |
378 | 379 | * Version: 380 | * GitHub: https://github.com/google/patrick 381 | * Source code: NA 382 | * Date/Publication: 2020-10-27 18:50:03 UTC 383 | * Number of recursive dependencies: 0 384 | 385 |
386 | 387 | ## Error before installation 388 | 389 | ### Devel 390 | 391 | ``` 392 | 393 | 394 | 395 | 396 | 397 | 398 | ``` 399 | ### CRAN 400 | 401 | ``` 402 | 403 | 404 | 405 | 406 | 407 | 408 | ``` 409 | -------------------------------------------------------------------------------- /tests/testthat/test-with_parameters.R: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | with_parameters_test_that( 16 | "Running tests:", 17 | { 18 | if (test_outcome == "success") { 19 | testthat::expect_success(testthat::expect_true(case)) 20 | } else { 21 | failure_message <- "`case` (isn't true|is not TRUE|to be TRUE)" 22 | testthat::expect_failure(testthat::expect_true(case), failure_message) 23 | } 24 | }, 25 | test_outcome = c("success", "fail", "null"), 26 | case = list(TRUE, FALSE, NULL), 27 | .test_name = c("success", "fail", "null") 28 | ) 29 | 30 | with_parameters_test_that( 31 | "Names are added", 32 | { 33 | testthat::expect_identical(.test_name, "case=TRUE") 34 | }, 35 | case = TRUE 36 | ) 37 | 38 | with_parameters_test_that( 39 | "Names can be extracted from cases", 40 | { 41 | testthat::expect_identical( 42 | .test_name, 43 | "logical=FALSE, number=1, string=hello" 44 | ) 45 | }, 46 | .cases = data.frame( 47 | logical = FALSE, 48 | number = 1.0, 49 | string = "hello", 50 | stringsAsFactors = FALSE 51 | ) 52 | ) 53 | 54 | with_parameters_test_that( 55 | "Cases are correctly evaluated:", 56 | { 57 | testthat::expect_length(vec, len) 58 | }, 59 | cases( 60 | one = list(vec = 1L, len = 1L), 61 | ten = list(vec = 1:10, len = 10L) 62 | ) 63 | ) 64 | 65 | with_parameters_test_that( 66 | "Cases are correctly evaluated with names added:", 67 | { 68 | testthat::expect_identical(.test_name, "vec=1, len=1") 69 | }, 70 | cases(list(vec = 1L, len = 1L)) 71 | ) 72 | 73 | with_parameters_test_that( 74 | "Data frames can be passed to cases:", 75 | { 76 | result <- rlang::as_function(FUN)(input) 77 | testthat::expect_identical(result, out) 78 | }, 79 | .cases = tibble::tribble( 80 | ~.test_name, ~FUN, ~input, ~out, 81 | "times", ~ .x * 2L, 2L, 4L, 82 | "plus", ~ .x + 3L, 3L, 6L 83 | ) 84 | ) 85 | 86 | with_parameters_test_that( 87 | "Patrick doesn't throw inappropriate warnings:", 88 | { 89 | testthat::expect_warning(fun(), regexp = message) 90 | }, 91 | cases( 92 | shouldnt_warn = list(fun = \() 1L + 1L, message = NA), 93 | should_warn = list( 94 | fun = \() warning("still warn!", call. = FALSE), 95 | message = "still warn" 96 | ) 97 | ) 98 | ) 99 | 100 | test_that("Patrick catches the right class of warning", { 101 | testthat::local_mocked_bindings( 102 | test_that = function(...) { 103 | rlang::warn("New warning", class = "testthat_braces_warning") 104 | } 105 | ) 106 | 107 | with_parameters_test_that( 108 | "No more warnings:", 109 | { 110 | testthat::expect_true(truth) 111 | }, 112 | truth = TRUE 113 | ) |> 114 | testthat::expect_no_warning() 115 | }) 116 | 117 | # From testthat/tests/testthat/test-test-that.R 118 | # Use for checking that line numbers are still correct 119 | expectation_lines <- function(code) { 120 | code_srcref <- attr(substitute(code), "srcref") 121 | if (!is.list(code_srcref)) { 122 | stop("code doesn't have srcref", call. = FALSE) 123 | } 124 | 125 | results <- testthat::with_reporter("silent", code)$expectations() 126 | unlist(lapply(results, \(x) x$srcref[1L])) - code_srcref[[1L]][1L] 127 | } 128 | 129 | test_that("patrick reports the correct line numbers", { 130 | # nolint start: indentation_linter. 131 | lines <- expectation_lines({ 132 | # line 1 133 | with_parameters_test_that("simple", { # line 2 134 | expect_true(truth) # line 3 135 | }, # line 4 136 | cases( 137 | true = list(truth = TRUE), 138 | false = list(truth = FALSE) 139 | )) 140 | }) 141 | # nolint end: indentation_linter. 142 | expect_identical(lines, c(3L, 3L)) 143 | }) 144 | 145 | test_that('patrick gives a deprecation warning for "test_name"', { 146 | with_parameters_test_that( 147 | "Warn about `test_name` argument:", 148 | { 149 | testthat::expect_true(truth) 150 | }, 151 | truth = TRUE, 152 | test_name = "true" 153 | ) |> 154 | testthat::expect_warning(regexp = "deprecated") 155 | 156 | with_parameters_test_that( 157 | "Warn about `test_name` column:", 158 | { 159 | testthat::expect_true(truth) 160 | }, 161 | .cases = tibble::tribble( 162 | ~test_name, ~truth, 163 | "true", TRUE 164 | ) 165 | ) |> 166 | testthat::expect_warning(regexp = "deprecated") 167 | }) 168 | 169 | expectation_names <- function(code) { 170 | expectations <- testthat::with_reporter("silent", code)$expectations() 171 | vapply(expectations, \(e) as.character(e$test), character(1L)) 172 | } 173 | 174 | test_that("glue-formatted descriptions and test names supported", { 175 | with_parameters_test_that( 176 | "testing for (x, y, z) = ({x}, {y}, {z})", 177 | { 178 | testthat::expect_gt(x + y + z, 0L) 179 | }, 180 | x = 1:10, y = 2:11, z = 3:12 181 | ) |> 182 | expectation_names() |> 183 | expect_identical( 184 | sprintf("testing for (x, y, z) = (%d, %d, %d)", 1:10, 2:11, 3:12) 185 | ) 186 | 187 | with_parameters_test_that( 188 | "testing for (x, y, z):", 189 | { 190 | testthat::expect_gt(x + y + z, 0L) 191 | }, 192 | x = 1:10, y = 2:11, z = 3:12, 193 | .test_name = "({x}, {y}, {z})" 194 | ) |> 195 | expectation_names() |> 196 | expect_identical( 197 | sprintf("testing for (x, y, z): (%d, %d, %d)", 1:10, 2:11, 3:12) 198 | ) 199 | 200 | with_parameters_test_that( 201 | "testing for (x, y): ({x}, {y})", 202 | { 203 | testthat::expect_identical(x, y) 204 | }, 205 | x = list(NULL, 1:10), y = list(NULL, 1:10) 206 | ) |> 207 | expectation_names() |> 208 | expect_identical(sprintf( 209 | "testing for (x, y): ({x}, {y}) x=%1$s, y=%1$s", 210 | c("NULL", toString(1:10)) 211 | )) |> 212 | expect_warning("produced output of length 0") |> 213 | expect_warning("produced output of length 10") 214 | 215 | # but fail kindly for potential accidental use of glue 216 | # c.f. https://github.com/r-lib/lintr/issues/2706 217 | expect_error( 218 | with_parameters_test_that("a{b}", { 219 | expect_true(TRUE) 220 | }, .cases = data.frame(d = 1L)), 221 | "Attempt to interpret test stub 'a{b}' with glue failed", 222 | fixed = TRUE 223 | ) 224 | # as well as an escape hatch to work around needing ugly escapes 225 | expect_no_error( 226 | with_parameters_test_that("a{b}", { 227 | expect_true(TRUE) 228 | }, .cases = data.frame(d = 1L), .interpret_glue = FALSE) 229 | ) 230 | }) 231 | -------------------------------------------------------------------------------- /R/with_parameters.R: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | #' Execute a test with parameters. 16 | #' 17 | #' This function is an extension of [testthat::test_that()] that lets you pass 18 | #' a series of testing parameters. These values are substituted into your 19 | #' regular testing code block, making it reusable and reducing duplication. 20 | #' 21 | #' You have a couple of options for passing parameters to you test. You can 22 | #' use named vectors/ lists. The function will assert that you have correct 23 | #' lengths before proceeding to test execution. Alternatively you can used 24 | #' a `data.frame` or list in combination with the splice unquote operator 25 | #' \code{\link[rlang]{!!!}}. Last, you can use the constructor `cases()`, which 26 | #' is similar to building a `data.frame` rowwise. If you manually build the 27 | #' data frame, pass it in the `.cases` argument. 28 | #' 29 | #' ## Naming test cases 30 | #' 31 | #' If the user passes a character vector as `.test_name`, each instance is 32 | #' combined with `desc_stub` to create the completed test name. Similarly, the 33 | #' named argument from `cases()` is combined with `desc_stub` to create the 34 | #' parameterized test names. When names aren't provided, they will be 35 | #' automatically generated using the test data. 36 | #' 37 | #' Names follow the pattern of "name=value, name=value" for all elements in a 38 | #' test case. 39 | #' 40 | #' @param desc_stub A string scalar. Used in creating the names of the 41 | #' parameterized tests. 42 | #' @param code Test code containing expectations. 43 | #' @param ... Named arguments of test parameters. All vectors should have the 44 | #' same length. 45 | #' @param .cases A data frame where each row contains test parameters. 46 | #' @param .test_name An alternative way for providing test names. If provided, 47 | #' the name will be appended to the stub description in `desc_stub`. If not 48 | #' provided, test names will be automatically generated. 49 | #' @param .interpret_glue Logical, default `TRUE`. If `FALSE`, and glue-like 50 | #' markup in `desc_stub` is ignored, otherwise [glue::glue_data()] is 51 | #' attempted to produce a more complete test description. 52 | #' @examples 53 | #' with_parameters_test_that("trigonometric functions match identities:", 54 | #' { 55 | #' testthat::expect_equal(expr, numeric_value) 56 | #' }, 57 | #' expr = c(sin(pi / 4), cos(pi / 4), tan(pi / 4)), 58 | #' numeric_value = c(1 / sqrt(2), 1 / sqrt(2), 1), 59 | #' .test_name = c("sin", "cos", "tan") 60 | #' ) 61 | #' 62 | #' # Run the same test with the cases() constructor 63 | #' with_parameters_test_that( 64 | #' "trigonometric functions match identities", 65 | #' { 66 | #' testthat::expect_equal(expr, numeric_value) 67 | #' }, 68 | #' cases( 69 | #' sin = list(expr = sin(pi / 4), numeric_value = 1 / sqrt(2)), 70 | #' cos = list(expr = cos(pi / 4), numeric_value = 1 / sqrt(2)), 71 | #' tan = list(expr = tan(pi / 4), numeric_value = 1) 72 | #' ) 73 | #' ) 74 | #' 75 | #' # If names aren't provided, they are automatically generated. 76 | #' with_parameters_test_that( 77 | #' "trigonometric functions match identities", 78 | #' { 79 | #' testthat::expect_equal(expr, numeric_value) 80 | #' }, 81 | #' cases( 82 | #' list(expr = sin(pi / 4), numeric_value = 1 / sqrt(2)), 83 | #' list(expr = cos(pi / 4), numeric_value = 1 / sqrt(2)), 84 | #' list(expr = tan(pi / 4), numeric_value = 1) 85 | #' ) 86 | #' ) 87 | #' # The first test case is named "expr=0.7071068, numeric_value="0.7071068" 88 | #' # and so on. 89 | #' 90 | #' # Or, pass a data frame of cases, perhaps using a helper function 91 | #' make_cases <- function() { 92 | #' tibble::tribble( 93 | #' ~.test_name, ~expr, ~numeric_value, 94 | #' "sin", sin(pi / 4), 1 / sqrt(2), 95 | #' "cos", cos(pi / 4), 1 / sqrt(2), 96 | #' "tan", tan(pi / 4), 1 97 | #' ) 98 | #' } 99 | #' 100 | #' with_parameters_test_that( 101 | #' "trigonometric functions match identities", 102 | #' { 103 | #' testthat::expect_equal(expr, numeric_value) 104 | #' }, 105 | #' .cases = make_cases() 106 | #' ) 107 | #' @importFrom dplyr .data 108 | #' @export 109 | with_parameters_test_that <- function(desc_stub, 110 | code, 111 | ..., 112 | .cases = NULL, 113 | .test_name = NULL, 114 | .interpret_glue = TRUE) { 115 | stopifnot( 116 | is.logical(.interpret_glue), 117 | length(.interpret_glue) == 1L, 118 | !is.na(.interpret_glue) 119 | ) 120 | if (is.null(.cases)) { 121 | pars <- tibble(...) 122 | possibly_add_column <- possibly(add_column, otherwise = pars) 123 | all_pars <- possibly_add_column(pars, .test_name = .test_name) 124 | } else { 125 | all_pars <- .cases 126 | } 127 | # TODO(#33): deprecate & remove this branch 128 | if ("test_name" %in% names(all_pars)) { 129 | msg <- paste( 130 | 'The argument and cases column "test_name" is deprecated. Please use the', 131 | "new `.test_name` argument instead. See `?with_parameters_test_that`", 132 | "for more information" 133 | ) 134 | warn(msg, class = "patrick_test_name_deprecation") 135 | # It would be nicer to do this with rename(), but that function doesn't 136 | # support overwriting existing columns. 137 | all_pars <- mutate( 138 | all_pars, 139 | .test_name = .data$test_name, 140 | test_name = NULL 141 | ) 142 | } 143 | if (!".test_name" %in% names(all_pars)) { 144 | all_pars$.test_name <- build_test_names(all_pars) 145 | } 146 | captured <- enquo(code) 147 | pmap( 148 | all_pars, 149 | build_and_run_test, 150 | desc = desc_stub, 151 | code = captured, 152 | .interpret_glue = .interpret_glue 153 | ) 154 | invisible(TRUE) 155 | } 156 | 157 | #' Generate test names from cases, if none are provided. 158 | #' 159 | #' @param all_cases A tibble containing test cases. 160 | #' @return A character vector, whose length matches the number of rows in 161 | #' `all_cases`. 162 | #' @noRd 163 | build_test_names <- function(all_cases) { 164 | case_names <- names(all_cases) 165 | pmap_chr(all_cases, build_label, case_names = case_names) 166 | } 167 | 168 | build_label <- function(..., case_names) { 169 | case_row <- format(list(...)) 170 | toString(sprintf("%s=%s", case_names, case_row)) 171 | } 172 | 173 | build_description <- function(args, desc, .test_name, .interpret_glue) { 174 | if (.interpret_glue) { 175 | completed_desc <- tryCatch(glue_data(args, desc), error = identity) 176 | if (inherits(completed_desc, "error")) { 177 | abort(sprintf( 178 | paste( 179 | "Attempt to interpret test stub '%s' with glue failed with error:", 180 | "%s", "", 181 | "Set .interpret_glue=FALSE if this test name does not use glue.", 182 | sep = "\n" 183 | ), 184 | # indent for clarity (the purrr error has similar mark-up) 185 | desc, gsub("(^|\n)", "\\1 ", conditionMessage(completed_desc)) 186 | )) 187 | } 188 | } else { 189 | completed_desc <- desc 190 | } 191 | desc_n <- length(completed_desc) 192 | if (desc_n != 1L || completed_desc == desc) { 193 | completed_desc <- paste(desc, .test_name) 194 | if (desc_n != 1L) { 195 | warn( 196 | paste("glue_data() on desc= produced output of length", desc_n) 197 | ) 198 | } else if (.interpret_glue) { 199 | completed_desc <- glue_data(args, completed_desc) 200 | } 201 | } 202 | completed_desc 203 | } 204 | 205 | build_and_run_test <- function( 206 | ..., .test_name, desc, code, env, .interpret_glue 207 | ) { 208 | test_args <- list(..., .test_name = .test_name) 209 | completed_desc <- 210 | build_description(test_args, desc, .test_name, .interpret_glue) 211 | 212 | withCallingHandlers( 213 | test_that(completed_desc, eval_tidy(code, test_args)), 214 | testthat_braces_warning = cnd_muffle 215 | ) 216 | } 217 | 218 | #' @rdname with_parameters_test_that 219 | #' @export 220 | cases <- function(...) { 221 | all_cases <- list(...) 222 | nested <- modify_depth(all_cases, 2L, list) 223 | bind_rows( 224 | nested, 225 | .id = if (!is.null(names(nested))) ".test_name" 226 | ) 227 | } 228 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------