Welcome to your first lesson in this course dedicated to practicing Test Driven Development (TDD) using Swift and XCTest. Test Driven Development is an effective approach that prioritizes writing tests before coding. This guides you to develop your application around testing, ensuring that each component functions correctly as intended. In this lesson, you'll learn the fundamentals of TDD and the Red-Green-Refactor cycle and understand their roles in creating consistent and maintainable code.
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.
In this unit, we will outline the test cases that guide the implementation of the calculateDiscount
function by employing the Red-Green-Refactor cycle of Test Driven Development (TDD). Each test case serves as a specific requirement, starting with writing a failing test ("Red") to define a behavior, followed by implementing minimal code changes to pass the test ("Green"), and finalizing with code optimization ("Refactor"). We will cover essential scenarios, such as applying correct percentage-based discounts, handling zero discount cases, managing decimal discount precision, and ensuring valid input handling to guarantee robustness and correctness.
- Description: The function must correctly apply a percentage-based discount to an original price.
- Test Case:
Swift
1func testApplyCorrectDiscountToPrice() { 2 // Arrange 3 let originalPrice: Double = 100 4 let discountPercentage: Double = 20 5 let expectedDiscountedPrice: Double = 80 6 7 // Act 8 let result = calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage) 9 10 // Assert 11 XCTAssertEqual(result, expectedDiscountedPrice) 12}
- Description: The function should return the original price when the discount percentage is 0.
- Test Case:
Swift
1func testReturnOriginalPriceWhenDiscountIsZero() { 2 // Arrange 3 let originalPrice: Double = 50 4 let discountPercentage: Double = 0 5 6 // Act 7 let result = calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage) 8 9 // Assert 10 XCTAssertEqual(result, originalPrice) 11}
- Description: The function must accurately handle decimal percentages in discount calculations and round the result to two decimal places.
- Test Case:
Swift
1func testHandleDecimalDiscountsCorrectly() { 2 // Arrange 3 let originalPrice: Double = 100 4 let discountPercentage: Double = 33.333 5 let expectedDiscountedPrice: Double = 66.67 6 7 // Act 8 let result = calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage) 9 10 // Assert 11 XCTAssertEqual(result, expectedDiscountedPrice, accuracy: 0.01) 12}
In XCTest, the accuracy
parameter in XCTAssertEqual
is used because floating-point arithmetic can result in tiny rounding errors. For instance, if you expect a result of 66.67
, but the actual result is 66.669999...
, specifying accuracy: 0.01
means the test will still pass as long as the difference between the expected and actual values is within 0.01
.
- Description: The function should not accept negative prices and must throw an error if encountered.
- Test Case:
Swift
1func testThrowErrorForNegativePrices() { 2 // Arrange 3 let originalPrice: Double = -50 4 let discountPercentage: Double = 10 5 6 // Act, Assert 7 XCTAssertThrowsError(try calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage)) { error in 8 XCTAssertEqual(error as? DiscountError, DiscountError.negativePrice) 9 } 10}
- Description: The function should not accept discount percentages greater than 100% and must throw an error in such cases.
- Test Case:
Swift
1func testThrowErrorForDiscountsGreaterThan100Percent() { 2 // Arrange 3 let originalPrice: Double = 100 4 let discountPercentage: Double = 110 5 6 // Act, Assert 7 XCTAssertThrowsError(try calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage)) { error in 8 XCTAssertEqual(error as? DiscountError, DiscountError.discountPercentageTooHigh) 9 } 10}
- Description: The function should not accept negative discount percentages and must throw an error in such cases.
- Test Case:
Swift
1func testThrowErrorForDiscountsLessThanZero() { 2 // Arrange 3 let originalPrice: Double = 100 4 let discountPercentage: Double = -10 5 6 // Act, Assert 7 XCTAssertThrowsError(try calculateDiscount(originalPrice: originalPrice, discountPercentage: discountPercentage)) { error in 8 XCTAssertEqual(error as? DiscountError, DiscountError.negativeDiscount) 9 } 10}
Looking ahead to the practice exercises, you will have the opportunity to ensure all tests pass while practicing the Red-Green-Refactor cycle. Your implementation may differ from the provided solutions, and that’s perfectly acceptable. When comparing your solution, don't just focus on whether the code looks the same - look at structure, error handling, naming conventions, and test coverage. Ask yourself: Is my version as readable and maintainable? Did I refactor unnecessary duplication? This reflective comparison builds stronger instincts for clean code. Each practice session will begin from a solution foundation, allowing you to compare your approach with the guided solution and develop your features to ensure test success.
As you undertake these exercises, remember to engage in the Red-Green-Refactor cycle. Each test provided serves as the "Red" phase, marking the next step to achieve. Your task is to transition through the "Green" phase by making the tests pass, and then enter the "Refactor" phase to enhance your code's clarity and maintainability while confirming that all tests remain successful.
