behavior-driven-testing

Ruby on Rails

The Easy Way To Write A Test Case

Steven Yap   7 Dec , 2015  

A lot of junior coders often complained to me that it is very difficult to write a test case even though they acknowledged that test cases are very important.

As most coders are not trained to write test cases, it can be a very daunting task to do for the uninitiated. In this blog post, I will present a very simple approach to writing your test cases.

What is TDD?

Before we go into writing a test case, you must understand what is test driven development (TDD). TDD means using test cases to drive your development. It can be broken down to three steps (in this specified order):

1) Red: Write a failing test case
2) Green: Write the code to pass the test case
3) Refactor: Refactor your code (including your test case code!)

We will write a test case first before writing the actual code. Naturally this test case will fail because there are no actual code to pass it (hence, red). Next we will write the actual code to pass the test (hence, green). Finally, we will refactor our actual code to make it better with the test case to protect us if we accidentally spoilt the code.

You can’t refactor your code confidently unless you have a test case to cover it.

So TDD can be summed up as Red-Green-Refactor. Through this cycle, we use test cases to drive our development. We achieve a higher code quality because we have a test case to cover our code which gives the affordance to refactor our code to be better.

Why is TDD and test cases important?

Test cases are important because they:

  • Prevents you from breaking existing working codes
  • Prevents others from breaking existing working codes
  • Provides an updated specification of the app
  • Enables refactoring of code
  • Gives coders a peace of mind = happy coders
  • Rubyists write tests so you should too

So we know test cases are important. But why can’t we write them AFTER the code? Isn’t it better? Isn’t it easier?

There are several advantages to write your test cases FIRST (in the style of TDD):

  • It is actually easy to write a test case first
  • It helps you to make better object design decisions
  • It focus your thinking when writing the code later
  • It prevents false positive in your tests
  • You can use it for debugging eg. byebug or pry
  • You can refactor your code straight away!

How to write test case easily?

A simple approach to write a test case is to think of behaviour, instead of implementation.

Let me give you an example.
Everyone knows how to use a calculator and how a calculator should behave.

We can use a calculator to help us to sum up numbers, say 1 + 1.

Input:
– Press 1
– Press +
– Press 1
– Press =

Output:
– Expect calculator to display 2

We understand easily what are the inputs we need and what are the outputs we expect.
When we perform the exact inputs, we expect the exact output.
If we don’t have the exact output, it means that the calculator is not working!

In the example above, do you know how does the calculator calculates 1 + 1 in order to return 2?
Unless you have knowledge of circuitry system, you will not be able to know the implementation.
However, without understanding the implementation of the calculator, we are still able to understand how should the calculator behave (ie. 1 + 1 should display 2).
This is the difference between behaviour and implementation.

Using behaviour thinking to test case

So how do we translate thinking about behaviour into our test cases?
We just need to know the inputs and the outputs, and we can write our test cases.

In writing a test case, input is simply calling the method that we are testing and output is simply expecting the result from the method.

Input = Calling the method
Output = Expecting the correct result
So Input & Output becomes Call & Expect.

Test case for Sum()

Let’s play with a very simple example: write a test case for the method sum() which will return the sum of two parameters. eg. sum(1, 1) # => 2

Remember “Call & Expect”? To create our test case, we will call the method eg. sum(1, 1) and we will expect the result to be 2.

With the above call and expect approach, the test case becomes:

RSpec.describe Calculator do
describe '#sum' do
it 'returns 2 for 1 and 1' do
result = Calculator.sum(1, 1)
expect(result).to eq 2
end
end
end

That’s it for our first test case! We can also easily triangulate it with another test case:

RSpec.describe Calculator do
describe '#sum' do
it 'returns 2 for 1 and 1' do
result = Calculator.sum(1, 1)
expect(result).to eq 2
end

it 'returns 8 for 3 and 5' do
result = Calculator.sum(3, 5)
expect(result).to eq 8
end
end
end

This creates the test case first without us writing the code. Isn’t this easy? 🙂

A More Difficult Example

Ok maybe sum() is a little too easy and not that practical. Let’s do something really tough.

In mathematics, the Leibniz formula for π, named after Gottfried Leibniz, states that

Leibniz formula for π

Based on the formula, the more fractions (eg. 1/3, 1/5, 1/7) we used, the more accurate the value of π we will get.

Write a method to calculate the value of π that accepts a number n which is the number of fractions used to approximate the value of π using Leibniz formula.

Take a moment to understand the problem but please don’t try to solve it.
Let me show you that is much easier to write the test case first, than the actual solution.

To write our test case, we think about ‘Call & Expect’ again.
In this problem, we will call the method with n which is an integer and we will expect it to give us the correct approximated π.
It is that easy.

Or not? er….

What should be the name of the method? Maybe approximate_pi()?
Where should I place this method? Maybe as a method under the module Calculator?
How should I call this method? Simply as Calculator.approximate_pi(6).

What should be the result to expect? We can get this easily by simply calculating (in Rails console) 4 * (1 - (1.0/3) + (1.0/5) - (1.0/7) + (1.0/9) - (1.0/11) + (1.0/13)) which we will get 3.2837384837384844.

In conclusion, we will call Calculator.approximate_pi(6) and we should expect 3.2837384837384844.
Call & Expect: We have derived our test case!

Here’s the code:

RSpec.describe Calculator do
describe '#appromixate_pi' do
it 'approximate pi to 3.283738 for 6 fractions' do
result = Calculator.appromixate_pi(6)
expect(result).to be_within(0.1).of(3.283738)
end
end
end

As you can see from this example, it is much easier to create the test case than the actual solution itself.

Think about object design

From the approximate π example, while it is easy to think in terms of ‘call & expect’, it may not be that easy to derive what to call in the first place.
This is because we need to make the design decisions on how to name the method, where to place the method and how to call the method.

Don’t mistook these design decisions as additional work and time taken.
These design decisions have to be made even if you are writing the actual solution directly without the test case so there is actually no loss in time.

It is a matter of making the design decision in the test case before the actual code or in the actual code without the test case.

There is a positive side effect of making the design decision in the test case: You are using your object straight away in your test case just by the virtue of calling it. This allows you to validate if this is a good API to use. This side effect subconsciously drives better object design in your coding.

Focus on the actual solution now

With the test case written, you can focus on writing the actual code now. I find that the test case generally helps me to focus on the code on hand instead of worrying about the entire structure/flow/interaction/things-not-done-yet/etc. This gives me great productivity!

Furthermore, because my mind does not worry about the other uncompleted tasks, I have less stress writing my code now.

Test case makes me happy. 🙂

Refactor Now!

After you have finished the actual solution and your test cases are passing, you can refactor immediately (instead of spending time now to write the test case).

This is great because you just finished the actual solution and this moment is the moment where you understand the code the most.

Instead of being distracted to write a test case, you can immediately work on refactoring your solution.

Furthermore, after finishing the actual solution, you have a better understanding of the problem/solution which helps you to refactor your test cases (either by creating other test cases or refining the test cases to test more accurately).

This really maximise productivity.

Write test case first!

I hope I have convinced you why writing test cases first is important and also given you a simple easy approach to create test cases – Call & Expect.

Ultimately, we all want productivity and happiness for developers. Writing test case first gives you more productivity and happiness in your development. Go forth now!

Image source from https://en.wikipedia.org/wiki/Sensory_processing_disorder

, , ,

One of the founders of Futureworkz - a Singapore-based mobile and web development company. He is a geeky Ruby coder and consults company on IT implementations and business startups.