Preface
The main objective of software development is to deliver functional solutions to clients, businesses and organisations and keep them satisfied at the maximum. Behind the scenes of software delivery and customer satisfaction are the developers themselves, who should be responsible and accountable to deliver what they have to deliver in an appropriate fashion and to make sure that they will stand well by their client after the delivery of the software. What aspects and tools of software development are we using to be responsible and accountable though? There are a few:- Unit Testing
- Risk management
- An appropriate software development methodology
- Understanding the requirement prior to the implementation
This article emphasises more on unit testing and how unit testing could potentially work as a risk management tool in a code-base. I will use JavaScript for the examples and the Jasmine testing tool.
What is Unit Testing?
A function and/or a program is a complex combination of inputs that deliver a single output. But what does complex mean? The definition of the word complex is:
So now it should be clear that the reason you go from red to green is to make the hypothesis and the implementation sound and the reason that you go from green to red is to make the hypothesis and its implementation complete.
Lets look at an example and how we can unit test it with Jasmine.
The second exercise in codingbat (warm-ups 1) asks you to solve the follow problem
We have two monkeys, a and b, and the parameters aSmile and bSmile indicate if each is smiling. We are in trouble if they are both smiling or if neither of them is smiling. Return true if we are in trouble.
Lets identify the "connected parts" in this exercise.
The exercise provides two premises to return true if you are in trouble: both the arguments aSmile and bSmile have to be true OR both the arguments aSmile and bSmile are false.
Lets translate the first part into a boolean expression:
(aSmile && bSmile)
Lets translate and the second part into a boolean expression:
(!aSmile && !bSmile)
Forget about connecting those two parts for the time being and lets unit test those two parts, starting from the (aSmile && bSmile)
In unit testing we are interested in those "connected parts". In fact, one part at a time, which allows us to be pro-active with the implementation and delivery of every single unit of functionality. For each unit or "connected part" of a function we use to write a test which works as a hypothesis for what has to be achieved. The life-cycle of that hypothesis is as follows:"consisting of many different and connected parts"
- The hypothesis fails, since there is no implementation to make the test pass, or because the test has successfully reproduced a bug which is still unresolved. Your next objective is to write the minimum amount of code to make the test pass.
- The hypothesis is correct (the test passed) probably because we have written the minimum amount of code to make the test pass.
- The hypothesis has to fail again.
This process is called Test Driven Development or TDD and it is used define a formal workflow for the process of unit testing and how a single unit of functionality should be tested. I know that you are wondering why the hypothesis has to fail again at step 3, but the most abstract way of explaining it is that we have to prove that the test and the actual implementation at step 1 and step 2 are correct. The process of Test Driven Development matches perfectly with two very important terms in science: The terms of soundness and completeness. Given that a hypothesis has been defined:
- Soundness: Soundness is the process of scrutinising the validity of an argument and that all the premises inside that argument are true.
- Completeness: Every function has some parameters that should take into account. If you can use this function along with its parameters in any context and the output is true, then it is said that the proof for the particular hypothesis is complete. Also, If we manage to use the parameters of this function to prove that the particular function is true based on the correct type of arguments and false based on the incorrect types of arguments, then also, the proof is complete.
Lets look at an example and how we can unit test it with Jasmine.
The second exercise in codingbat (warm-ups 1) asks you to solve the follow problem
We have two monkeys, a and b, and the parameters aSmile and bSmile indicate if each is smiling. We are in trouble if they are both smiling or if neither of them is smiling. Return true if we are in trouble.
Lets identify the "connected parts" in this exercise.
The exercise provides two premises to return true if you are in trouble: both the arguments aSmile and bSmile have to be true OR both the arguments aSmile and bSmile are false.
Lets translate the first part into a boolean expression:
(aSmile && bSmile)
Lets translate and the second part into a boolean expression:
(!aSmile && !bSmile)
Forget about connecting those two parts for the time being and lets unit test those two parts, starting from the (aSmile && bSmile)
Step 1: The hypothesis should fail
Running this test we will see it failing, as it doesn't have an underlying implementation.
Step 2: Write the minimum amount of code to make the test pass
If we run the test now, it should pass as we've written the code to make it pass
Step 3: Make the test fail again
Running the test, we will see it failing and we are now in good shape to say that out proof for this part of the problem is complete, as we are not expecting the output to be false if both monkeys are smiling.
Now lets unit test the second part of the exercise which is (!aSmile && !bSmile)
The two last tests could be omitted, but it is always good to write such tests to increase your confidence. This brings us to another two important terminologies in unit testing:
This test translates to: "I am confident that the output of the function will be false if it has incorrect inputs, aSmile=false and bSmile=true". And this test will pass with no problem. But why do we need confidence tests? The main reason is because whenever developers deal with bugs, they have to write the respective tests to reproduce them. Writing confidence tests in the first place though, we act pro-actively and if the implementation of the function changes at some point, the confidence test will be able to catch the new changes and inform the developer.
Now lets unit test the second part of the exercise which is (!aSmile && !bSmile)
Step 1: The hypothesis should fail
Like in the first piece of functionality, running this test we will see it failing, as it doesn't have an underlying implementation.
Step 2: Write the minimum amount of code to make the test pass
The test should now pass
Step 3: Make the test fail again
Now the test should fail and this is a good sign that our implementation is complete, as we are not expecting the output to be false if both monkeys are not smiling.
It's time to put all the pieces together. The whole test suite is as follows:
And the implementation of the function will look like:
We can simplify the implementation as follows:
- Code coverage: Code coverage is the process of validating the all premises of a function with unit tests. 100% code coverage means that all the premises of a function have been scrutinised with unit tests and that the unit tests have passed. The unit tests that have been used to bring the code coverage to 100% are the following: Reason being, is that the two tests scrutinise the two premises of the monkeyTrouble function in order to check whether they return the correct output.
I hope that this article worked as a good starting point for you for Unit Testing. Comments from other developers are always welcome, as this is my first article on the topic. As a JavaScript developer I tend to unit test every single line of business logic and add as many confidence tests as possible. It's not me being pedantic, but if you think the time that it saves you during development and debugging, then it really pays off. Now I am on a learning curve, learning C# for my new role and I hope that the next article blog will be related to delegates and then another one on how to do unit testing in C#. Until then, happy coding!











No comments:
Post a Comment