Complete TDD Example
This tutorial walks through building a complete PriceCalculator class using TDD with TestLink. You'll see the full workflow from start to finish.
What We're Building
A PriceCalculator that:
- Calculates base price from quantity and unit price
- Applies discounts based on user tier
- Adds tax based on region
- Returns a final total
Project Setup
Ensure you have TestLink installed:
bash
composer require testflowlabs/test-attributes
composer require --dev testflowlabs/testlinkPart 1: Base Price Calculation
Red Phase
Create the test file:
php
<?php
// tests/PriceCalculatorTest.php
use App\Pricing\PriceCalculator;
describe('PriceCalculator', function () {
describe('calculateBasePrice', function () {
test('multiplies quantity by unit price', function () {
$calculator = new PriceCalculator();
$result = $calculator->calculateBasePrice(
quantity: 5,
unitPrice: 100
);
expect($result)->toBe(500);
})->linksAndCovers(PriceCalculator::class.'::calculateBasePrice');
test('returns zero for zero quantity', function () {
$calculator = new PriceCalculator();
$result = $calculator->calculateBasePrice(
quantity: 0,
unitPrice: 100
);
expect($result)->toBe(0);
})->linksAndCovers(PriceCalculator::class.'::calculateBasePrice');
});
});Green Phase
php
<?php
// src/Pricing/PriceCalculator.php
namespace App\Pricing;
use TestFlowLabs\TestingAttributes\TestedBy;
class PriceCalculator
{
#[TestedBy('Tests\PriceCalculatorTest', 'multiplies quantity by unit price')]
#[TestedBy('Tests\PriceCalculatorTest', 'returns zero for zero quantity')]
public function calculateBasePrice(int $quantity, int $unitPrice): int
{
return $quantity * $unitPrice;
}
}Validate
bash
./vendor/bin/testlink validate
# ✓ All links are valid!Part 2: Discount Calculation
Red Phase
Add tests for discount calculation:
php
describe('applyDiscount', function () {
test('applies 20% discount for premium tier', function () {
$calculator = new PriceCalculator();
$result = $calculator->applyDiscount(
basePrice: 1000,
tier: 'premium'
);
expect($result)->toBe(800);
})->linksAndCovers(PriceCalculator::class.'::applyDiscount');
test('applies 10% discount for gold tier', function () {
$calculator = new PriceCalculator();
$result = $calculator->applyDiscount(
basePrice: 1000,
tier: 'gold'
);
expect($result)->toBe(900);
})->linksAndCovers(PriceCalculator::class.'::applyDiscount');
test('applies no discount for standard tier', function () {
$calculator = new PriceCalculator();
$result = $calculator->applyDiscount(
basePrice: 1000,
tier: 'standard'
);
expect($result)->toBe(1000);
})->linksAndCovers(PriceCalculator::class.'::applyDiscount');
});Green Phase
php
#[TestedBy('Tests\PriceCalculatorTest', 'applies 20% discount for premium tier')]
#[TestedBy('Tests\PriceCalculatorTest', 'applies 10% discount for gold tier')]
#[TestedBy('Tests\PriceCalculatorTest', 'applies no discount for standard tier')]
public function applyDiscount(int $basePrice, string $tier): int
{
$discountRate = match ($tier) {
'premium' => 0.20,
'gold' => 0.10,
default => 0.0,
};
return (int) ($basePrice * (1 - $discountRate));
}Part 3: Tax Calculation
Red Phase
php
describe('addTax', function () {
test('adds 8% tax for US region', function () {
$calculator = new PriceCalculator();
$result = $calculator->addTax(
price: 1000,
region: 'US'
);
expect($result)->toBe(1080);
})->linksAndCovers(PriceCalculator::class.'::addTax');
test('adds 20% tax for EU region', function () {
$calculator = new PriceCalculator();
$result = $calculator->addTax(
price: 1000,
region: 'EU'
);
expect($result)->toBe(1200);
})->linksAndCovers(PriceCalculator::class.'::addTax');
});Green Phase
php
#[TestedBy('Tests\PriceCalculatorTest', 'adds 8% tax for US region')]
#[TestedBy('Tests\PriceCalculatorTest', 'adds 20% tax for EU region')]
public function addTax(int $price, string $region): int
{
$taxRate = match ($region) {
'US' => 0.08,
'EU' => 0.20,
default => 0.0,
};
return (int) ($price * (1 + $taxRate));
}Part 4: Calculate Total
Red Phase
php
describe('calculateTotal', function () {
test('combines base price, discount, and tax', function () {
$calculator = new PriceCalculator();
// 5 items × $100 = $500 base
// Premium discount: $500 × 0.80 = $400
// US tax: $400 × 1.08 = $432
$result = $calculator->calculateTotal(
quantity: 5,
unitPrice: 100,
tier: 'premium',
region: 'US'
);
expect($result)->toBe(432);
})->linksAndCovers(PriceCalculator::class.'::calculateTotal');
test('handles standard tier with EU tax', function () {
$calculator = new PriceCalculator();
// 2 items × $50 = $100 base
// Standard: no discount = $100
// EU tax: $100 × 1.20 = $120
$result = $calculator->calculateTotal(
quantity: 2,
unitPrice: 50,
tier: 'standard',
region: 'EU'
);
expect($result)->toBe(120);
})->linksAndCovers(PriceCalculator::class.'::calculateTotal');
});Green Phase
php
#[TestedBy('Tests\PriceCalculatorTest', 'combines base price, discount, and tax')]
#[TestedBy('Tests\PriceCalculatorTest', 'handles standard tier with EU tax')]
public function calculateTotal(
int $quantity,
int $unitPrice,
string $tier,
string $region
): int {
$basePrice = $this->calculateBasePrice($quantity, $unitPrice);
$discountedPrice = $this->applyDiscount($basePrice, $tier);
return $this->addTax($discountedPrice, $region);
}Final Code
Here's the complete PriceCalculator:
php
<?php
namespace App\Pricing;
use TestFlowLabs\TestingAttributes\TestedBy;
class PriceCalculator
{
#[TestedBy('Tests\PriceCalculatorTest', 'multiplies quantity by unit price')]
#[TestedBy('Tests\PriceCalculatorTest', 'returns zero for zero quantity')]
public function calculateBasePrice(int $quantity, int $unitPrice): int
{
return $quantity * $unitPrice;
}
#[TestedBy('Tests\PriceCalculatorTest', 'applies 20% discount for premium tier')]
#[TestedBy('Tests\PriceCalculatorTest', 'applies 10% discount for gold tier')]
#[TestedBy('Tests\PriceCalculatorTest', 'applies no discount for standard tier')]
public function applyDiscount(int $basePrice, string $tier): int
{
$discountRate = match ($tier) {
'premium' => 0.20,
'gold' => 0.10,
default => 0.0,
};
return (int) ($basePrice * (1 - $discountRate));
}
#[TestedBy('Tests\PriceCalculatorTest', 'adds 8% tax for US region')]
#[TestedBy('Tests\PriceCalculatorTest', 'adds 20% tax for EU region')]
public function addTax(int $price, string $region): int
{
$taxRate = match ($region) {
'US' => 0.08,
'EU' => 0.20,
default => 0.0,
};
return (int) ($price * (1 + $taxRate));
}
#[TestedBy('Tests\PriceCalculatorTest', 'combines base price, discount, and tax')]
#[TestedBy('Tests\PriceCalculatorTest', 'handles standard tier with EU tax')]
public function calculateTotal(
int $quantity,
int $unitPrice,
string $tier,
string $region
): int {
$basePrice = $this->calculateBasePrice($quantity, $unitPrice);
$discountedPrice = $this->applyDiscount($basePrice, $tier);
return $this->addTax($discountedPrice, $region);
}
}Final Validation
bash
./vendor/bin/testlink validateValidation Report
─────────────────
Link Summary
PHPUnit attribute links: 9
Pest method chain links: 0
Total links: 9
TestedBy Summary
TestedBy attributes found: 9
Synchronized: 9
✓ All links are valid!Final Report
bash
./vendor/bin/testlink reportCoverage Links Report
─────────────────────
App\Pricing\PriceCalculator
calculateBasePrice()
→ Tests\PriceCalculatorTest::multiplies quantity by unit price
→ Tests\PriceCalculatorTest::returns zero for zero quantity
applyDiscount()
→ Tests\PriceCalculatorTest::applies 20% discount for premium tier
→ Tests\PriceCalculatorTest::applies 10% discount for gold tier
→ Tests\PriceCalculatorTest::applies no discount for standard tier
addTax()
→ Tests\PriceCalculatorTest::adds 8% tax for US region
→ Tests\PriceCalculatorTest::adds 20% tax for EU region
calculateTotal()
→ Tests\PriceCalculatorTest::combines base price, discount, and tax
→ Tests\PriceCalculatorTest::handles standard tier with EU tax
Summary
Methods with tests: 4
Total test links: 9What You Learned
- TDD rhythm - Red → Green → Refactor with links at each phase
- Incremental development - Build features piece by piece
- Link management - Keep production and test links synchronized
- Validation - Verify link integrity throughout development
What's Next?
- BDD Workflow - Learn behavior-driven development with TestLink
- Run Validation in CI - Automate link validation