Testing Your Code’s Text
The “Ubiquitous Automation” chapter of The Pragmatic Programmer opens with the following quote:
Civilization advances by extending the number of important operations we can perform without thinking.
–Alfred North Whitehead
As a responsible and accomplished developer, when you encounter a bug in your application, what’s the first thing you do? Write a failing test case, of course, and only once that’s done do you focus on fixing the problem. But what about when the bug is not related to the behavior of your application, but rather to its configuration, display, or some other element outside the purview of normal testing practices? I contend that you can and should still write a failing test.
Scenario: In the process of merging a topic branch into master, you encounter a conflict in one of your ERB files. You fix the conflict and commit the resolution, run the test suite, and then deploy your changes to production. An hour later, you receive an urgent email from your client wondering what happened to the footer of their site. As it turns out, there were two conflicts in the file, and you only fixed the first, committing the conflict artifacts of the second into the repo.
Your gut instinct is to zip off a quick fix, write a self-deprecating commit message, and act like the whole thing never happened. But consider writing a rake task like this:
namespace :preflight do task :git_conflict do paths = `grep -lir '<<<\\|>>>' app lib config`.split(/\n/) if paths.any? puts "\ERROR: Found git conflict artifacts in the following files\n\n" paths.each {|path| puts " - #{path}" } exit 1 end end end
This task greps through your app
, lib
, and config
directories looking for occurrences of <<<
or >>>
and, if it finds any, prints a list of the offending files and exits with an error. Hook this into the rake task run by your continuous integration server and never worry about accidentally deploying errant git artifacts again:
namespace :preflight do task :default do Rake::Task['cover:ensure'].invoke Rake::Task['preflight:all'].invoke end task :all do Rake::Task['preflight:git_conflict'].invoke end task :git_conflict do paths = `grep -lir '<<<\\|>>>' app lib config`.split(/\n/) if paths.any? puts "\ERROR: Found git conflict artifacts in the following files\n\n" paths.each {|path| puts " - #{path}" } exit 1 end end end Rake::Task['cruise'].clear task :cruise => 'preflight:default'
We’ve used this technique to keep our deployment configuration in order, to ensure that we’re maintaining best practices, and to keep our applications in shape as they grow and team members change. Think of it as documentation taken to the next level – text to explain the best practice, code to enforce it. Assuming you’re diligent about running your tests, every one of these tasks you write is a problem that will never make it to production.