Dealing with randomness in algorithms can be a huge mental block for some people when they try to understand how they might use TDD. TDD is so deterministic, intentional, and controlled that your initial gut reaction to introducing a random process may be to think that it makes TDD impossible. This is a place where TDD actually shines though. Here's how.
Let's pick up where we left off on the simplistic NumberGuesser
from earlier. We're going to add a requirement so that it will randomly choose numbers that the user has guessed, but will also weigh for what is most likely.
To get there, I first have the NumberGuesser
guess whatever the previous number was revealed to be every time I ask for a guess. The test for this looks like the following:
It's a simple test that ultimately just requires us to set a variable value in our class. The behavior of predicting on the basis of the last previous input can be valuable. It's the simplest prediction that we can start with.
If you run your tests here, you will see them fail. This is what my code looks like after getting this to pass:
Upon making this test pass, we can review it for any refactoring opportunities. It's still pretty simple, so let's keep going. Next, I will have NumberGuesser
randomly choose from all of the numbers that were previously guessed, instead of just the last previous guess. I will start with making sure that the guessed number is the one that I've seen before:
Running this test now will cause a new failure. While thinking about the laziest way of getting this test to work, I realized that I can cheat big time. All I need to do is create my new method, and take the first element in the list:
For our purposes, laziness is king. Laziness guards us from the over-engineered solutions, and forces our test suite to become more robust. It does this by making our problem-solving faster, and spurring an uncomfortable feeling that will prompt us to test more edge cases.
So, now I want to assert that I don't always choose the same number. I don't want to force it to always choose a different number, but there should be some mixture. To test this, I will refactor my test, and add a new assertion, as follows:
I get the test failure message It shouldn't always guess the same number
, which is perfect. This test also causes others to fail, so I will work out the simplest thing that I can do to make everything green again, and I will end up here:
There are probably many ways that one could get this test to pass. We've solved it this way because it's first to my mind, and feels like it's leading us in a good direction. What refactoring do we want to do here? Each method is a single line except the guess
method. The guess
method is still pretty simple, so let's keep going.
Now, I notice that if I've just used number_was
to enter the observations of the previous numbers, it will only ever guess the previous number, which is bad. So, I need another test to catch this. Let's write the new test (this should be our fourth):
This fails on the last assertion, which is perfect. I will make the test pass using the following code:
Tip
Downloading the example code
You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
There are other issues here that I can write for the failing tests. Notice that if I were to provide a single observation and provide a set of observations, every assertion that I've listed so far would succeed. So, I write a new test to ensure that NumberGuesser
guesses every number at least once. We can code this up in the following way:
And my final code looks like the following:
Technically, there is a chance that this test will fail just due to a random chance. The probability of this test failing for this reason is 0.5^100, which is 7.9 x 10^-31. Basically, the chance is zero.