Unit testing and test parametrization with RSpec?

Last year I made the switch from developing with Python and Django to working with Ruby and Rails. With that, I also had to learn a new test runner. No more pytest :(.

For some reason, a very popular test runner for testing Rails apps is RSpec. Its domain-specific language (DSL) is written with Behavior Driven Development in mind. Hence its tag line:

Behaviour Driven Development for Ruby. Making TDD Productive and Fun.

I'm really struggling with using RSpec, and I wonder why. Most of the time, I'm fighting the framework instead of enjoying its benefits. So I want to explore these questions and hopefully get some answers how to do this better. This is the first post on this topic.

Testing simple functions

Let's take a simple function that adds two numbers.

# Python
def add(a, b):
    return a + b
# Ruby
def add(a, b)
  a + b

Using pytest, it takes 2 lines of code to test this function with one set of parameters.

def test_add():
    assert add(5, 4) == 9

Using RSpec, this takes me at least 5 loc.

describe '#add' do
  it 'returns the sum of 2 integers' do
    expect(add(5, 4)).to eq(9)

Or 4 if I use the subject shorthand notation.

describe '#add' do
  subject { add(5, 4) }

  it { is_expected.to eq 9 }

That's a lot of words for just a simple assertion.

Test parametrization

Now I want to test not only one set of test parameters, but many. With pytest I use the parametrize marker.

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (5, 4, 9),
    (1, 2, 3),
    (1, 2, 4)
def test_add(a, b, expected):
    assert add(a, b) == expected

In case of a test failure, pytest shows me exactly which parameters lead to the error.

======== FAILURES =========
________ test_add[1-2-4] ______

a = 1, b = 2, expected = 4

    @pytest.mark.parametrize("a, b, expected", [
        (5, 4, 9),
        (1, 2, 4)
    def test_add(a, b, expected):
>       assert add(a, b) == expected
E       assert 3 == 4
E        +  where 3 = add(1, 2)

RSpec 1

A naïve Ruby implementation would be to loop over an array of parameter arrays.

params = [
  [5, 4, 9],
  [1, 2, 3],
  [1, 2, 4]

describe '#add' do
  params.each do |p|
    it 'returns the sum of 2 integers' do
      expect(add(p[0], p[1])).to eq(p[2])

Failure output is not very helpful as I don't see which parameters caused the error.


  1) #add returns the sum of 2 integers
     Failure/Error: expect(add(p[0], p[1])).to eq(p[2])

       expected: 4
            got: 3

       (compared using ==)
     # ./demo02_spec.rb:16:in `block (3 levels) in <top (required)>'

RSpec 2

Another approach, that I found on the internets, includes writing a test function and call that function repeatedly using different parameters.

def test_add(a, b, expected)
  describe '#add' do
    subject { add(a, b) }

    it { is_expected.to eq expected }

test_add 5, 4, 9
test_add 1, 2, 3
test_add 1, 2, 4

The test function looks a little obscure, but the last part is very readable. Also, when a failure occurs, we can see the exact line where it happens.


  1) #add should eq 4
     Failure/Error: it { is_expected.to eq expected }

       expected: 4
            got: 3

       (compared using ==)
     # ./demo03_spec.rb:12:in `block (2 levels) in test_add'


Lastly, there is the rspec-parameterized plugin that provides a syntax that's very close to pytest.

require 'rspec-parameterized'

describe '#add' do
  where(:a, :b, :expected) do
      [4, 5, 9],
      [1, 2, 3],
      [1, 2, 4]

  with_them do
    it 'returns the sum of 2 integers' do
      expect(add(a, b)).to eq expected

Failure output is actually helpful, even if not as clean as pytest:


  1) #add a: 1, b: 2, expected: 4 returns the sum of 2 integers
     Failure/Error: expect(add(a, b)).to eq expected

       expected: 4
            got: 3

       (compared using ==)
     # ./demo05_spec.rb:20:in `block (3 levels) in <top (required)>'

The reason we have not yet introduced this plugin into our test suite at work is that the gem has a few dependencies that have not been updated in many years:


All-in-all, I am still very confused about how to best use RSpec. Testing simple functions includes a lot of boilerplate code that's hard to write and slow to read. Test parameterization is doable but no clear best practice has emerged, yet.

If you can help me out and give me some pointers, or if you can tell me that I'm going at this completely wrong, please mention me on Mastodon (preferred) or Twitter or contact me another way.

Possible future questions include:

Update August 2018

Working a full-time job in an office is something different. Add 3 hours of commute each day, and my life is a lot different than it was a year ago. I firmly believe that I'm about as productive now than I was when I was working fewer hours but alone in my home office. But that's for another day.

More time to and at work means a lot less time for other things, which is something I am still struggling to get used to. Combine that with having moved across the Atlantic, finding and moving into a new apartment, and dealing with German bureaucracy! All things considered, I guess I'm dealing with it pretty well.

Still, I had to pause a lot of activities during the last four months, including this blog. As I'm learning a new programming language (Ruby) and a new web framework (Rails), there should be enough fun topics to write about (There are). Just like last year with Django and software testing. But who has the time? Not me at the moment. At least I played my first gig with my old band two weeks ago. That was good, and it won't be the last time.

Lastly, in case you're reading this and wonder why, after almost four months back in Germany, I haven't managed to come visit you: I'm sorry, I'm working on it. In the meantime, our guestroom is large, has an even larger bed and is open to you anytime!

Moving back to Germany

My search for a new job has been successful. I'm starting a new position as a Ruby-On-Rails developer with a company at the Frankfurt Airport on May 2nd (I will commute from Mannheim). In the end it took me 4 months, 7 applications, 5 interviews and 2 job offers. This doesn't sound too bad, but still, it's not an experience I want to repeat anytime soon.

Check my Now page to see where I'm currently at in the process of moving back to Germany.

Wait. Rails?

Yupp. Rails.

I'll need to update the about page.

To hear what it feels like to leave Mexico and start something completely new, you'll have to listen to one of the next episodes of Several Ways To Live. There Nick and I will discuss the coming-home part of being an expat.

Let me just quickly give you an incomplete and unordered list of things I am looking forward to in Germany:

Oh, yes. Döner!!

How to listen to your audio books using your favorite podcast app

This post talks about two things:

  1. how to save backup copies of your Audible audio books
  2. how to create an podcast feed of your audio books

Update 2018-05-27: Audible stopped offering to download the audio files in "Format 4". There are solutions out there, but I haven't gotten around to studying them, yet.

Update 2018-05-29: The solution is on GitHub and is called KrumpetPirate/AAXtoMP3. Thanks to @igami@social.tchncs.de for the tipp!


I buy all ebooks from the Amazon Kindle store, because I know I can save a backup copy of each book in a non-DRM format. This is not about sharing ebooks but rather about making sure that I can keep a copy of my books in a digital format that I will be able to read in the future, too. Also, I want to be able to use any ebook reading app or device I want to, not just Amazon's Kindle apps. Incidentally, I read most of my ebooks on a Kindle Paperwhite because it's just a great device. I sync the books via the Calibre app, though, and the Kindle device is not registered with my Amazon account.


I want the same for my for Audible books. I love audio books, but I hate that I have to rely on the Audible app, with no protection against someone closing down my Amazon account or what not. Turns out, there is a way to do that, without even removing any DRM, because, apparently, there is no DRM on these files.

How to backup your Audible books

Creating a backup mp3 of audible books is surprisingly simple. Thanks to @octotherp for this tipp!

  1. Go to your Audible library.
  2. In the upper right hand corner, select "Format 4" as the download audio format.
  3. Use ffmpeg to convert the downloaded files to mp3.

To convert a single audio book, the shell command is:

$  ffmpeg -i IAmABook.aa -codec copy IAmABook.mp3

To convert all downloaded audio files, use a shell for loop:

$ for file in *.aa; do ffmpeg -i "$file" -codec copy "${file/%aa/mp3}"; done

This converts all files with the extension .aa and renames them with the extension .mp3.

How to create a podcast feed of your audio books

To create a podcast (RSS) feed from a collection of mp3s is surprisingly easy, too. Because others have solved the problem for us.

I'm using the PHP script DirCaster and my shared hosting account at my favorite web host DreamHost.

Download and unzip DirCaster in a directory

    $ wget http://dircaster.org/DirCasterV09k.zip
    $ mkdir audiobooks
    $ cd audiobooks
    $ unzip ../DirCasterV09k.zip

Edit config_inc.php to set the title and description of the feed. I also add a feed image:

$rssImageUrlTAG   = "https://myaudiobookfeed.com/books.png";

To keep thinks neat, I put the mp3 files into a subfolder books and edit the config_inc.php accordingly:

$mediaDir = "./books"

The audiobooks directory now has to be served by a web server. A simple one that runs PHP is enough though. I put my feed at a random subdomain that no-one is going to find by accident.

At least my favorite podcast app Pocket Casts has no problem subscribing to it. I also tried adding password protection using HTTP auth, but that didn't work.

If you have questions about any of this, please send me a message.

The Xochimilco Trilogy

A few months ago I started a new podcast project Several Ways to Live in Mexico City with my friend Nick. We talk about Mexico and food and culture. And now I think we have created our first highlight of the series, a 3 episode expedition to the town of Xochimilco, where we talked to my friends Jahshua and Edgar.

Episode 8 is a short preview, recorded on the way back from Xochimilco. Episode 9 talks about the history and culture of Xochimilco. And episode 10 starts tackling some questions about -isms that we have observed but could not explain just yet.

  1. SW008 Segundo Piso
  2. SW009 Xochimilco I
  3. SW010 Xochimilco II: Chilling on the Chinampas

If you have comments or questions, please don't hesitate to talk to us on Twitter or Mastodon!