
Why testing your code is crucial (and what got me to finally believe this)
When I first began coding, I never really understood what the big deal was around testing. I’d dabbled around a little bit with RSpec, Capybara, and Jasmine while working on a few personal projects but, frankly, never really got why everyone was so gung-ho about testing — I found that it got in the way of cranking out code, which I was so eager to do.
I did a lot of reading on testing and nothing I came across really convinced me to jump on the bandwagon. It wasn’t until recently, that I’ve come to fully love and appreciate testing. This blog post is my attempt to frame testing in a slightly different manner and in a way that was helpful for me.
Testing enables you to better scale your application
When I first started writing code and working on personal projects, my applications were quite small and simple — I could get away with manually testing by clicking through my web apps (sometimes dropping in pry or a debugger here and there) to make sure that everything worked as it should.
However, at my company, our stack currently consists of a Rails backend with 19 controllers, 45 models, 6 modules, 24 other classes in our lib folder, plus more. On our front end, we’re using Angular with 17 controllers and a number of custom directives, factories, and filters. We also integrate with a number of third party services.
What does all this mean? A small change in one part of our application can easily have an unexpected impact on some other part(s) of the application. That impact might be a crash of the entire application or a small bug that has a smaller impact. This problem is exacerbated when you’re working with a team because you may not be aware of a change someone else made somewhere else in the app that might impact your work. Time also exacerbates the problem because as it passes, developers tend to forget the dependencies that certain methods, functions, classes, and modules have with one another.
Bugs that result from modifications, deletions, and additions are inevitable; however, testing allows developers to catch these bugs as soon as they happen anywhere in their application.
Sure, you can always manually run through your application, click on every link, fill out every form, try any and all combination of potential user actions to test your app after you make changes; however, once the application starts growing, this begins to take up a lot of time.
Yes, testing does take a little more time upfront to write test suites (which, I find is a huge barrier to entry for many developers); however, once the tests are written, one can test an entire application end-to-end by just hitting a button — compare that to the five or more minutes it takes (every single time you make a change!) to try and recreate absolutely everything a user could do on your app — and you are still likely to miss a lot this way as not all bugs are immediately visible on the surface of an application.
Testing helps you clarify what you really want your app to do
I recently heard someone compare testing to a shopping list. As you may know, going to the grocery store without a shopping list can be dangerous — you end up buying all the things you fancy and forget the things you really need. Testing is the same way — it forces you to sit down, before you write your code, and figure out what you really need your methods to do once you start writing your code.
Writing tests prior to actually writing your application code makes you take a step back and really think about what you need each input and output to be for every method. Without testing, it is really easy to start building methods that you don’t really need or that require more than they should.
Testing forces you to write better code
The last reason why testing is so crucial is because it forces you to write better code. It is often tempting to write long methods that enable you to get from point A to point B while handling a number of different responsibilities. For example, imagine a method, scrape_document_and_save, that scrapes a page for html (using a tool like Nokogiri), parses that document to pull out relevant information, and then saves that information to the database. Such a method is quite easy and quick to write but it violates the Single Responsibility Principle for methods that roughly states that all methods should have one and only one responsibility.
Testing generally involves looking a method’s output given a set of inputs — if the test fails (i.e., the output was not what we expected), then we know there is an issue with that method and that we need to look into it. However, with the example given above, if the test fails, we don’t know if the failure was related to the scraper, the parser, or the attempt to save to the database. Testing forces us to make each of these three distinct functions a distinct method (or module/class) so that we can easily pinpoint exactly what the error was immediately.