Testing is the foundation of reliable software. When applied well, they reduce bugs, increase delivery speed and give confidence to refactor. Two popular paradigms are Test-Driven Development (TDD) and Behavior-Driven Development (BDD). Although complementary, they have different focuses.
When to use TDD vs BDD
- TDD: focus on code units (functions, classes). You write the test before implementation, ensuring that the public API behaves as expected.
- BDD: focus on high-level behavior (user flows, requirements). Uses almost natural language (Gherkin) to describe scenarios.
Rule of thumb: use TDD for business logic and internal libraries; use BDD for UI flows and integrations.
Recommended tools
| Layer | Tool | Why use |
|---|---|---|
| Unitary (JS/TS) | Jest | Fast, snapshot testing, integrated coverage |
| Unitary (Node) | Mocha + Chai | Flexible, good integration with Sinon |
| UI (React) | Testing Library | Test the UI as the user sees it |
| End-to-end | Cypress | Real browser testing, visual debugging |
| BDD | Cucumber.js | Gherkin syntax, Jest/Cypress integration |
TDD flow step by step
- Write the test that fails (red).
- Implement minimal code to pass (green).
- Refactor keeping the tests green (refactor).
- Repeat.
Practical example, Price formatting function
// priceFormatter.test.ts (Jest) import { formatPrice } from './priceFormatter'; test('formata número como moeda BRL', () => { expect(formatPrice(1234.5)).toBe('R$ 1.234,50'); });
// priceFormatter.ts (implementação mínima) export function formatPrice(value: number): string { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); }
BDD flow with Cucumber.js
Scenario definition (Gherkin)
Feature: Cadastro de Usuário As a visitor I want to create an account So that I can access protected features Scenario: Cadastro bem-sucedido Given I am on the registration page When I fill the form with valid data And I submit the form Then I should see a confirmation message And I receive a verification email
Implementation of steps (Cypress + Cucumber)
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; Given('I am on the registration page', () => { cy.visit('/register'); }); When('I fill the form with valid data', () => { cy.get('#email').type('usuario@example.com'); cy.get('#password').type('SenhaForte123!'); cy.get('#confirmPassword').type('SenhaForte123!'); }); When('I submit the form', () => { cy.get('form').submit(); }); Then('I should see a confirmation message', () => { cy.contains('Cadastro concluído').should('be.visible'); });
General good practices
- Keep tests fast: if a test takes more than 500ms, it is probably doing unnecessary I/O.
- Isolation: use mocks/stubs for external dependencies (APIs, banks).
- Minimum coverage: 80% line coverage is a good starting point, but prioritize critical logic.
- Continuous integration: configure the pipeline (GitHub Actions, GitLab CI) to run
npm teston each PR. - Visual feedback: use IDE plugins that show real-time test results.
Implementation checklist
- Choose testing framework (Jest, Cypress, etc.)
- Configure
jest.config.jsandcypress.json - Create directories
tests/unitandtests/e2e - Write first failed test for each new feature
- Integrate tests into the CI pipeline
- Monitor coverage and fallback failures
Conclusion
TDD and BDD are not just techniques, they are mindset changes. When adopted correctly, they ensure that your code evolves without fear of breaking existing functionality. Start small, write clear tests, and let them guide your development.
What is your experience with TDD or BDD? Share in the comments!
Also read
- Automated testing: why untested code is debt
- Automated test architecture: a quick guide for teams that need speed
- Automated test architecture: the essential steps to set up from scratch
- Software testing cycle: trends and a quick guide for leaders
- Automated Testing: Architecture and Fundamentals
- Stress Tests - Business Models in Practice
