Test Driving a Kata (in Swift!)
A kata is a small exercise designed to improve a developer’s skills. The Prime Factors kata wants a program that can take any number, and return all of the prime factors of that number.
I’m going to do this exercise in Swift, but the language itself isn’t important. The high level solution is more of a concept, and we’re going to focus on test driving this implementation, although some of these examples might progress more than one step for the sake of brevity. Okay, here are our first tests:
Typically I would reach for an object oriented implementation, but I’ve been trying to lean towards functional programming where it makes sense. Since this is a simple input/output function, I don’t want to have to instantiate a new object every time I want to use this functionality. Plus, look how clean that reads!
The above test is intended to ensure that the program knows what to do when a number has no prime factors, and that
1 is not considered a prime factor. So here is our solution to this constraint:
Beautiful! Next test:
XCTAssertEqual failed: (“”) is not equal to (“”)
Remember, that if a number is prime then it is a prime factor of itself. So let’s implement a “quick and dirty” fix:
Since this passes, let’s refactor what we can:
Since we know that
2 is going to be the smallest number that we can consider prime, I extracted it out into a constant for clarity. I also took advantage of Swift’s
guard statement to make it clear that our program is not intended to process numbers below
2. Let’s move on to our next test:
XCTAssertEqual failed: (“”) is not equal to (“[2, 2]”)
And our immediate implementation:
Not too much has changed, here. I created
primeFactorsOfInput so that we can use conditional to mutate the array that we’re eventually going to return. Right now we’re just taking the first factor of the input (which is also the
primeMinimum right now) and either returning just the array with
primeMinimum , or the input divided by the first factor. This might be easier to read after a quick refactor:
Much better! On to the next test!
XCTAssertEqual failed: (“[2, 2]”) is not equal to (“”)
This test reveals a flaw in our core logic. In our code, we’re assuming that every number’s first factor is
2. To think that our program will only get passed an even number is naive. So let’s make our
firstFactor variable a little more dynamic by having it represent the return of a function. The function,
getFirstFactor() is below.
For those that aren’t familiar with the syntax, I’m simply iterating up to the input given and returning the first factor, or the first number that can divide the input evenly.
The rest of the code stays the same. The only other thing I changed was to save our (lexical)
firstFactor variable as the return of this
getFirstFactor(of: input) function, and we can solve up to 5. Bring on the next test!
XCTAssertEqual failed: (“[2, 6]”) is not equal to (“[2, 2, 3]”)
Here is the real challenge. We can handle prime factors of small numbers, where the factors only appear once. However
12 presents a dilemma because we have a prime that goes into it twice. It took me longer than expected to get over this little hump, but the solution is actually much more obvious than you might initially assume. If you look at what the test wants, we already have one of those numbers —
2. As for the
6 , we know our program can already handle the prime factors for an input like
6- so this looks like a great opportunity for recursion! I’m a huge fan of recursion because it forces you to keep your functions small, consistent, and with little to no side effects. Here’s my solution to this test with a minor naming refactor:
Instead of appending the
remainingFactor to the end of our array, we can pass it to our
primeFactors function and recursively call it. And since our function returns an array, all we have to do is simply add the return of the recursive call to our current
primeFactorsOfInput array. I didn’t have to make any changes to get the next three tests to pass:
I didn’t test this over
330, so I’m not completely positive that this is a flawless solution. If you find a number that it doesn’t work with, then feel free to drop a comment below and I’ll update the post.
I summarized our solution with these bullet points:
- Take the first factor of the input and create an array around it
- If the number is prime: we return the array
- If the number is not prime: repeat these steps with the other factor of our current input as the input for
primeFactors, and merge the result with our array.
For a final refactoring, I decided to wrap a function around the important logic. This allowed me to place the most important part of the code at the top of the function to be read first, and the
getFirstFactor portion below. Here is the final solution:
For me, the point of this exercise was a lesson in TDD. The problem itself may seem trivial, but I actually learned a lot about TDD throughout the process. Whether it’s because I think a module or chunk of code is complete before it actually is, or it’s in anticipation of incoming functionality that doesn’t (and may never) exist — I still struggle with the timing of a good refactor. Hopefully this post helped you in whatever aspect of TDD that you’re trying to get more familiar with!