How I broke the code (and how I fixed it)
A couple of months ago, I broke the code. There, I wrote it out loud, out of my chest. How? Why? In order to tell you about it, I need to go back to that moment, when it all happened.
The new feature
The project was the typical web app with an admin area to edit content. Like any good old legacy project, it’s something that will probably go on live for a while, whilst new features, or some functionality extensions, will be asked, and some lucky developer will have to delve deep into unknown territory and work them through.
The project architecture was well structured. The code was easy to read and it had some tests, mostly Full Stack Tests, with Cucumber. No pain there. The rails version was (Rails 3.0.3) and the ruby version 1.8.7.
Despite the “version limitation”, you can pull out an interesting challenge and test your engineering skills (I guess that’s what historians tell themselves while trying to solve something written by someone 300 hundred years ago). How can you add more functionality, working with old versions of the framework and runtime, without breaking what’s already running, or anything that you might add as well?
Tests and best practices!
RESTFul for the wicked
Nowadays, most of the running web apps, have integrations with third party apps. From authentication to sharing data, through a well known public API. In this case, the requested feature was the development of a RESTFul API to expose the web apps data. After a bit research and trying some approaches, I ended up with something like this:
Rails is great to setup an API that it is easy to read and understand with the routes expressive DSL.
Rails 5 support the api option that allows developers to build a rails app API, without rails full functionality.
APIS need to have a version associated to it because once out there, they will be consumed by somebody. If the API needs to be changed, we can keep the previous version, and develop a new one, side by side.
The controller for the API looked like this.
I’m not covering the security issues around developing an API since it is outside of the scope of this post but it should be definitely be addressed when developing an API, as well as CORS.
Everything was going great and all, but then, I missed something, the integration tests, or any tests at all for that matter.
Why did I miss the tests? At the time I was developing the API, I was in a deep load of work and I thought (wrongly) that I just needed to get things done. I thought I didn’t have time to write tests, so I limited the testing to manual testing.
The endpoints were developed, manually tested (creeps) and the feature was closed.
The nail in the coffin
Some time later, the client asked for new features, yet he wanted these have the same authorization logic used in the admin area. While fiddling around the app, I noticed that there was a controller that implemented this kind of authorization control for other endpoints. I thought to myself: “Nice!”. I’m adding new RESTFul endpoints, I can use the previous controller and have it inherit from the controller that has the authorization logic. Unbeknownst to me was what that the other controller had more than just the latter.
So I developed the new endpoints and ended up having something like this:
All right! The new endpoints sharing the same authorization code. Everything seemed fine, again with manual testing. So let us just deploy!
At the office we had a peer testing sanity check on a staging environment. Someone that is not related with the project, will randomly test both new and old features from the project. Thankfully one of the team mates noticed the bug: the authorization code was also being enforced on the previous endpoints, where I did not want authorization for the moment.
I started off with the right ideas and a good practice. I isolated the new feature but didn’t write the much needed tests. If time was an excuse, that will bite you later. More time will be lost looking and solving a bug, when the code breaks. If there’s something that prevents you from actually developing the tests, then you need to address those issues first. Easier said than done, but better do it than fail later spectacularly.
We all know the mantra.
- See it fail
- Do that again until it passes.
In my case I wanted to test the endpoints call and check if the JSON return was the correct one, as well as the status HTTP code.
In the example, I used the RSPEC. My choice here was due to my familiarity and experience with the test framework.
Making sure that the right output is being exposed through your API is primal. JSON Schema, just like XML schema but better and less verbose, enforces the structure correctness. I used the rspec json schema gem to implement tests that validate the structure.
I ended up having something like this.
I could’ve made up that I had the tests, or that my mistake was not this naive but I didn’t. It’s better to be honest when mistakes are made. It’s a better learning process to understand what went wrong, to avoid doing the same in the future, no matter how experienced you are and the “excuse” you come up with. It’s a far more interesting story than the other way around.
If the original project didn’t have any tests at all, it’s never too late to start doing your own, for the project. Let that not be an excuse.
If you are in a deep load of work and you “can’t” implement tests, something is wrong and it should be addressed first. It may sound easier said than done but it needs to be done. Tests take time but they make you think about the system, more than you’d think, in my own experience, and in the short run, you will start seeing the gains from it. Less bugs, more quality, happy people.
You can find a Rails API project example here, for different rails versions:
with API tests.
There are a few topics that I didn’t cover here like data consistency tests, Endpoint security, CORS, controller tests and CI. Those will be fodder for other upcoming posts.