The Importance of Testing in Software Development: A Regression Example in Dart
Makrem Ltifi
Software Engineer | Flutter, Angular & Node.js | Committed to Delivering Quality Solutions
In software development, ensuring that your code works as expected is only part of the battle. The real challenge comes when you start adding new features and improving your application, as these changes can sometimes break existing functionality. This is where testing shines—particularly regression testing, which ensures that your application continues to function properly after new changes are introduced.
In this article, we’ll demonstrate how a simple application can fail when new features are introduced, even if they seem harmless. We'll write tests for our initial code, add a new feature, and witness a regression. Then, we’ll fix the bug and write more tests to ensure our application is solid.
Use Case: E-commerce Shipping Calculation
We’ll build a simple Dart class to manage an e-commerce system that calculates shipping fees for orders. We'll add a feature to apply discounts on shipping fees, which will introduce a regression bug. Through this, we’ll highlight the value of tests in catching bugs before they go into production.
Step 1: Initial Code (Shipping Fee Calculation)
Let’s start with a simple class that calculates shipping fees based on order amount. Orders over $100 will have free shipping, and for others, a $10 fee is applied.
class Order {
final double totalPrice;
final bool isExpressShipping;
Order(this.totalPrice, {this.isExpressShipping = false});
}
class ShippingCalculator {
double calculateShipping(Order order) {
if (order.totalPrice > 100) {
return 0.0;
} else {
return order.isExpressShipping ? 20.0 : 10.0;
}
}
}
Here, if the total order price exceeds $100, the shipping is free. Otherwise, normal shipping is $10, and express shipping costs $20.
Step 2: Write Tests for the Initial Code
We’ll write tests to ensure the logic works as expected:
import 'package:flutter_test/flutter_test.dart';
import 'shipping_calculator.dart';
void main() {
group('ShippingCalculator Tests', () {
test('Free shipping for orders over 100', () {
final order = Order(150.0);
final calculator = ShippingCalculator();
expect(calculator.calculateShipping(order), equals(0.0));
});
test('Standard shipping for orders under 100', () {
final order = Order(50.0);
final calculator = ShippingCalculator();
expect(calculator.calculateShipping(order), equals(10.0));
});
test('Express shipping for orders under 100', () {
final order = Order(50.0, isExpressShipping: true);
final calculator = ShippingCalculator();
expect(calculator.calculateShipping(order), equals(20.0));
});
});
}
Step 3: Add a New Feature (Shipping Discount)
Now, we’ll add a new feature: shipping discounts. Users with a coupon can receive a discount on their shipping fees.
class ShippingCalculator {
double calculateShipping(Order order, {double discount = 0.0}) {
double shippingCost;
if (order.totalPrice > 100) {
shippingCost = 0.0;
} else {
shippingCost = order.isExpressShipping ? 20.0 : 10.0;
}
shippingCost -= discount;
return shippingCost;
}
}
This update adds a discount parameter (optional). However, we accidentally introduce a regression bug in our logic. The discount is applied before checking for free shipping, which leads to shipping fees being discounted even when they should be free.
领英推荐
Step 4: Regression Test (Catch the Bug)
Now, let’s modify our test cases to include shipping discounts and catch this regression.
void main() {
group('ShippingCalculator Tests', () {
test('Free shipping for orders over 100', () {
final order = Order(150.0);
final calculator = ShippingCalculator();
expect(calculator.calculateShipping(order), equals(0.0));
});
test('Standard shipping for orders under 100', () {
final order = Order(50.0);
final calculator = ShippingCalculator();
expect(calculator.calculateShipping(order), equals(10.0));
});
test('Express shipping for orders under 100', () {
final order = Order(50.0, isExpressShipping: true);
final calculator = ShippingCalculator();
expect(calculator.calculateShipping(order), equals(20.0));
});
test('Apply shipping discount on standard shipping', () {
final order = Order(50.0);
final calculator = ShippingCalculator();
expect(calculator.calculateShipping(order, discount: 5.0), equals(5.0));
});
test('Apply discount but shipping should remain free', () {
final order = Order(150.0);
final calculator = ShippingCalculator();
expect(calculator.calculateShipping(order, discount: 5.0), equals(0.0));
});
});
}
The test "Apply discount but shipping should remain free" will fail because the discount is incorrectly applied to the order, reducing the shipping fee below zero, even when it should already be free (the shipping fee will be -5).
Step 5: Fix the Bug
To fix this, we need to ensure that discounts are not applied when shipping is already free.
class ShippingCalculator {
double calculateShipping(Order order, {double discount = 0.0}) {
double shippingCost;
if (order.totalPrice > 100) {
return 0.0; // Free shipping for orders above 100
} else {
shippingCost = order.isExpressShipping ? 20.0 : 10.0;
// Apply discount
shippingCost -= discount;
}
return shippingCost < 0.0 ? 0.0 : shippingCost; // Shipping cannot be negative
}
}
Now, the test will pass because we correctly prevent the discount from being applied when shipping is free ( and ensured that the shipping cannot be negative ).
Step 6: Rerun the Tests
After the fix, running the tests will show that everything passes, including our test for the regression:
Conclusion
In this article, we explored how adding a new feature (shipping discounts) introduced a regression bug into our e-commerce system. By writing thorough tests, we were able to catch this bug before it caused real problems. This highlights the importance of writing tests not just to ensure that new features work but to protect existing features from breaking when the code evolves.
Tests provide a safety net, ensuring your application remains stable as you continue to enhance it. Always remember: Test often, test early, and test thoroughly.