The Importance of Testing in Software Development: A Regression Example in Dart

The Importance of Testing in Software Development: A Regression Example in Dart

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));
    });
  });
}
        

  • Test 1: Ensures that orders over $100 have free shipping.
  • Test 2: Ensures standard shipping for orders under $100.
  • Test 3: Tests that express shipping costs $20.

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.

要查看或添加评论,请登录

Makrem Ltifi的更多文章

  • Understanding Flutter's Build Modes: Debug, Profile, and Release

    Understanding Flutter's Build Modes: Debug, Profile, and Release

    Understanding the different build modes is essential for optimizing the performance, testing, and final deployment of…

  • Is MongoDB Safe? Understanding the Risks and Solutions

    Is MongoDB Safe? Understanding the Risks and Solutions

    In today's digital age, securing your database is more critical than ever. MongoDB, one of the most popular NoSQL…

  • Handling Null Safety in Dart

    Handling Null Safety in Dart

    Null safety in Dart is an important feature that ensures variables are explicitly handled as nullable or non-nullable…

  • Flutter: Boost Efficiency with MVVM and GetX

    Flutter: Boost Efficiency with MVVM and GetX

    Introduction Flutter has rapidly gained popularity for its ability to create beautiful, natively compiled applications…

  • Simplify Code and Minimize Errors with Enumerations

    Simplify Code and Minimize Errors with Enumerations

    Introduction: In programming, clarity and reliability are crucial. One powerful but often overlooked tool for achieving…

  • Variable Types in Dart

    Variable Types in Dart

    Choosing the right variable type is essential for writing clean, efficient, and maintainable Dart code. Let's delve…

  • Hot Restart in Flutter: Not Quite JIT, Not Quite AOT

    Hot Restart in Flutter: Not Quite JIT, Not Quite AOT

    Hot reload and Hot restart are two potent tools in Flutter that accelerate the development workflow, though they work…

  • Flutter's Three Trees

    Flutter's Three Trees

    In Flutter, "trees" hold a special significance. They are the invisible structures that bring your app's user interface…

社区洞察

其他会员也浏览了