From Document to Code: Unit Tests Derived from Spec Driven Design
Introduction
Software development has evolved towards increasingly precise practices. Among them, Spec Driven Design (SDD) stands out by placing specification at the center of development. When combined with unit testing, SDD creates a robust cycle: first, the expected behavior is defined; then, 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 Foundation of Development
The specification describes the expected behavior of the system — inputs, outputs, rules, constraints, and exceptions. It eliminates subjective interpretations and serves as the single reference for development, testing, and validation. According to Hansen et al. (2015), a good specification is clear, concise, and testable, allowing anyone, technical or not, to understand what the software is supposed to do.
Why is specification critical?
Without specification, each team member creates their own interpretation of the problem. This leads to rework, misalignment, and inconsistencies. When the specification is well-defined before development, the team eliminates noise and accelerates deliveries — exactly what agile methodologies seek: to iterate quickly without losing quality.
Complete SDD Example: From Specification to Test
Next, a practical example of how Spec Driven Design guides the construction of tests and code. The case is simple, but it shows the end-to-end process.
Specification (SDD)
Context: financial calculation module.
# Specification — Business Rule: Progressive Discount Calculation
## Description
The system must 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), apply 5%.
- If the amount is greater than 500, apply 10%.
## 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 discountfunc 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:
- Specify the behavior.
- Derive the unit tests.
- Implement only what is necessary to meet the tests.
- Refactor safely, knowing that the contract is protected.
This flow reduces ambiguities and ensures that the final software matches the documented needs exactly.
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 safety net for evolutionary changes. When modifying the code, the developer immediately knows if they violated any specification rules. 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. Beck (2003) emphasizes that “tests are the best documentation.” 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 respected. This reduces subjective discussions and increases delivery speed.
SDD within agile practices
Teams following SDD can integrate automated tests in 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 must be updated whenever the system's behavior changes. Additionally, the team must balance test coverage with domain complexity. However, when used correctly, SDD reduces rework, increases quality, and makes development more predictable.
References
BECK, K. Test Driven Development: By Example. Addison-Wesley, 2003.
HANSEN, J. et al. Improving Software Quality through Specification and Testing. In: Proceedings of the International Conference on Software Engineering. IEEE, 2015.
HUMPHREY, W. S. Managing the Software Process. Addison-Wesley, 1989.
LEFF, A. et al. Spec Driven Development: A New Approach to Software Development. ACM Transactions on Software Engineering and Methodology, 2017.