What is TDD and Automated Unit Testing Part 1
Posted by 1/27/2018 06:33:00 PM with No comments
on This is a part from a series dedicated to TDD and Automated Unit Testing. I will try to do my best to keep this series alive and updated.
The series parts:
What is TDD and Automated Unit Testing Part 1
What is TDD and Automated Unit Testing Part 2
What is TDD and Automated Unit Testing Part 3
What is TDD and Automated Unit Testing Part 2
What is TDD and Automated Unit Testing Part 3
First, let’s agree on something. The main purpose of this
post is to give you an introduction about TDD and automated unit testing. It is
not intended by any mean to deal with this post as if it is a main reference or
guide to teach you TDD or automated unit testing. Don’t get me wrong, for sure
there would be some code snippets and examples but this is not enough for you
to depend on to learn TDD and automated unit testing. There are other reach materials
where you can rely on to grasp the professional knowledge you desire but starting
with this post would make it much easier for you, I hope.
After being clear about this important note, let’s try to
dive in and come up with some main definitions and concepts that may help us
understand what is it all about.
Why is testing important?
It may seem that this is a trivial question to ask but
believe me some people already think that testing is not that important at all
and it acts as just some extra cost. So, in simple words, testing is important
because even writing a simple method or function could raise some issues you
didn’t expect at all. That’s not for sure because you are a lousy coder or
something, but it could be because you didn’t cover all cases and scenarios
which could direct the written code into weird behavior. But why could this
happen? Aren’t you an experienced coder/developer? How could you miss these
causes?
How could an experienced coder/developer miss some cases and scenarios and leave them uncovered?
This happens because when you are implementing certain logic
and writing its code you are putting on the hat of a coder/developer. This
could help you cover most of the direct and obvious cases and scenarios because
you would be totally focused on these main core cases and scenarios which is
totally logical. But, still there would be some other cases which your mind
didn’t detect at the moment of writing the code and that’s because these types
of cases need another type of hats, the tester hat.
When putting on the tester hat, you start considering the
behavior of your code from the point of view of the end user, the real user.
This opens your mind on a totally new perspective, you start thinking and
acting as the end user ignoring all your previous knowledge of the code and its
inner logic. You start dealing with the code as a black box which you can’t see
inside of it but you still know what it should do, you know how it should react
for some certain input and you know what you should expect as an output. So,
starting testing by this perspective helps you exploring new set of cases and
scenarios which you should cover and your code should handle in a logical way.
Is that all what makes testing important?
No, I can keep writing for days telling you why testing is
important, for example, how much would it cost to find a bug in development phase,
staging phase, deployment phase and on production. But, my main concern is to
walk you through why testing is important for you, the coder or the developer
and keep in mind that what affects the quality of your work will for sure
affect the quality of the whole project.
But why should we make testing automated, isn’t it enough to do it manually?
No, believe me it is not enough to do it manually. If we are
talking about a simple method or function maybe it would be enough. But, for a
big system where change requests and new features keep coming that would not be
enough at all. You still don’t know why, I am going to tell you now. Every time
you do a change for a single line of code in the entire system you open a gate
to introducing new issues or illogical behavior. If you are convinced that
testing your code is important to cover up all hidden cases and scenarios, then
you should be convinced that applying a change to an already written code could
introduce new hidden cases and scenarios and may be affect old ones. Then,
applying changes should be followed by a re-testing phase to make sure all
changes introduced to related cases and scenarios, whether they are newly added
or modified, are covered.
Ok, re-testing is important, but still, why automated?
It should be automated because as you can see applying a
change on one line of code could introduce other changes on other parts/modules
of the system. So, these other parts/modules should also be re-tested. But, who
will be the one to spot these other parts/modules and tell you about them to go
and start re-testing them? This person could be you which is logical as you are
the one who introduced the first change which started the chain reaction and
you should be aware of the subsequent changes in the other parts/modules. But,
are you really aware of all the subsequent changes? May be sometimes you are aware
but other times you are not and you should not be totally ashamed of it because
software systems are getting complicated by time which makes it harder. So, if
there is something that could tell you where else to look, or even better tell
you where else a problem or an issue has aroused, that would be great and that
something my friend is the automated testing. If you cover your system
blocks/parts/modules with automated unit tests, whenever you introduce a change
to any of these blocks/parts/modules you will get an instant feedback if any of
the other blocks/parts/modules are now broken and that is the power of
automated unit testing. Also, rather than applying the same manual testing
steps every time you introduce a change, why not write these steps only once
and then run them more than once?
WOW, so should we implement automated unit testing for the entire system?
This is not a simple question to answer. As you will see in
the rest of this post automated unit testing introduces some complexity and
code standards which you should abide to and not always you will have the power
to do it as it should. So, at the end it is a compromise. You should decide
what fits your case keeping in mind all aspects of your project and client.
But, let’s say it that way, do as much as you can even when you know you won't
perfect it.
What else do you know about automated unit testing?
I know some other things but let’s start with an important
thing. From the era of manual unit testing, coders/developers gained the
knowledge of how to define unit test cases and scenarios and compile a list of
inputs and expected outputs. This kind of knowledge is still required even with
automated unit testing. It is a kind of skill which grows with practicing. Now,
the new skill introduced by automated unit testing is writing automated unit
testing and this introduces us to TDD.
What is TDD?
TDD stands for Test Driven Development. You can call
it as a concept or a principal or a methodology, it doesn’t matter to me, what
matters to me is to really understand what it is about.
So, what is TDD about?
It is about defining some steps or a procedure to make sure
from day one that your project will abide to automated unit testing concepts
and implementations. It takes testing to a whole new level of existence and
commitment. With TDD testing is not just a secondary thing to do when you can
or have time, it is a main thing, it is a driving thing. Did you hear about RGR
or Red Green Red?
What is RGR or Red Green Red?
These are the steps you should follow when trying to
implement some logic, even before starting to write the first line of code. You
start by writing the test case!!! But how to start by writing a test case when
I don’t have code to test!!! This is weird at first I know but once you
understand it you will get accustomed to it. When writing a test case, you are
writing some code to test a business functionality, not a line of code or a code
function/method. This drives to writing a code function/method to make this
test come true and valid and accordingly, make the business functionality come
true and satisfied.
The first Red means that you start by writing a test
case which you are sure that it would fail. When writing this test case you
will find yourself in need to write some other code which is implementing the
logic you are testing in the first place. So, do it but make sure to only write
the headers of the methods just to make the code compile, nothing less, nothing
more. Keep in mind that you already know that the test case you have in hand
right now will fail and that is fine and intended.
The Green means that you start implementing your code
method so that the test passes successfully. At this point you have an
implemented code method with a passing test case.
The last Red means that you need to get back to your
implemented code method and spot the piece of code which makes the test pass.
Then you should change this piece of code, may be by commenting it or applying
a minor change, but, your change should be an educated change, not just a
random one. You should be sure and convinced that the change you are applying
contradicts with the intended logic of the code method and would finally cause
an illogical behavior. After applying the change and running the test case, the
test should fail. If that happens, then you are good and you can revert your
change to get the previous passing logic.
Now you may be asking, why the hassle of doing the last Red?
The main purpose of the last Red is to make sure that you are testing
the right subject right. This helps you be sure that the test you have written
is really testing the business logic, not a dummy test that would pass anyways.
That’s why you try to contradict the business logic implementation into your
code method and check if the test would fail as it should or not. If it didn’t
fail then, you have a faulty test case as it is passing even with the wrong
business logic implementation.
Some people extends the RGR with an extra R to be RGRR. This
extra R stands for Refactor. This means that finally you should check
your code method implementation and see if it need some refactoring. If it
doesn’t need, then you are done with this test case and you can move to the
next test case. But, if it does need refactoring, you should do the needed
refactoring then you should repeat the steps starting from the Green
step.
Can we have an example on RGR or even RGRR?
Sure, let’s assume that the system you should start working
on is a calculator. One of the operations the system should handle is adding
two numbers. Then you would start working on this business feature as follows.
Red: start writing a test case. The test case should
be
void ShouldReturn3WhenAdding1And2() { check if Calculator.Add(1, 2) returns 3 }
At this point you don’t have the Calculator.Add method, and
that’s why your test case code doesn’t compile. So, you then go and write the Calculator.Add
method and do the simplest changes just to make the code compile.
number Calculator.Add(number first, number second) { returns
1 }
At this point, the code compiles and that’s what we need up
till now.
Green: Get back to the Calculator.Add method and work
on it so that it does what it is intended to do.
number Calculator.Add(number first, number second) { returns
first + second }
Now try to run the test, it should be passing. If it isn’t
passing, get back to the Calculator.Add method and see what is wrong and keep
doing this till the test passes.
Red: Make a breaking change in the Calculator.Add
method so that the business logic is contradicted. For example, you can replace
the “+” with a “*”.
number Calculator.Add(number first, number second) { returns
first * second }
Now run the test again and it should fail as the test is
expecting 3 and it gets 2. So now you are sure that the test is already testing
the right business logic in the right way. Revert the change to move on.
number Calculator.Add(number first, number second) { returns
first + second }
Here you may ask, how could this simple test be an invalid
test? How could it be testing another business feature or how could it be
testing in a wrong way?
May be because this is a simple test you can’t see what
could have gone wrong. But on complex methods there is a room for cases where
you may be writing a faulty test. I can show you that even with simple methods like
the Calculator.Add method.
Suppose that your test case was like this
void ShouldReturnNumberGreaterThan1WhenAdding1And2() { check
if Calculator.Add(1, 2) returns a number greater than 1 }
In this case both methods below would pass the test even
when they are implementing two different business logic and one of them is
completely wrong
number Calculator.Add(number first, number second) { returns
first + second }
number Calculator.Add(number first, number second) { returns
first * second }
In this case, the final Red would have warned you
that the test is wrong as it would have passed on both cases which should not
happen.
Now you know how to follow the RGRR, but what about the way the testing code is written?
You may have heard about AAA. It stands for Arrange,
Act, Assert. These are the steps to follow to write a testing case code. Arrange
means to start your testing code by preparing the subject you are willing to
test. For example, in the calculator example above, if the add method is not a
static method, then you will need to create an instance of the Calculator class
first to have access to the Add method. This kind of actions is not the core of
the test case, it is just some preparations and arrangements to be able to
proceed with the testing process. Act means to start applying the code or
logic that you are trying to test. For example, in the same calculator example,
the line where you call the Add method is the acting part of the testing
process. Assert means to check the result(s) coming from the Act step,
see if the result(s) are the expected ones or not. You may need to do more than
one Assert and that’s fine. For example, in the same calculator example, the
assertion is done when checking if the result of the Add method is 3 or not.
Now, let me tell you about Testing Frameworks and Test Runners
Testing Frameworks are some libraries which help you
write your test case code. They provide you with some methods like
Assert.AreEqual, Assert.AreNotEqual, Assert.Throws,….. and the names of these
methods and sometimes the implementation differ from one testing framework to
another. They also provide some attributes which change the behavior or the way
the test cases may run.
Test Runners are some plugins and libraries which
manage the process of running the existing test cases. They may provide you
with some settings and options to choose from like when to trigger the test
cases? After each build? On demand? And so on….
Ok, now I am totally interested into starting TDD, so good bye
No wait. Up till now you didn’t know everything about TDD
and automated testing, we just scratched the surface. There is more about this
topic and the practices you should know about. But, I am tired right now so let’s
take a break and then we can get back.