TDD and Vim
When I first started working in Vim, reading Bram Moolenar’s Seven Habits of Effective Text Editing was very helpful. In it he outlines three fundamental tips for gradually improving one’s efficacy in the editor.
While you are editing, keep an eye out for actions you repeat and/or spend quite a bit of time on
Find out if there is an editor command that will do this action quicker. Read the documentation, ask a friend, look at how others do this.
Train using the command. Do this until your fingers type it without thinking.
These are so basic, yet incredibly profound. In Vim there is always a quicker way to do things and that doesn’t just apply to editing text in a conventional sense, any adjacent processes can also be optimized.
Consider the process of TDD. There are plenty of ways we can use Vim to make the Red, Green, Refactor loop as tight and fast as possible. Let’s take a look at each stage of the cycle and delve into some of those potential optimizations.
RED - Writing a Failing Test
If we’re writing tests, there’s going to be some basic boilerplate to write. In the case of BDD style frameworks like Rspec and Speclj, we know we are going to be frequently writing code that looks like this:
(describe "The App"
(context "Starting it up"
(it "loads the thing we expect it to load"
(let [something {:this "and that"}]
(should= something something_else)))))
While it’s not a ton of boilerplate, we’ll probably be repeating it a fair amount. Let’s make things easier by writing some basic
snippets that will significantly cut down on the amount of time we need to create this boilerplate. One really easy way to
do this by creating some abbreviations in our .vimrc
file.
:iabbrev dci (describe " " <CR> (context " " <CR> (it " " <CR> #body here#)))
Now simply by typing dci
in insert mode we can create much of the boilerplate for our tests.
(describe " "
(context " "
(it " "
#body here#)))
Considering that we will probably want multiple it blocks per context and multiple context blocks per describe, we might also add the following abbreviations as well:
:iabbrev ci (context " " <CR> (it " " <CR> #body here#)))
:iabbrev ib (it " " <CR> #body here#)))
We can also specify the what types of files these abbreviations work in using an autocommand.
"" au is just an abbreviation of autocmnd
:au FileType clojure :iabbrev dci (describe " " <CR> (context " " <CR> (it " " <CR> #body here#)))
:au FileType ruby :iabbrev dci describe " " do <CR> context " " do <CR> it " " do #body here# <CR> end <CR> end <CR> end
By adding autocommands to to our abbreviations we can ensure that our abbreviations are only expanded in the file type we want. Also, we can now have the same abbreviation for multiple file types, but with different behavior.
Abbreviations work well for creating quick on the fly expansions for text you write frequently, but for boilerplate code, they are, at best, a stop-gap solution. Much better options exist in the form of Plugins like SnipMate, and UtiliSnips that were explicitly created to help Vim users quickly and easily create small reusable “snippets” of code. I’d recommend starting off with the abbreviations first though, they might turn out to be more than enough.
GREEN - Passing The Failing Test
Alright! we’ve now written our first failing test. How can we get to ‘Green’ as quickly as possible? First, we need to actually determine that our current test failed, which means two things.
- We want the time between writing our tests and running them to be as short as possible.
- We want to verify that our test failed with minimal effort.
This is a common workflow I’ve seen and have used before:
- write a failing test
- hit
ctrl z
to ‘pause’ Vim and go back to the terminal - type in test command, something like
lein test
or hit the up arrow to get the most recent command - hit enter
- wait for the test to run
- look at the results
- hit
fg
to return to Vim - probably switch back and forht a couple more times to reference the error message.
Not bad, but there’s a lot of room for error, and all things considered this approach is pretty slow. Ideally, our tests should already have been run before we even have had time to think. Also, wouldn’t it be nice to be able to see our fialing test output without needing to switch contexts all the time? With the aid of a terminal multiplexer like tmux (though any will do) we can do both with out much fuss.
Setting Up The Multiplexer
install tmux and run the following commands:
#create a tmux session
tmux
#open up split
#the tmux prefix key is <Ctrl b> by default
#vertical
<prefix> %
#horizontal
<prefix> "
#resize the split with option and arrow keys
<prefix> ⌥ <left> <right> <up> <down>
Once you have things configured the way you want open up Vim in one of the windows. Personally I like to have Vim on the left with a narrow column on the right displaying test output.
Sweet now we can see our code and test output on the same screen! That’s a huge step forward from the workflow I described earlier, but
we can still make it better. It can be difficult and to switch between, and keep straight Vim and tmux commands. I like to think of tmux
as a sort of extension to Vim, so I added the following lines to my .tmux.conf
to make things a bit easier and more Vim like.
setw -g mode-keys vi
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
Now I can use Vim naviagtion keys in tmux. I just have to prepend each one with the tmux prefix.
Even though we’ve made moving around tmux a breeze, we don’t want to have to switch over to our other tmux pane just to run our tests. Ideally, we should never have to leave Vim. We ought to just be able to glance over, see the error message, and then go right back to working on making that test pass.
Creating A Test Autorunner
I like for my tests to run as soon as I save the file I’m currently working on. In Vim-speak that’s called writing to the buffer
and it’s known as an autocommand event. Vim includes a multitiude of these types of triggers, including the FileType
event that
we saw when talking about abbrevaitons and snippets. the BufWrite
command is what we’ll use as our autocommand event, but feel free
to checkout out any of the others that are available by typing :help autocommand-events
when in Vim.
Let’s say that we are working in an Elixir project. Here’s an autocommand we can use to run our tests.
autocmd BufWrite *.ex :silent exec "!tmux send-keys -t 1 C-l \"mix test\" Enter" | redraw!
We already know what BufWrite
is about, but there’s a lot going on here, so let’s walk through the rest of it.
*.ex
This says only run this autocommand when the file has a .ex
extension
:silent exec
:silent
will allow us to run the command without needing making us leave Vim and hit enter afterwards. exec
will allow Vim to evaluate the proceeding string as a command.
"!tmux send-keys
This is the beginning of our command. First, !
basically puts us in the terminal, meaning whatever we write after it, is
executed by whatever shell we are running. Essentially, this is the equivalent of switching over to the terminal and typing in
the tmux send-keys
command
-t 1
This is an option passed to the tmux send-keys
command it specifies to which pane the proceeding keys must be sent.
C-l \"mix test"\ Enter"
These are the keys that we are actually sending to pane we specified with the -t
option. C-l
will reload the pane prior
to \"mix text"\ Enter
being sent, so our tests are always run on a fresh screen. \"mix test\" Enter
is the command we would
normally input into the terminal if we wanted our tests to run.
| redraw!
Finally | redraw!
will clear the current Vim screen and redraw it. This is helpful because some times Vim can get in
a funky state after doing an external command. This just ensures we are never bothered by this occasional nuisance.
Awesome! Now whenever we write to a buffer our tests will automatically run and we need only to have them skate across our periphery to ensure they fail or pass.
Quick Aside On Vim’s “!” Operator
Vim is a Unix style program. It does one thing, text editing, exceedingly well. However, because it can interact
with the shell via !
it also has access to anything you can do in the shell. As such, just as we are delegating to a test runner
and tmux to run our tests we can do something similar with any other command line application. For example, we could easily envision
some sort of git workflow in which we never have to leave Vim!
NB: If you don’t like you’re screen real-estate eaten up by a Tmux column, then you might want to try out vim-dispatch. There’s also plenty of Plugins that take care of helping Vim interface with tmux (vimux and tslime) . If you are finding your efficiency hampered by the limitations of the techniques I described above, I’d recommend looking into any of those.
Refactor - Stay Green, but Make Things a Little Nicer
We are green! Now how can we make our code better?
Project-Wide Search and Replace
One of the most common refactorings I find myself making is changing the name of a function. That’s a pretty simple task in Vim if we are only talking about one file, but what If we need to make a change across multiple files? Well, actually it’s pretty easy too, most people just don’t know about it.
:grep foo
:cdo %s/foo/bar/g | update
:grep foo
will find all instances of foo
in your project, and populate them in what is called the quickfix window :cdo
will then take whatever command follows it and apply it to every single one. In our case, the command we want to
apply is a global substitution of foo
with bar
, but it could be anything. After making the changes, we want to save the changes, which
is exactly what the | update
portion does.
NB: The quickfix window isn’t normally visible. To see it just use the :copen
command.
Aside from a few specific use cases here and there, like project-wide serach and replace, much of refactoring comes down to plain old text editing. To that end, I think the single most helpful thing one can do to optimize their refactoring. Is to learn th fundamentals of Vim’s powerful language.
Plain ‘Ol Text Editing
In normal mode, every vim command follows the same structure
<command><number><text-object or motion>
Every text object is created using either a
literally meaning “a” or i
meaning “inner” as well as one of the following:
w, W, s, p, [, ], (, ), {, }, >, <, t, ", ', `
For example, aw
is “a word”, iw
is “inner word”, a}
is “a block” (it contains both { and }) i}
, is “inner block” (it doesn’t contain { or })
and so on… (you can look up the rest with :help text-objects
)
We can then combine text-objects with some basic commands like d
, c
and y
for some powerful functionality.
For instance lets say we want to change an entire block of code, cap
“change a paragraph” should get the job done. Maybe we actually just want to
copy the body of a javascript function between curly-braces yiB
or yi}
will both work. By familiarizing ourselves with Vim’s unique
lexicon we can start to think using this language and come up with even better ways to do things. HIN: :help movement.txt
is an excellent place to start.
Wrapping Things Up
Vim is very well suited to help sofware professionals optimize their craft. For things that we do everyday like TDD it’s helpful to take some time upfront to consider potential changes to our current approach that we might make better. Hopefully, some of the things we discussed above inspire you to take a critical look at your current workflow and try out some new new things.
Even if you aren’t someone who uses Vim regularly, I think Bram’s suggestions still apply. Get in the habit of not only identifying repetition, but also try to make a habit of doing something about it. That mgiht be just writing it down after it dawns on you and then scheduling some time later to find a better way, or just stopping whatever you’re doing and finding a better way right then and there. The point is, as professionals we should never be complacent, and always strive to find better ways of doing things.
Now if you excuse me, I really want to try this out.