Today we are going to test-drive the testing framework without any external testing framework. This will be done through test-driving a simple kata (FizzBuzzKata). For example:
- every time we expect a test to fail and it doesn’t, this is a failing test for our testing framework, that we will be fixing,
- every time we expect a test to pass and it doesn’t, this is another failing test for our testing framework, that we will be fixing.
For practical reasons, today we are going to use concrete programming language instead of pseudo-code - javascript. Except for small details, that we will point out, the techniques shown here are language-agnostic.
This article is only first one of the series “Build Your Own Testing Framework”, so make sure to stick around for next parts! All articles of these series can be found here: http://www.tddfellow.com/blog/categories/build-your-own-testing-framework/.
Shall we begin?
FizzBuzzKata
Given the number,
- return
Fizz
when the number is divisible by 3, - return
Buzz
when the number is divisible by 5, - return
FizzBuzz
when the number is divisible by 3 and 5, - return string representation of number otherwise.
Writing your first test
How do we write our first test, when we don’t have a testing framework and we want to create one? - It seems, that we have to design how the test should like in our brand new testing framework.
I personally, would go with the xUnit-like design, since it is relatively simple. Given this, we might write our first test and it will look something like that:
1 2 3 4 5 6 7 |
|
This test should fail, because function fizzBuzz
is not defined, but it doesn’t fail, since function testNormalNumberIsReturned
is never called. In fact, the object with FizzBuzzKataTest
is never being created.
Easiest way to solve that:
1 2 3 4 5 6 |
|
If we run this code with node
:
1
|
|
We will get the expected error:
1 2 3 4 5 |
|
So, let’s define this function:
1 2 3 4 5 |
|
If we run our test again, we will get the following error:
1 2 3 4 5 |
|
Clearly, to fix it we need to define assertTrue
on FizzBuzzKataTest
object. Obviously, we do not want our user to define all their assertion for every test suite. This means, that we want to define it on FizzBuzzKataTest
object outside of the definition of FizzBuzzKataTest
.
There are two ways to go about it:
- inheritance: make
FizzBuzzKataTest
inherit from some other object functionassertTrue
, or - composition: make
FizzBuzzKataTest
accept a special object with functionassertTrue
defined on it.
I would like to go with composition method since it gives us more flexibility in the long run:
1
|
|
and the usage of assertTrue
has to change appropriately:
1
|
|
and t
has to be created and passed in correctly:
1 2 3 4 5 6 7 8 9 |
|
If we run the test suite again, we will not get any failure anymore. But we were expecting assertTrue
to fail, so let’s make it fail:
1 2 3 |
|
When we run the test suite, we get:
1 2 3 4 5 |
|
Now, let’s customize the error message a bit:
1 2 3 4 5 6 7 8 9 |
|
When running this, we are getting the expected error:
1 2 3 4 5 |
|
This looks better now. Let’s fix the error now by implementing the simplest thing that could work:
1 2 3 |
|
And as we run our test suite we get:
1 2 3 4 5 |
|
Oh, it should have passed the test. I know why it didn’t: we throw this error unconditionally, let’s add an appropriate if
statement to assertTrue
function:
1 2 3 4 5 |
|
If we run this code, it does not fail. That was our first green state - it took as awhile to get here. The reason for this is that we are not only test-driving FizzBuzzKata
, additionally, we are writing a feature test for a non-existing testing framework. Now that we are green, we should think about refactoring, i.e.: making the structure of our code right.
Obviously, we should move our testing framework code outside of the test suite file. Probably, somewhere in src/TestingFramework.js
. For that, we need to first parametrize FizzBuzzKataTest
and extract the function to run the test suite.
Parametrize:
1 2 3 |
|
and extract method:
1 2 3 4 5 6 7 |
|
and inline the variable testSuiteConstructor
:
1
|
|
Now it is time to move testing code to src/TestingFramework.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
And to be able to require runTestSuite
function:
1 2 3 4 5 6 7 |
|
And, finally, let’s use that from our test suite:
1 2 3 4 5 6 7 8 9 |
|
If we run the test suite again, everything should pass. Somehow, I don’t feel comfortable now, let’s try to break the test suite and see if it will fail as expected:
1 2 3 |
|
And run tests:
1 2 3 4 5 |
|
Yes, it still works as expected. We have just introduced a Mutation to our code, to see if it is still tested properly. Let’s undo the Mutation and see the test still pass. And it does.
If you look closely now, it should be possible to inline FizzBuzzKataTest
definition as an argument of runTestSuite
call:
1 2 3 4 5 |
|
And if we run our test suite, it still works. Just to check, that we are still good, let’s repeat our Mutation from the previous step. It should still fail as expected. And it does. Undo the mutation and the test is still passing. Great.
I think we are done with Refactoring step, for now, let’s get back to writing another failing test.
Writing the second test
1 2 3 |
|
If we run these tests, they do not fail. This is strange, let’s look at runTestSuite
function again:
1 2 3 4 |
|
Great, it just runs one specific function, we should probably run all functions starting from test
instead:
1 2 3 4 5 6 7 8 9 |
|
REMARK: this code is Javascript specific. Other programming languages will have their own way of iterating over the function/method list and calling a function by its name. Usually, it is some sort of reflection for compiled languages and meta-programming features for interpreted languages.
If we run tests now, we get the expected failure:
1 2 3 4 5 |
|
If we try to change return "1"
to return "2"
, of course this test will pass, but the other will fail:
1 2 3 4 5 |
|
This is great for couple of reasons:
- It validates, that our change to how
test*
functions are discovered is correct, and - We have to have a bit smarter implementation to pass both tests now:
1 2 3 |
|
And if we run the tests, they pass. Now, that we are in Green state, we should start refactoring. Have you noticed the duplication already?
1 2 3 4 5 |
|
Extracting "1"
and "2"
as variable expected
, and fizzBuzz(1)
and fizzBuzz(2)
as variable actual
, makes these 2 lines identical:
1 2 3 4 5 6 7 8 9 10 11 |
|
Specifically, this is identical:
1
|
|
This sounds like t.assertEqual(expected, actual)
to me. So let’s extract it:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now, let’s use it and inline our expected
and actual
variables:
1 2 3 4 5 6 7 |
|
This looks much more readable. If we run tests, they still pass. If we try to break our code by using some Mutation, the tests fail as expected. Great, our refactoring was a success!
Let’s finish test-driving our fizzBuzz
function.
Test-Driving Fizz Buzz Kata
First test for Fizz
case:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
This is pretty stupid implementation, but it works for that one tests, so let’s write the test, that will break this implementation and force us to write real if
condition:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
This technique is called Triangulation:
- the first test is to force us to write some
if
statement with a correct body, - second is to force us to make the condition right.
- If we had an
else
clause, we would have had another test to make that part right.
OK, that looks like a right implementation for Fizz
, let’s write the test for Buzz
now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
And finally let’s implement final requirement FizzBuzz
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
I think we are done with the implementation. FizzBuzzKata
has an extended set of requirements, but they are out of the scope of this article. These requirements force us to introduce Strategy pattern and stop using this unmaintainable chain of if
statements.
Refactoring this code to Strategy pattern is left as an exercise for the reader.
Bottom Line
Congratulations! Using FizzBuzzKata
we have test-driven bare-bones testing framework to the point, that we can do Test-Driven Development for a simple Kata. And all that without having any testing framework in place.
The code is available on Github: https://github.com/waterlink/BuildYourOwnTestingFrameworkPart1
Now, with this minimal framework in place, it should be possible to unit-test the framework itself, so that we can support more use cases. This will be covered in next series of “Build Your Own Testing Framework”. Stay tuned!
Thanks!
Thank you for reading, my dear reader. If you liked it, please share this article on social networks and follow me on twitter: @tdd_fellow.
If you have any questions or feedback for me, don’t hesitate to reach me out on Twitter: @tdd_fellow.