What if I told you there was a way to significantly reduce potential bugs in production code? Undoubtedly, most programmers would not hesitate to jump on this opportunity no matter the cost. As many programmers know, it can be quite the unpleasant experience for a user to encounter a bug, and for the development team to attempt to recreate the bug and fix it in production as quickly as possible.
Well lucky for us, there is a solution, but it is no magic pill. It is…
Test Driven Development (TDD)
Test Driven Development, or TDD, is a software engineering practice of writing unit tests before writing the production code. The mission of TDD is to provide structure to the coding process and as a result, write cleaner, more purposeful code. This practice stems from the Extreme Programming (XP) method emerging from the Manifesto for Agile Software Development.
“[A] set of values based on trust and respect for each other and promoting organizational models based on people, collaboration, and building the types of organizational communities in which we would want to work.”
Below are two key values of the Agile Manifesto pertaining directly to TDD state:
- Working software over comprehensive documentation
- Responding to change over following a plan
Test driven development differs from the V-model, a software development life cycle, in that TDD values “test first” approach versus that of the V-model’s “test last” approach, which has proven to be much less efficient.
TDD is otherwise known as the “red, green, refactor” cycle method, working in small increments to get your tests to pass. The red represents the unit test you write that fail at first, the green is the production code you write to get the test to pass as soon as possible, and refactor, well, is pretty much self explanatory. The cycle should last no more than ten minutes, and the idea is that you are staying succinct in what you want for your features, and you can gain confidence in refactoring because you know that everything will work as long as it continues passing the regression tests.
That being said, the resulting production code should still follow standard software engineering conventions such as KISS (keep it simple, stupid), and DRY (don’t repeat yourself). Additionally, writing good tests takes practice, and should follow the FIRST acronym (fast, independent, repeatable, self-validating, timely) as mentioned in Hacker Noon’s article.
There are two types of tests including unit tests and integration tests. Simply put, unit tests ensure that each feature works independently, whereas integration tests check if all the features work together in a simulation of the execution environment.
Getting Started with Writing Tests
To write and run tests with your code, you can choose from many excellent tools. There is an abundance of options out there, so I would suggest asking your peers or coworkers what their companies use and get started that way. It depends on what language you use, what features you want, and what your budget is. For Ruby, many use RSpec, which is what I will be using in the example below.
If you are just getting started with TDD, I recommend following a tutorial. I started following the tutorial on Upcase created and led by Thoughtbot developers Harry Schwartz and Ian Anderson. I was inspired by their tutorial for the following example.
For our example, we are going to create a calculator. Specifically, we want to create a new instance of a calculator that has the functionality to subtract two given numbers.
So let’s start by writing the tests. Schwartz and Anderson recommend writing at least two tests for a specific function to ensure that our code works as intended.
describe Calculator do
describe "#sub" # => instance method, just a string
it "returns the difference of its two arguments" do
calc = Calculator.new expect(calc.sub(10,2)).to eq(8)
end it "returns the difference of two different arguments" do
calc = Calculator.new
expect(calc.sub(5, 1).to eq(4)
So we’ve written the tests, and as you may notice, a lot of the tests are just strings. That is so you, the developer, can have flexibility with your tests. The main keywords of RSpec include
eq() . The tests should fail, and the command line should print
uninitialized constant Calculator.
We do not have a
Calculator class. So now, let’s write the production code to make the firs test pass! Remember, we are working in tiny increments, making one test pass at a time. Try not to let your ego get ahead of you — write the tests, and write the minimal code to make the tests pass.
Now our error reads
undefined method 'sub' for #<Calculator> . So, there is no instance method
sub for our
Calculator class. Let’s write it in. Again, one thing at a time, let’s make this test pass.
def sub(a, b)
Our next error:
expect(calc.sub(10, 2)).to eq(8).
Evidently, we could make this code pass by hardcoding the value
8 in the method
sub, but what about our second test? Well, we could write a conditional to return a certain value given two specific inputs, but let’s just give the people what they want. If you know an elegant reusable method off-hand, then use it. If not, then you can always refactor later.
def sub(a, b)
a - b
Ta-da! Our tests pass, and everything is green.
Of course, this was a short introduction with an extremely simple example of the TDD method. Hopefully, you gained a small introduction to what the TDD process is like. Testing does take a bit of practice and investment in time, but it is all the more worth it to write cleaner code. As you practice more, the better you will get, and the more purposeful your tests will be. I will be continuing my tutorial on Upcase and will continue to share more about my journey here.