Welcome to your second lesson in this course dedicated to practicing Test Driven Development (TDD) utilizing Swift and XCTest. In this unit, we will continue adding features to our calculateDiscount
function. We have five more requirements for you to implement!
In this course, emphasis is placed on hands-on practice, where you'll receive requirements through tests, one at a time. Your task is to implement code that makes each test pass, simulating a real-world TDD environment. As the course guide, it's akin to being your pair programmer, providing test-driven prompts to hone your skills as you progress.
As a reminder, the Red-Green-Refactor cycle is central to TDD, guiding your development process:
- Red: Start by writing a failing test to define the next step.
- Green: Implement just enough code to pass the test, focusing on functionality.
- Refactor: Optimize the code for clarity and efficiency without changing its behavior.
- Description: The function should validate that the price is a numeric value and raise an error if a non-numeric input is provided.
- Test Case:
Swift
1func testThrowErrorForNonNumericPriceInputs() { 2 // Arrange 3 let originalPrice: Any = "invalid" 4 let discountPercentage = 10.0 5 6 // Act, Assert 7 XCTAssertThrowsError(try calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage)) { error in 8 XCTAssertEqual(error as? DiscountError, DiscountError.invalidPrice) 9 } 10}
Swift is a statically typed language, so passing a string to a function expecting a Double
would typically cause a compile-time error. To simulate this scenario, the calculateDiscount
function must accept inputs of type Any
or a protocol like Numeric?
, then explicitly check the type at runtime and throw errors if the type is invalid. This pattern simulates validation logic found in loosely typed inputs such as user-entered data or external APIs.
- Description: The function should validate that the discount percentage is a numeric value and raise an error if a non-numeric input is provided.
- Test Case:
Swift
1func testThrowErrorForNonNumericDiscountInputs() { 2 // Arrange 3 let originalPrice = 100.0 4 let discountPercentage: Any = "invalid" 5 6 // Act, Assert 7 XCTAssertThrowsError(try calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage)) { error in 8 XCTAssertEqual(error as? DiscountError, DiscountError.invalidDiscount) 9 } 10}
Validating inputs at the start of the function prevents unnecessary computation and avoids cascading failures. In production systems, this protects your business logic from corrupted or unexpected external data - especially important when integrating with user-facing components like forms or payment systems where input format isn't guaranteed.
- Description: The function should ensure that very small prices do not fall below a defined minimum value after discounts are applied.
- Test Case:
Swift
1func testHandleVerySmallPricesCorrectly() { 2 // Arrange 3 let originalPrice = 0.001 4 let discountPercentage = 1.0 5 let expectedDiscountedPrice = 0.01 // Should not go below 0.01 6 7 // Act 8 let result = try? calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage) 9 10 // Assert 11 XCTAssertEqual(result, expectedDiscountedPrice) 12}
- Description: The function should apply a minimum discount percentage of 1% if a lower percentage is provided.
- Test Case:
Swift
1func testApplyMinimumDiscountAmountWhenDiscountIsLessThanMinimum() { 2 // Arrange 3 let originalPrice = 100.0 4 let discountPercentage = 0.1 // 0.1% discount 5 let expectedDiscountedPrice = 99.0 // Should apply minimum 1% discount 6 7 // Act 8 let result = try? calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage) 9 10 // Assert 11 XCTAssertEqual(result, expectedDiscountedPrice) 12}
- Description: The function should cap the discount at a maximum dollar value of $500, even if the calculated discount would exceed that cap.
- Test Case:
Swift
1func testCapMaximumDiscountAmountAt500() { 2 // Arrange 3 let originalPrice = 2500.0 4 let discountPercentage = 30.0 // Would normally be $750 off 5 let expectedDiscountedPrice = 2000.0 // Should only apply $500 maximum discount 6 7 // Act 8 let result = try? calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage) 9 10 // Assert 11 XCTAssertEqual(result, expectedDiscountedPrice) 12}
In this section, you practiced implementing and testing advanced requirements for the calculateDiscount
function. These included handling invalid, non-numeric inputs; managing very small prices to ensure minimum price thresholds are met, and applying specific discount constraints, such as enforcing a minimum discount percentage and capping the maximum allowable discount amount.
As you proceed with the practice exercises, keep the Red-Green-Refactor cycle in mind to guide your process. In this unit:
- Each test prompts you to refine the function's robustness, beginning with the Red phase by writing tests for additional edge cases.
- In the Green phase, aim to pass each test by incrementally adding necessary logic, such as input validation and boundary enforcement.
- The Refactor phase offers the chance to simplify and optimize your code after passing all tests, ensuring that it is not only correct but also clear and maintainable.
These exercises deepen your TDD skills by applying practical constraints and validations, simulating real-world requirements where robustness and reliability are essential.
