Home > Web Front-end > JS Tutorial > Test-Driven Development (TDD) with Bun Test

Test-Driven Development (TDD) with Bun Test

Patricia Arquette
Release: 2024-12-29 17:15:15
Original
974 people have browsed it

Test-Driven Development (TDD) is a powerful methodology for writing clean, bug-free code. In this article, we’ll explore how to implement TDD using Bun’s built-in test runner, Bun Test, which is known for its speed and simplicity.

What is TDD?

Test-driven development (TDD) is a software development practice in which tests are written before the code. The TDD practice guides the implementation and ensures functionality through iterative writing, testing, and refactoring cycles.

TDD is a development process that follows these steps:

  • Write a test for the desired functionality.
  • Define all the testing scenarios you want to cover.
  • Run the test and verify that it fails (as the functionality might be incomplete or not cover all scenarios yet).
  • Update and refactor the code to make the test pass while ensuring all tests succeed.

This iterative process is designed to produce robust and well-tested code.

Setting up your JavaScript project with Bun

If you haven’t installed Bun, install it by following the instructions at the Bun JavaScript documentation.
Then, initialize a new project:

bun init
Copy after login
Copy after login
Copy after login
➜  example bun init
bun init helps you get started with a minimal project and tries to guess sensible defaults. Press ^C anytime to quit

package name (example):
entry point (index.ts):

Done! A package.json file was saved in the current directory.
Copy after login
Copy after login
Copy after login

Create a test file in the tests directory (e.g., tests/example.test.js). Bun automatically detects files ending with .test.ts or .test.js for testing.

mkdir tests
touch tests/example.test.js
Copy after login
Copy after login
Copy after login

Writing your first test

Let’s start with a simple example.
We’ll create a calculator file to implement some mathematical functions.
We’ll first focus on a simple function, like sum(), even though JavaScript already has a native addition operator. This allows us to concentrate on structuring the tests rather than the complexity of the logic.
Here’s the plan:

  • Create a calculator.ts file where we’ll define a sum() function that initially returns 0.
  • Write tests for the sum() function, covering several test cases.
  • Run the tests and confirm that they fail.
  • Update the logic of the sum() function to make the tests pass.
  • Rerun the tests to ensure our implementation is correct.

Create your calculator.test.js file

In the tests/calculator.test.js file, you can implement your tests:

import { describe, expect, it } from "bun:test";
import { sum } from "../calculator";

describe("sum function", () => {
  it("should return the sum of two numbers (both are positive)", () => {
    expect(sum(2, 3)).toBe(5);
  });
  it("should return the sum of two numbers (one is negative)", () => {
    expect(sum(-1, 2)).toBe(1);
  });
});
Copy after login
Copy after login
Copy after login

These tests verify the behavior of the sum() function, defined in the calculator module. The tests are written using Bun's testing library and organized within a describe block named "sum function". The describe() block helps to group "similar" tests. Each it() block specifies a particular scenario to test. Here's what each test does:

  1. Test: Adding two positive numbers
    • Description: "should return the sum of two numbers (both are positive)"
    • This test checks if the sum function correctly calculates the sum of two positive integers.
    • Example: sum(2, 3) is expected to return 5.
  2. Test: Adding a negative and a positive number
    • Description: "should return the sum of two numbers (one is negative)"
    • This test validates that the sum function correctly handles a scenario where one number is negative.
    • Example: sum(-1, 2) is expected to return 1.

These tests ensure that the sum function behaves as expected for basic addition scenarios, covering both positive numbers and mixed (positive and negative) inputs.

Create your calculator.ts file

Now, you can create your calculator module that will export the sum() function.
In the calculator.ts file:

bun init
Copy after login
Copy after login
Copy after login

The first version of the function returns a hardcoded value, so I expect the tests to fail.
Running the tests:

➜  example bun init
bun init helps you get started with a minimal project and tries to guess sensible defaults. Press ^C anytime to quit

package name (example):
entry point (index.ts):

Done! A package.json file was saved in the current directory.
Copy after login
Copy after login
Copy after login

Test-Driven Development (TDD) with Bun Test

Now we can adjust the logic of the sum() function in the calculator.ts adjust the logic of the sum() function:

mkdir tests
touch tests/example.test.js
Copy after login
Copy after login
Copy after login

Now, if you run the tests, you will have a "green" ✅ status.

Test-Driven Development (TDD) with Bun Test

Refactoring tests with dataset

If you want to run the same tests with different scenarios (input values), you can use the each() method.

import { describe, expect, it } from "bun:test";
import { sum } from "../calculator";

describe("sum function", () => {
  it("should return the sum of two numbers (both are positive)", () => {
    expect(sum(2, 3)).toBe(5);
  });
  it("should return the sum of two numbers (one is negative)", () => {
    expect(sum(-1, 2)).toBe(1);
  });
});
Copy after login
Copy after login
Copy after login

Using a dataset-driven approach, this code tests the sum function from the calculator module. The it.each() method is employed to simplify repetitive test cases by iterating over a dataset of inputs and expected outputs. Here's a breakdown of how it works:

First, you can define a dataset

export function sum(a: number, b: number) {
  // Function yet to be implemented
  return 0;
}
Copy after login

The dataset is an array of arrays. Each inner array represents a test case, where the elements correspond to:

  • a (first number to add),
  • b (second number to add),
  • expected (the expected result of sum(a, b)).

The describe function groups all tests related to the sum function under a single block for better organization.

In the describe() block, it.each(dataset) iterates over each row in the dataset array.
"Sum of %d and %d should be %d" is a description template for the test, where %d is replaced with the actual numbers from the dataset during each iteration.
For example, the first iteration generates the description: "Sum of 2 and 3 should be 5".

In the callback function (a, b, expected), the elements of each row in the dataset are destructured into variables: a, b, and expected. Then, inside the test, the sum function is called with a and b, and the result is checked using expect() to ensure it matches the expected.

Why use it.each() (or test.each())?

  • Efficiency: instead of writing separate it() or test() blocks for each case, you can define all test cases in a single dataset and loop through them.
  • Readability: the test logic is concise, and the dataset makes adding or modifying test cases easy without duplicating code.
  • Scalability: useful when dealing with multiple test cases, especially when the logic being tested is similar across cases.

Another practical example: calculating the mean

To show an additional example for TDD, let’s implement a mean function in the calculator module that calculates the mean (average) of an array of numbers. Following the TDD approach, we’ll start by writing the tests.

In the already existent calculator.test.js add these tests specific for mean() function:

bun init
Copy after login
Copy after login
Copy after login

Now in the calculator.ts file, add the mean() function:

➜  example bun init
bun init helps you get started with a minimal project and tries to guess sensible defaults. Press ^C anytime to quit

package name (example):
entry point (index.ts):

Done! A package.json file was saved in the current directory.
Copy after login
Copy after login
Copy after login

So now you can execute again the tests

mkdir tests
touch tests/example.test.js
Copy after login
Copy after login
Copy after login

Test-Driven Development (TDD) with Bun Test

All the tests should pass.
In this case, the implementation is already tested, so no further refactoring is needed. However, always take the time to review your code for improvements.

Test coverage

Test coverage is a metric that measures the percentage of your codebase executed during automated tests. It provides insights into how well your tests validate your code.
The Bun test coverage helps to identify the "line coverage".
The line coverage checks whether each line of code is executed during the test suite.

Running the test coverage:

import { describe, expect, it } from "bun:test";
import { sum } from "../calculator";

describe("sum function", () => {
  it("should return the sum of two numbers (both are positive)", () => {
    expect(sum(2, 3)).toBe(5);
  });
  it("should return the sum of two numbers (one is negative)", () => {
    expect(sum(-1, 2)).toBe(1);
  });
});
Copy after login
Copy after login
Copy after login

Test-Driven Development (TDD) with Bun Test

Why is Coverage Important?

  • Identifying gaps in tests: coverage reports highlight which parts of your code are not tested. This helps you ensure critical logic isn’t overlooked.
  • Improving code quality: high coverage ensures that edge cases, error handling, and business logic are tested thoroughly, reducing the likelihood of bugs.
  • Confidence in refactoring: if you have a well-tested codebase, you can refactor with confidence, knowing your tests will catch regressions.
  • Better maintenance: a codebase with high test coverage is easier to maintain, as you can detect unintended changes or side effects during updates.
  • Supports TDD: for developers practicing Test-Driven Development, monitoring coverage ensures the tests align with implementation.

Balancing coverage goals

While high test coverage is important, it's not the only measure of code quality. Aim for meaningful tests focusing on functionality, edge cases, and critical parts of your application. Achieving 100% coverage is ideal, but not at the cost of writing unnecessary or trivial tests.

Conclusion

Test-Driven Development (TDD) with Bun Test empowers developers to write clean, maintainable, and robust code by focusing on requirements first and ensuring functionality through iterative testing. By leveraging Bun's fast and efficient testing tools, you can streamline your development process and confidently handle edge cases. Adopting TDD not only improves code quality but also fosters a mindset of writing testable, modular code from the start. Start small, iterate often, and let your tests guide your implementation.

The above is the detailed content of Test-Driven Development (TDD) with Bun Test. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template