Leaders Logo

From Document to Code: Unit Tests Derived from Spec Driven Design

Introduction

Software development has evolved into practices increasingly focused on precision. Among them, Spec Driven Design (SDD) stands out by placing the specification at the center of development. When combined with unit tests, SDD creates a robust cycle: first, the expected behavior is defined; then, the tests ensure that the implementation follows this contract exactly. This article presents how Spec Driven Design works in practice and how it guides the creation of unit tests in C# and Go (Golang).

Specification as the Basis of Development

The specification defines the expected behavior of the system by explicitly describing its inputs, outputs, business rules, constraints, and exceptional scenarios. Acting as a single source of truth, it reduces subjective interpretations and provides a consistent reference for development, testing, and validation activities. A well-crafted specification is clear, concise, and testable, allowing both technical and non-technical audiences to accurately understand what the software should accomplish (GOLDMAN; ABRAHAM; SONG, 2007).

Why is the specification important?

In the absence of an explicit specification, each team member tends to form their own interpretation of the problem, which often results in rework, misalignment, and inconsistencies in implementation. The literature on specification-driven development highlights that the prior formalization of the system's behavior is one of the main mechanisms to mitigate ambiguities and prevent errors early in the development cycle (FOFUNG, 2015).

When the specification is defined before implementation, it can be directly integrated into the testing process. Studies show that combining formal specifications with practices like Test-Driven Development (TDD) expands scenario coverage, improves requirement traceability, and generates more accurate documentation artifacts, complementing traditional unit tests (BAUMEISTER, 2004).

Complete Example of SDD: From Specification to Test

Test-oriented SDD flow

The image below represents the flow of Spec Driven Design (SDD) applied to test-driven development, highlighting how the specification guides the creation of unit tests and, subsequently, the implementation of the code.

SVG Image of the Article

Next, a practical example of how Spec Driven Design guides the construction of tests and code. The case is simple but shows the end-to-end process.

Specification (SDD)

Context: financial calculation module.

# Specification, Business Rule: Progressive Discount Calculation

## Description
The system should apply a discount based on the total purchase amount.

## Rules
- If the amount is less than 100, no discount is applied.
- If the amount is between 100 and 500 (inclusive), a 5% discount is applied.
- If the amount is greater than 500, a 10% discount is applied.

## Formula
final_price = amount - (amount * percentage)

## Test Cases Derived from the Specification
1. amount = 50 ? final_price = 50
2. amount = 100 ? final_price = 95
3. amount = 500 ? final_price = 475
4. amount = 800 ? final_price = 720

Note: the tests come directly from the specification. There is no room for interpretation. The test has become a contract.

Tests in C# (derived from the specification)

using Xunit;

public class DiscountService
{
    public decimal Calculate(decimal value)
    {
        if (value < 100)
            return value;

        if (value <= 500)
            return value * 0.95m;

        return value * 0.90m;
    }
}

public class DiscountServiceTests
{
    [Theory]
    [InlineData(50, 50)]
    [InlineData(100, 95)]
    [InlineData(500, 475)]
    [InlineData(800, 720)]
    public void Calculate_ShouldApplyCorrectDiscount(decimal input, decimal expected)
    {
        var service = new DiscountService();

        var result = service.Calculate(input);

        Assert.Equal(expected, result);
    }
}

Tests in Go (derived from the specification)

package discount

func Calculate(value float64) float64 {
	if value < 100 {
		return value
	}

	if value <= 500 {
		return value * 0.95
	}

	return value * 0.90
}
package discount_test

import "testing"
import "discount"

func TestCalculate(t *testing.T) {
    cases := []struct{
        input float64
        want  float64
    }{
        {50, 50},
        {100, 95},
        {500, 475},
        {800, 720},
    }

    for _, c := range cases {
        got := discount.Calculate(c.input)
        if got != c.want {
            t.Errorf("Calculate(%f) = %f; want %f", c.input, got, c.want)
        }
    }
}

From Document to Code: The Flow of Spec Driven Design

In SDD, the process is straightforward:

  1. Specify the behavior.
  2. Derive the unit tests.
  3. Implement only what is necessary to meet the tests.
  4. Refactor safely, knowing that the contract is protected.

This flow reduces ambiguities and ensures that the final software corresponds exactly to the documented needs.

Why does this reduce failures?

Because the implementation does not arise from “ideas” or “interpretations.” It arises from explicit rules. If the test fails, the implementation is wrong. If the test passes, the contract is met. Simple and direct.

Unit Tests and Code Quality

Unit tests act as a protective evolutionary net. When modifying the code, the developer immediately knows if they violated any rule of the specification. In agile environments, especially multi-product or with simultaneous squads, this confidence is mandatory.

Refactoring without fear

Refactoring means improving the internal structure without changing the external behavior. With tests derived from the specification, the rule is recorded in code and does not get lost over time.

Collaboration and Agility

SDD improves communication among developers, QA, PO, and stakeholders. The specification becomes the single source of truth. The tests ensure that it is being adhered to. This reduces subjective discussions and increases the speed of deliveries.

SDD within agile practices

Teams that follow SDD can integrate automated tests into CI/CD, delivering continuous value. Each change triggers the test suite, validating compliance with the expected behavior.

Challenges and Final Considerations

Applying SDD requires discipline. The specification needs to be updated whenever the system's behavior changes. Furthermore, the team must balance test coverage with domain complexity. However, when used correctly, SDD reduces rework, increases quality, and makes development more predictable.

References

  • GOLDMAN, James L.; ABRAHAM, George; SONG, IlYeol. Generating Software Requirements Specification (IEEE Std. 830 1998) document with Use Cases. no, v. 215, p. 1-12, 2007. reference.Description
  • FOFUNG, Titus. Desenvolvimento Orientado por Especificação Formal. 2015. reference.Description
  • BAUMEISTER, Hubert. Combining formal specifications with test driven development. In: Conference on Extreme Programming and Agile Methods. Berlin, Heidelberg: Springer Berlin Heidelberg, 2004. p. 1-12. reference.Description
About the author