Beginner level TDD: The Prime Factors Kata

Fran Iglesias
Docplanner Tech
Published in
8 min readFeb 17, 2022

– Some time has passed since our last TDD session, my young Padawan.

– Yes, master. I was busy practicing the exercise you taught me. I even tried it with different programming languages.

– That’s good. I would like to discuss with you another exercise. This time, it’s a bit more of an interesting problem which enable you to decompose a number in its prime factors.

– Oh, nice! We are going to program the “Eratosthenes sieve”, then.

– Hum. Something like that, I would say.

– I’m willing to start… With a test, of course.

– So…

– Our first test should be small enough to fail, but only for a good reason. For example, one test that instantiates a class, then requiring us to introduce that minimal code.

– This sounds right. Show me the code.

– Sure.

– How do you make this test pass?

– First of all, I execute the test to be sure that it fails for the expected reason: that the class PrimeFactors doesn’t even exist. Once I have checked that, then I simply write enough code to instantiate the class. Like this:

– Ok, good. And now?

– Once I have the test in green, I can refactor moving the class to its own file. Thanks to the test, I can be sure that I put it in the right place.

– I love to see that you listen to my advice, young apprentice. But, don’t you feel that this way of working is slow?

– Not at all. It seems slow, but I can perform these first steps very fast with the help of my IDE. The fact is that it avoids stupid errors, like typos or putting a class in the wrong namespace, and this sort of thing.

– Let’s advance a little more, then.

– Ok. The next thing I need is that PrimeFactors understands a message to decompose a number, so I’m going to test for that:

– Aha. So, you don’t pass an argument yet.

– That’s right. First of all, I want to be sure that the class can receive the message. After that, I will introduce the argument.

– Very nice. We have all that is needed to start developing the behavior. Isn’t it?

– Almost, master. We should start with a refactor to be ready for that.

– Can you show me that?

– Sure.

– Your progress is impressive, young padawan. You introduce a change in the code that allows us to start introducing parameters without breaking the current tests.

– Yes. If the language allowed method overloading, I would do it by introducing a second decompose method accepting an integer. But PHP was born this way.

– I see. What would be our first example?

– Maybe the number one. It has no factors.

– Very interesting. How do you make the test pass?

– Easy peasy.

– Well done. What about refactoring?

– I can see that all of the tests will be decomposing a number and expecting an array of factors, so I think that I can reflect that into the test using this idea:

– Ooooh. Very nice. So, you are introducing some domain vocabulary in the test. Clever!

– Thanks, master.

– And now, it is very easy to add a new test.

– That’s right.

– Let’s make the test pass.

– I would start doing this:

– Seems right. But maybe you can refactor something.

– Hum. Let me see… Instead of returning [2], I should return the variable.

– Why?

– If the number is 2, that is a prime number, so the decomposition is the same number. I understand that this better reflects our mental model.

– Good point. Can we go to the next example?

– Of course. Number 3 is also a prime.

– It should have a similar solution; is it?

– Yes, master. Very similar:

– This really needs a refactor, young padawan.

– Yes, of course:

– I can see something more.

– Really?

– Yes. Numbers are greater than one.

– Oh. I see.

– We are ready for the first not-prime number.

– I would start simple.

– I agree.

– Time to think about this. What are you returning here?

– The array of factors.

– That’s right, but what type of response is that?

– Errr. A constant?

– Correct! Why do you return a constant?

– Because I don’t know exactly how to compute the result. I only have one example and for the moment the test is not asking me for more. This is the minimum amount of code that makes the test pass.

– Wise answer. So, what do we need to progress?

– A new example that challenges the current solution.

– Like five?

– Let me think… No. Decomposition of five is implemented already, because being a prime number, simply returning the number as the unique factor is the correct solution.

– Six, then.

– And the code to make it pass:

– Do we need more examples to see a general rule and refactor things?

– I don’t think so. Maybe in other problems, we would need to have some more examples, but not here.

– So, what do you propose?

– I can see several things. First of all, both 4 and 6 are divisible by 2. If a number is divisible by 2, the first factor would always be 2. I think I can reflect that in the code.

– Uh, oh. The example of decomposing 2 fails because we are introducing the number one as a prime factor.

– I see. The problem is that the remainder of dividing 2 by 2 is 1.

– I need to change that to prevent it.

– I’m not sure that remaining is a very good name.

– I agree, but I cannot think of a better one.

– There is something that bothers me. There are lots of number two’s, there. Also, I think that we could do something with that array. If you were using some kind of collection class it would be easy to spot.

– Hhhm. Let’s go step by step, master. The number two represents a divisor, or a factor. What do you think?

– That sounds good enough to me for the moment.

– Ok. Let me make some changes.

– Now, we got rid of constants and have variables in the response.

– Aha.

– What about treating the array as a collection. In PHP we don’t have native collections, but there are some array functions that can do the job.

– Yes, master. I can see what you are proposing. But using the other notation is faster.

– I understand, but we shouldn’t care about that yet. I want you to spot something.

– Can you see something weird?

– We have three returns. I don’t worry about having multiple returns, but in this case, they feel like a bad solution, because we start with an empty array or collection of factors, and we push those that we are finding.

– And…?

– Ooops. The variable remaining. After guessing the first factor, this remaining is the part of the number that is left without decomposing. In fact, we could keep the name number for that variable.

– Show me how we can change that.

– Sure.

– Wow!

– What happened?

– I have to make some more changes than I expected and move the second $number > 1 condition to a new place - but the code looks clearer now. We remove one level of indentation.

– That’s good. What about a new example?

– Ok. The next number is 7, but it is a prime, so the test will pass. I will try with 8.

– The test fails. It correctly extracts the first factor, but then it puts the remaining 4 as the second factor. We need to force the code to keep trying to decompose the number while it is possible.

– Isn’t while a generalized form of if?

– Hhhm. Let me try something.

– Damned! Something is terribly wrong. We’ve got an infinite loop.

– What’s the problem?

– Maybe I’m trying to do too much, but if the number is 3, the while loop never ends.

– Yes. And, why does this happen?

– Because in that case, we don’t reduce the number. We get the factor right because it is prime, but we are not reducing the number after that, so the loop cannot end.

– What can we do?

– Hhhm… I’m stupid.

– What?

– I changed the wrong conditional. We should keep trying dividing by 2:

– Great! What does this failure teach to us?

– That I’m stupid?

– No! The lesson here is that we should concentrate on the example we are testing. You tried to generalize too much and too early, instead of concentrating on the thing that was important at this point: dividing by 2.

– Yep. I can see it now.

– Did you spot how a little change from if to while can have a big effect on the program?

– Yes, indeed.

– Let’s try with another example. It’s time to try with 9.

– It fails. That’s great. It’s telling us something new. What is it?

– We need to manage a change in the divisor. But, we can start simple:

– Now, I can refactor in small steps:

– And…

– And… what?

– Now, I can see that I need to increase the divisor when I exhaust it… and do that until the number is 1, so no more factors could be found. But I need to do some preparatory refactor before and initialize the divisor outside of the first condition.

– I wonder if we could change that if to while, so we get the same structure and remove that ugly duplication.

– You could do that provided it doesn’t break the tests.

– Now, it is clear how to remove duplication.

– Yes.

– This fails.

– That’s because the remaining if condition should be removed.

– And that’s all.

– All? What do you mean?

– Let’s try another example.

– It works

– Try 30 and 900

– All of them pass.

– I can’t imagine another test that can fail. The exercise is mostly completed. You will need to clean some details, and maybe do some optimization here and there.

– Like this:

– This is the end of our lesson for today.

– Thanks, master. I will practice this exercise.

– I hope you do. There are some important takeaways from this exercise. First, you should follow the three laws and the red-green-refactor cycle. Second, make small changes secured by the tests. Third, try to concentrate on the example at hand and work only to fulfill it. Fourth: avoid generalizing too early. And Fifth: don’t try to optimize code until you have defined the behavior.

--

--

Fran Iglesias
Docplanner Tech

I’m a software developer who likes testing, refactoring and application architecture.