TDD-Albums-II
A free hands-on tutorial for Swift engineers learning Test-Driven Development.
The TDD-Albums-II tutorial is a sequel to the original TDD-Albums library from 2015. The TDD-Albums library started as a hands-on tutorial for a few iPhone developers at eBay that were interested in learning Test-Driven Development (TDD). When I first saw TDD, it was from Jon Reid. Jon offered to “pair-program” and teach his best-practices for writing unit-tests (and designing software). The TDD-Albums library (and tutorial) was intended as a step-by-step series of pair-programming exercises to build a functional iPhone app using TDD (almost) every step of the way. In those days, we built our app code using Objective-C. We built our test code using Swift (which was, at the time, still a new language). Our app was simple, but demonstrated important patterns (like dependency-injection) that scale to more complex situations.
Times have changed, and so have (many of) the tools engineers use to build iPhone apps. TDD-Albums-II is a “modern” version of the original tutorial. We will choose Swift instead of Objective-C (most of the time). We will choose SwiftUI and Combine instead of UIKit. We will choose Swift Concurrency instead of Grand Central Dispatch. Our tools will have changed, but the most important decisions that we use to design and architect our app will have not. We will still write our test code first, we will still write our app code to make our test code pass, and we will still use dependency-injection to swap “production” logic with “test-double” logic when it is advantageous (and safe) to do so.
Protocol-Driven
Like the previous version of our tutorial, TDD-Albums-II makes frequent use of protocols to formalize type interfaces without “locking-down” hidden dependencies on production types. This enables us to swap out (inject) logic for testing (and leads to fast and consistent tests).
Generic-Driven
Like the previous version of our tutorial, TDD-Albums-II practices dependency-injection; we build simple types that do simple things, and then we inject (compose) those types together to do more complex things (or we inject test-double types for fast and consistent tests). In the previous (Objective-C) version of our tutorial. we used “subclass-and-override” to inject these dependencies. Those of you familiar with testing might have also seen other techniques like injecting dependencies with a constructor. What these have in common is leveraging the dynamic nature of Objective-C to inject these dependencies at run-time using polymorphism.
We take a different approach for Swift. We inject our dependencies using Generic Programming. For our tutorial, we either need to inject production logic or test-double logic. Since we know our state at compile-time, we don’t actually need polymorphism to inject these dependencies. Choosing Generic Programming gives us types that can dispatch statically for faster (and, one might argue, safer) code.
Questions and Comments
Please try reading through the tutorial if you are at all interested in experimenting with TDD (or just unit-testing in general). Any comments and questions are welcome. I would also encourage trying to open a discussion (or issue) directly on the GitHub repo for other engineers to engage on over there.
Thanks.