Occasionally I like command line tools to also say the output, in addition to ‘printing’ it (to STDOUT).
A current context is starting a server locally with updated information. The start up takes time, so I tend to not wait at the command line for it to fully boot.
I wanted a noticeable signal when the thing is up – and also a confirmation that the version of the information source *is* actually updated. The commands tee and (the macOS) say do this for me:
tee >(say&)
tee copies the output of its input to STDOUT (usually the terminal) and the given file. The construction >(other command&) causes tee to send the input not to a file but to othercommand and the & causes that command to return immediately (instead of returning only after the command to finish).
Overall, I do something like this. The system returns a lengthy JSON string, so I use jq to get to the version information I’m looking for:
In most of my projects, I use the command line a lot. My colleague Marcus Franke pointed me to autojump, which allows you to quickly navigate directory structures, as well as open folders in a GUI tool like the Finder (or Pathfinder) on a Mac (and I’m sure it works similarly on Windows & Linux).
As the project page says:
autojump is a faster way to navigate your filesystem. It works by maintaining a database of the directories you use the most from the command line.
I use a Mac (most of the time) as well as Homebrew, so the installation of autojump was simple:
brew install autojump
After using cd to navigate to my most visited directories to ‘train’ auto jump, I can use ‘j d’ to jump into my dev folder, which is where I keep all my project directories:
$ j d
/Users/stephan/dev
~/dev $
Yes, it’s just three key strokes: ‘j’, space and ‘d’. I like this quick way to jump between directories, because it’s a lot less typing (compared to entering the real path names) and moving back and forth in the directory structure is pretty easy and fast.
Automating tests/checks, seems more valuable than having automated tests—like planning being more valuable than having the plan.
In my case the automating certainly led to other interesting results — and that’s the topic of this post.
Some context
Let’s assume we’re using Cucumber to automate tests for some application and follow the typical Cucumber setup: A folder features containing the feature files and inside of that another folder (step_definitions) containing, well, the step definitions. A support folder inside the feature directory keeps supporting files, such as environment configuration(s) etc.
Keeping it clean
The way I started to work is to start simple: At first I put everything inside the step definitions. Pretty soon though, I extract code chunks into methods of their own. And then move those method definitions into their own class (or module) – an in fact a file of their own.
That way, I end up with two more levels of abstraction below the feature descriptions and the step definitions: Classes (or modules) containing method definitions (see picture).
Here I follow Uncle Bob Martin‘s advice of “Extract till you drop” (see his book ‘Clean Code‘, and episode 3 (Functions) of the video series at cleancoders.com): Extract methods until there’s nothing left to extract (and extract that code into the right place).
A first result: A high level interface to the system
One of the results of doing this, is a really high level and clean API to the system at hand. In addition to that I find that step definitions are much easier to understand, since they (mostly) contain a business level description of how the system is used and accompanying assertions to check whether the system behaves as it should.
Enter Pry
Pry is a command line tool for Ruby, like the interactive ruby shell irb that comes with Ruby. However, Pry is a lot more powerful and offers to display method definitions (even those defined in the C sources of internal Ruby classes) and a whole lot more. For a good introduction see the Pry Screencast by Josh Cheek on vimeo.
A trivial and contrived example
Let’s assume we’re trying to find stuff on the web (I’m on a Mac; there are hints about how to achieve this on Windows in the code comments):
require 'safariwatir' # Windows: require 'watir'
class GoogleSearch
def initialize
@browser = Watir::Safari.new # Win: Watir::Browser.new
end
def search_for search_term
@browser.goto 'http://wellknown-search-engine.example.com/'
@browser.text_field( :name, 'q' ).set search_term
@browser.button( :id, 'gbqfb' ).click
@browser.ol( :id, 'rso' ).links.map(&:text)
end
end
Given that interface and Ppy, the creation of a console app borders on being trivial (yet, it took me a long while to get to this point):
Think about this for a moment: Take Pry and your API and in 3 lines of code you get a console app for your system. The console can be used like this:
[10] stephan@nibur … #ruby google_search_console.rb
[1] pry(#<GoogleSearch>):1> search_for 'pry video josh'
=> ['Pry Screencast on Vimeo',
# ...
As mentioned above, you can even display the method definition (including lines of code, file name etc.)
[8] pry(<GoogleSearch>)> show-method search_for
From: /Users/stephan/…/google_search.rb @ line 8:
Number of lines: 6
Owner: GoogleSearch
Visibility: public
def search_for search_term
@browser.goto 'http://google.de/'
@browser.text_field( :name, 'q' ).set search_term
@browser.button( :id, 'gbqfb' ).click
@browser.ol( :id, 'rso' ).links.map(&amp;amp;:text)
end
[9] pry(#&amp;lt;GoogleSearch&amp;gt;)&amp;gt;…
Leaving thoughts
It seems that doing something can be more valuable that having the result. Notwithstanding, we still need to produce something of value for our business.
I came to realise that clean code matters more than ‘only’ providing a code base that’s easy to understand and change: Without the high level API, I might not even have noticed the possibility to create a ‘business level console’.
Having a console app like this allows to prepare starting points for exploratory testing (that might be ‘automation assisted exploring). Or short: I think a clean and high-level interface plus Pry is a cool tool for exploration.