Hero background

Building Quality By Testing

I’m Jakub and testing helps me build better software.
Join me to unlock the power of testing.

My Open Source

1. Define behavior of the system

Write scenarios in Gherkin syntax to describe the behavior of your system.

Use Given, When, Then keywords to shape your specifications clearly.

Feature: Sales Trends
Scenario: User views the sales trend for a product category
  Given the user can view product sales information
  When they view the sales trend for "Electronics"
  Then they should see the sales trend plot for "Electronics"

Scenario: User views the sales trend for a specific product
  Given the user can view product sales information
  When they view the sales trend for "Smartphone"
  Then they should see the sales trend plot for "Smartphone"

2. Implement steps in R

Write implmementaion for steps in scenarios as R functions.

Whether it's a package, a Shiny app, or an API – simulate Users interactions with your code.

library(cucumber)

given("the user can view product sales information", function(context) {
# View product sales information
)}

when("they view the sales trend for {string}", function(product, context) {
# View the sales trend for the specified product
})

then("they should see the sales trend plot for {string}", function(product, context) {
# Verify the sales trend plot is displayed for the specified product
})

3. Verify User behaviors

Don't ever miss when Users can no longer interact with your code the way you specified.

Run your scenarios and verify the programmed behaviors are still valid.

> cucumber::test()
#> ✔ | F W  S  OK | Context
#> ✔ |          2 | Feature: Sales Trends
#>
#> ══ Results ═══════════════════════════════════════════
#> [ FAIL 0 | WARN 0 | SKIP 0 | PASS 2 ]

1. Create a test plan

Define which source files should be mutated and how.

Plan tests for all files or only for files you're working on. You're in control of the scope of your testing.

plan <- muttest::plan(
  source_files = "R/calculate.R",
  mutators = list(
    muttest::operator("+", "-"),
    muttest::operator("-", "+"),
    muttest::operator("*", "/"),
    muttest::operator("/", "*"),
    muttest::operator("==", "!="),
    muttest::operator("!=", "==")
  )
)

2. Run mutation testing

Get a score of how often your tests catch mutations (bugs) in your code.

  • 0% score → Your tests pass no matter what changes. Your assertions are weak.
  • 100% score → Every mutation triggers a test failure. Your tests are robust.
> muttest::muttest(plan)
#> ℹ Mutation Testing
#>   |   K |   S |   E |   T |   % | Mutator  | File
#> x |   0 |   1 |   0 |   1 |   0 | + → -    | calculate.R
#> ✔ |   1 |   1 |   0 |   2 |  50 | * → /    | calculate.R
#> ── Mutation Testing Results ────────────────────────────────────────────────────
#> [ KILLED 1 | SURVIVED 1 | ERRORS 0 | TOTAL 2 | SCORE 50.0% ]

3. Improve your tests

See which source files needs better tests.

Patch the gaps that won't be caught by your tests or code coverage.

test_that("calculate returns a numeric", { 
  expect_true(is.numeric(calculate(2, 2))) 
test_that("calculate always returns 0", { 
  expect_equal(calculate(2, 2), 0) 
})

Tutorials

# tests/acceptance/test-budget.R
test_that("Scenario: I can inspect my net balance", {
  # Given
  dsl$record_income(2000)
  dsl$record_expense(500)
  # When
  dsl$inspect_finances()
  # Then
  dsl$verify_total_income(2000)
  dsl$verify_total_expenses(500)
  dsl$verify_net_balance(1500)
  dsl$teardown()
})

Start with user stories
01

Transform vague requirements into clear, testable user stories that define what users want to accomplish.

Executable specifications
02

Turn user scenarios into working code that validates your application behaves as expected.

Domain-specific language
03

Create a testing vocabulary that bridges technical implementation with business language.

UI independence
04

Focus on behavior, not implementation details, allowing your UI to evolve without breaking tests.

Shiny best practices
05

Learn practical techniques for building more maintainable, testable Shiny applications.

Test inside-out or outside-in
01

Learn different testing strategies and when to apply them for maximum effectiveness in Shiny apps.

Automate acceptance criteria
02

Convert business requirements into automated tests that validate your application meets user needs.

Isolate Shiny modules
03

Test each Shiny module independently to ensure they function correctly before integration.

Inject fake dependencies
04

Use dependency injection to replace external services with test doubles for faster, more reliable tests.

Structured testing approach
05

Apply a systematic testing methodology that improves code quality and development speed.

Learn BDD fundamentals
01

Understand the core principles of Behavior-Driven Development and why it improves software quality.

From Vague Wish to Working Code
02

Cooperate with stakeholders to transform vague requirements into clear, testable scenarios.

Use Given-When-Then
03

Master the Given-When-Then syntax to write scenarios that describe system behavior in a structured way.

Use Cucumber
04

Execute specifications using an R implementation of Cucumber.

Hero background
Jakub Sobolewski

I'm Jakub Sobolewski

I’m a software engineer specializing in R with 5+ years of experience.

I believe automated testing is the key to building quality software.

My journey into R testing began with a project where to develop code, you had to be connected to the production environment. Turns out, it was a terrible developer experience.

I'm particularly passionate about knowledge sharing, which is why I maintain an active blog and R Tests Gallery. I believe that when we share our testing experiences—both successes and failures—we all become better developers.

I approach testing with a practical mindset: tests should make development faster and more confident, not slower and more burdensome. My goal is to help teams find testing strategies that actually enhance their workflow.