Testers Write Tools — Sometimes With Gems!

I recently talked about testers writing tools. That particular tool, however, was a simple script. You can definitely improve your career options by being able to write more substantive tools.

As a tester I’ve recently had to start writing some Ruby programs that would serve as effective test frameworks. As it turns out, it was best for me to package up my logic in the form of a Ruby gem so that I could allow others to install it easily along with any dependencies.

So if you have a test framework and you’ve wrapped it up as a gem, you have an easy way to only provide a robust framework to your team but also a way to introduce good development practices into your test team by working on your own internal project. Further, you can allow your test team to see what developers go through as part of their development, packaging, and distribution process.

What I’ll do here is take you through the barest essentials for getting started on the process of creating your own gem. There are lots of sites out there that talk about this to varying levels of detail. Honestly, I’m probably not offering anything new. But writing this out has helped me focus my thoughts and I’m hoping it might do the same for others. Incidentally, I’m doing all of this on Windows because many testers are using a Windows environment and there’s this perception out there that a lot of development can only be done on Linux environments. (In fact, Ruby developers tend to have a focus on the Mac environment.)

So, the distribution system for Ruby is called Rubygems. Using this system you can wrap up all of your logic into a gem file and have that available to your team. This is somewhat similar to how you would distribute an assembly (either .exe or .dll) in .NET or a .jar file in Java.

Creating a Ruby gem is actually really easy to do. Many tutorials about writing gems recommend using supporting tools like Jeweler, Hoe, or Bundler to create the structure of the gem for you. You can do that or you can also build the gem structure from scratch. Given that Bundler provides such a minimal framework — almost as minimal as just creating it from scratch, I decided to go with that. I did investigate Jeweler a bit, and I know there are “wars” about which is better, but I personally found Bundler more attractive for my needs. You should investigate the options to see what suits your own needs.

Gem names are sort of like the domain names of web sites. Once one is taken, you can’t use it. This won’t matter to you if all you are doing is creating a local gem that you will distribute to others in your own way and not through the Rubygems service. But if you want to publish your gem to Rubygems.org, then you are going to need to pick a name that doesn’t exist. For my purposes here, while I’ll indicate how you can publish your own gem, I won’t actually be doing that with this post so feel free to use whatever name you want.

You are going to need to have Ruby installed. I use Ruby 1.9.2 at the time of writing and I’m just about ready to switch to 1.9.3. You will also need to make sure you have the bundler gem installed. If you are at the point where you want to be creating your own gem, I’m going to have to assume you know how to get yourself a working Ruby environment.

To begin to create a gem using Bundler, use the bundle gem command like this:

$ bundle gem tester_fun

This command creates a scaffold directory for the new gem and if Git is installed on your machine, it initializes a Git repository in this directory.

I would recommend you have Git installed. Not only has the Ruby community rallied around Git but if you want to host any of your working solutions on Github, then obviously having Git would be a really good thing for you. The msysGit distribution works wonderfully on Windows.

After you run the above command, you’ll see output like this:

  create  tester_fun/Gemfile
  create  tester_fun/Rakefile
  create  tester_fun/.gitignore
  create  tester_fun/tester_fun.gemspec
  create  tester_fun/lib/tester_fun.rb
  create  tester_fun/lib/tester_fun/version.rb
Initializating git repo in c:/Projects/tester_fun

Putting that in a slightly more directory-focused way, this gets you:

\tester_fun
  .gitignore
  Gemfile
  Rakefile
  tester_fun.gemspec

  \lib
    tester_fun.rb

    \tester_fun
      version.rb

Now, let’s say you were creating your gem from scratch, rather than using Bundler as I just did. In that case, the main file you would be creating is the gemspec file. This file contains information about the gem and it’s absolutely essential that it exist. It’s just what it sounds like: the specification for the gem. Without that, you don’t have a gem. So let’s do this: go ahead and delete that gemspec file. Get rid of it, I say! (Or, at the very least, just rename it.) We’re going to recreate that file from scratch so that you can understand what’s going on with it.

Got it? Have you crated a new, blank tester_fun.gemspec yet?

Okay, so, at a bare minimum your gemspec file would have to contain something like this:

Make sure you put that in there. Now that you’ve written some of the gemspec, you can build your gem.

$ gem build tester_fun.gemspec

This command will package your gem project into a gem file that can be used to install the gem. This file will have the structure gem_name-version.gem. The gem name and version are being pulled from the gemspec file. So the file I get is tester_fun-0.0.1.gem.

When you ran Bundler created a Rake file for you, that Rake file also contains tasks that let you build the gem that way as well. Try this:

$ rake build

The only difference here is that the Rake task puts the gem in a pkg directory.

If you’ve done anything at all with Ruby, you’ve most likely had to install a gem or two. Well, you can actually install your own gem right now:

$ gem install tester_fun-0.0.1.gem

Again, since Bundler created a Rake file for you, that Rake file also contains tasks that let you install the gem. Try this:

$ rake install

Both commands work the same. Let’s check what actually got installed, though. First check out your gem environment:

gem env

Doing this shows you the environment where your gem information gets stored. Check out the INSTALLATION_DIRECTORY in particular. This will tell you where the gems are going to be installed. My gem is specifically stored here:

C:\Ruby192\lib\ruby\gems\1.9.1\gems\tester_fun-0.0.1

Were you to check that directory, you’d notice there’s no source code at all. Now, that may or may not seem odd to you depending on what you expected. We actually do have source code: the tester_fun.rb and version.rb file in the lib directory. The reason this wasn’t carried over and installed with the gem is because rubygems doesn’t know what files to package into the gem. To fix that, let’s tell the gemspec what files need to be added. A slight complicating element is that how you do this depends on whether you have Git installed. The added lines are below, but note you’d only add one of them:

Now build and install the gem again. Upon doing that you should seem some source files in your gem’s installation directory. I should note that the relevant command for adding files would have been put in place by Bundler. So normally you wouldn’t have to do that unless you’re choosing to build your gemspec from scratch.

Speaking of that, if you were starting from scratch, at this point you’d create a directory called lib and then put a file in there called tester_fun.rb. Since you ran the Bundler command, however, this was done for you. This file is a starting point for your own code. In fact, at this point let’s consider the generated files.

  • Gemfile: This file is used to manage dependencies for the development of your code. This file contains nothing more than a ‘gemspec’ statement. This statement will look inside the Gemspec file for any dependencies specified and load them through Bundler. Why this file? It’s generally a good practice to manage the gem’s dependencies inside the Gemspec file and let Bundler load them automatically through the Gemfile. For example if you want to use RSpec to test your gem, then instead of adding a reference to RSpec in the Gemfile I would add it as a development dependency in the Gemspec file. Since the Gemspec file is referenced through the Gemfile this makes it possible to run bundle to ensure that you have all the required gems installed.
  • Gemfile.lock: If you use an IDE, like RubyMine, you might get this file at the start. Otherwise, you will get this when you run ‘bundle install’ or ‘bundle lock’. When you run ‘bundle install’, Bundler installs any gems specified in your Gemfile (or your Gemspec, if you go that route) and records the version number for each one in Gemfile.lock. Why this file? The presence of this file means Bundler now has a record of the gem version of each dependency it is managing and it can check the gem repository against this record when running commands in the future. Think of it as a way to lock in dependencies and to always make sure that the minimum dependencies are available.
  • Rakefile: The Rakefile adds some gem helper tasks — build, install, and release — from Bundler. The build task will build the current version of the gem and store it in a pkg folder. The install task will build and install the gem to the local system (similar to issuing a “gem install” command). The release task will push the gem to Rubygems. You’ve seen two of those in action already. Why this file? Rakefiles are the build files of the Ruby world, similar to Ant’s build.xml or Maven’s pom.xml in Java development or the makefile of the Unix/Linux world.
  • .gitignore: You’ll only get this if you have Git installed. This file contains the files that Git should ignore when versioning files. By default, anything in the pkg directory is ignored and anything with a .gem extension is ignored. So whether you used rake build or gem build, the resulting gem file would not be versioned. Adding whatever exclusions you want to .gitignore as soon as possible is good idea. Why this file? It’s simply a way to avoid versioning files. As a good example, if you are setting up a project in an IDE (Komodo, RubyMine, Eclipse, whatever), you might want to make sure any IDE tool artifacts are accounted for in this file.
  • .gemspec: This is the Gem Specification file, containing all of the metadata about the gem. This is information that is used by services like Rubygems. This is also where you specify the dependencies the gem needs in order to run. Why this file? Because without this file, you don’t have a gem!

Those are the primary files generated that are separate from your code. Bundler does give you a head start on the code structure of your gem as well. Let’s at least go over what it creates in that area as well.

  • lib/tester_fun: A convention which is important to follow when creating gems is to keep all your library classes inside a folder named after the gem. This folder should contain all the code for the gem.
  • lib/tester_fun.rb: This is the file that will be required by Bundler when the gem is loaded. This file defines a module which can provide a namespace for all the gem’s code. In our case, that namespace is TesterFun. This is the file that will be loaded when someone requires the gem once they have installed it. What that will do is put your gem’s lib path into the load path of anyone who installs your gem. This means your code is now in their namespace. To avoid running into conflicts with stuff that’s already in that namespace, it’s important to put all the classes within the gem in an enclosing namespace module.
  • lib/tester_fun/version.rb: This file defines a namespace constant (TesterFun) and within that a VERSION constant. This file is loaded by the Gemspec to specify a version for the gem specification. When a new version of the gem is released, this version number should be incremented to indicate to Rubygems that a new version is available.

There’s your base files and your project layout for your gem.

Regarding that VERSION constant that I mentioned, since I had you redefine your gemspec, you should now change that to be how Bundler gave it to you originally. Change this line:

to

However, in order to do this, your gemspec file has to know where that constant is. So add these lines to the top of your gemspec file:

The first line adds the lib/ directory of your gem to Ruby’s load path, so you can include the files within that directory relative to your load path.

Gems not only provide libraries of code, but they can also provide one or more executable files to the shell’s PATH environment variable. (The shell here would be Bash or whatever in Linux, the DOS shell in Windows, or the Terminal in a Mac.) Probably the best known example of a gem with an executable is one we’ve just used: rake. Cucumber provides the same thing. Adding executables is a simple process, you just need a file in a bin/ directory and then you indicate in the gemspec that you have executables to distribute. I’ve shown this below, in two different formats:

Pick whichever way you prefer.

If your gem depends on another gem in order to work correctly, then you have to include an add_dependency statement in the gemspec. If you use certain gems when developing the gem but users will not require those gems, then you can include an add_development_dependency statement in the gemspec. So, as an example, let’s say you were writing a testing tool that will use both watir-webdriver and selenium-webdriver. These are things that users of your gem will need to have in place. You’d add the following to your gemspec:

Ruby Stuff

The command add_dependency is an alias for add_runtime_dependency. You’ll sometimes see examples where one or the other is used.

The version numbers are optional. There is a notation around how you include what particular version and I won’t get into that here. Suffice it to say that the above basically says the gem requires versions of watir-webdriver and selenium-webdriver that either match those versions or are greater than those versions.

Let’s say that as part of developing your gem, you use rspec and cucumber to perform build testing and you use simplecov to provide measures of test coverage. In that case, you’d add the following to your gemspec:

Keep in mind that these are not gems that are required to use your gem. They are gems that are required for people who want to develop your gem. The overall idea here is that the gemspec allows you to specify both runtime and development dependencies. Runtime dependencies are what the users of your gem will need installed with the gem in order to use it. Development dependencies are used by developers working on your gem.

You can run the following command:

$ bundle install

This will cause Bundler to read your gem, get any dependency information you have specified, and then install the gem dependencies if required. This will also cause the Gemfile.lock to be created. If you change the dependencies at some point, then you’d run this command:

$ bundle update

This will cause the gems to be updated and the Gemfile.lock file to be regenerated with any necessary changes.

All this being said, note that if you used Bundler to create your gem you would have just stuck with the generated gemspec file and made your modifications accordingly. What I wanted to do is show you a bit about how this process works. And obviously this is just the start. Clearly the usefulness of a gem will be in the code logic that exists in that lib directory. I’m not going to be getting into that here simply because that all depends on the type of tool you want to build.

But think about what you can do now! You’ve just started to develop your own project.

In fact, let’s go a step further. If you sign up for an account on Github — which is free — and if you are using Git, you can create a repository on the site. Let’s say you set up a repository called tester_fun. If I did that, I could then run the following command in my tester_fun directory:

$ git remote add origin git@github.com:jeffnyman/tester_fun.git

This tells Git that I have a remote server called origin for this repository. So now let’s say you’ve done the above in terms of creating your gem files. You would have to add those files and have at least one commit. (I’m not really teaching Git here, so I’m skipping over lots of stuff.) You could then run this:

$ git push -u origin master

This command pushes the named branch to that remote server, and the -u option tells Git to always pull from this remote server for this branch unless told differently.

Think about what you can do now! You’ve not only started to develop your own project but you’ve got it under version control and you’re able to distribute it out there for others to look at. Even if you’re just doing this for fun, you’re enhancing your career by knowing how to do this.

One last point. Rubygems.org is the place where most Ruby gems are hosted. Whenever you do “gem install some_gem” you’re most likely installing a gem that’s hosted on rubygems.org. Rubygems makes it incredibly easy to make your gem available for anyone to install. As with Github, if you don’t already have an account on rubygems.org, create one. Again, it’s free. Now if you wanted to release your gem to the world you could do:

$ gem push

or, using the Rake task:

$ rake release

This will push your gem out to the world.

Think about what you can do now! You’ve not only started to develop your own project, not only got it under version control, and not only have you distributed it to a remote server so others can work on it with you, but you’ve created an actual Ruby gem that can be installed just like any other gem. Pretty cool stuff.

I don’t think testers have to be developers in the strict sense. But I do think that testers will have to be able to develop testing tools. I also know for a fact that testers will have to interact with developers at all times. As such, it doesn’t hurt to step into developer shoes and at least go through the procedures they go through as they build solutions. Beyond that, who knows, maybe I’ll be using your gem when I need the next great testing tool.

Share

About Jeff Nyman

Anything I put here is an approximation of the truth. You’re getting a particular view of myself … and it’s the view I’m choosing to present to you. If you’ve never met me before in person, please realize I’m not the same in person as I am in writing. That’s because I can only put part of myself down into words.

If you have met me before in person then I’d ask you to consider that the view you’ve formed that way and the view you come to by reading what I say here may, in fact, both be true. I’d advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.

This entry was posted in Testing. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *