Auf geht's Freiburg, kämpfen und siegen!

Gestern war ich das erste Mal seit 20 Jahren beim Eishockey. Nachdem ich damals einige Spiele der Providence Bruins miterleben durfte, war es diesmal ein DEL-Spiel zwischen den Adlern aus Mannheim und einer Brausetruppe aus München in der SAP-Arena hier um die Ecke.

Da ich weder die Regeln noch die Taktik dieses Spiels verstehe, habe ich mich auf die wichtigen Beobachtungen konzentriert.

Die Pausenshow

Die zwei Eispolierfahrzeuge brauchen zu zweit 5 Minuten, um die Eisfläche einmal abzufahren. Dabei hätten sie dafür bis zu 18 Minuten Zeit. Ich spüre deutliches Optimierungspotenzial.

Der Bierstand

Die Stadionwurst konnte ich mit Bargeld bezahlen. Das kenne ich aus den Fußballarenen der Neuzeit nicht mehr. Dafür ist das Bier genauso schlecht wie überall sonst auch.

Eben jener Bierstand ist zwischen den Halbzeitpausen übrigens wie leergefegt. Ideal, um die Toiletten zu besuchen und den Getränkevorrat aufzufüllen.

Die Fans

Die Fangesänge sind dieselben wie im Fußballstadion. Es werden allerdings nur die einfachen Songs gesungen, die die ich also auch noch mitsingen kann. Die individuellen Mannschafts- oder Städtenamen waren allerdings unverständlich, weshalb mein Gehirn überall immer "Freiburg" eingefügt hat. Sehr angenehm!


RaBa München bringt genauso viele Auswärtsfans mit wie RaBa Leipzig. Das war schon ziemlich lächerlich, denn Mannheim ist immer eine Reise wert.

Das Spiel

Neben 9 Toren fiel mir vom Spiel noch auf:

Da heult keiner, weil er mal unsanft zu Boden gebracht wird. Erfrischend anders. Andererseits war ich irritiert, dass es gar keine Faustkämpfe auf dem Eis gab. Das habe ich anders in Erinnerung.

Und der Videobeweis ist scheiße.

Anki vs. the RZL

Last week I went to our local hackspace, the RaumZeitLabor, and I talked to Cheatha about my recent adventures with Anki. There were a few other people there that listened to my ramblings as well, and their reactions were not at all what I had expected.

It wasn't a well-prepared talk or anything, but the ensuing discussion was eye-opening.

There were a few firmly held opinions:

  • memorizing facts using the flashcard method (Karteikartenlernen) is horrifying to many.
  • the same people say that it doesn't make sense for learning a programming language, because learning how to program is a matter of practice and practice alone.

I even confused Cheatha, which is hard to do. So my main takeaway from the evening is that there are many more ideas in what I'm working on than I realized, all of which I tried to put into one short talk. But that simply doesn't work. The ideas are:

  1. What is spaced-repetition learning? How and why does it work? What's it good for? (boring but necessary)
  2. How to leverage Anki's templating system to efficiently create effective flashcards. (fun for nerds like us, but…)
  3. How I captured and memorized vocabulary for learning Spanish when I lived in Mexico, and how I'm capturing and memorizing vocabulary while learning Ruby now. (my main focus right now)
  4. How I try to remember more of what I read: books, articles, documentation. (still a little fuzzy, also for me)
  5. tbc

I'll have to explore each of these ideas separately. And I will, because I still believe that it will benefit me. So I guess the very next question I have to answer is: what benefit do I hope to get from memorizing all this information? It's a valid question that until now got drowned by my excitement of learning the new tools and the perception of "making progress".

Better Anki Learning Steps

I've been playing around with Anki, the spaced repetition application, A LOT in the last few weeks. This will certainly not be the last post about Anki, but I'll start with a simple reference post for me to remember where I learned how Anki's learning steps work.

Check out this video:

It explains

  1. the different types of cards in Anki,
  2. how learning intervals are calculated,
  3. what the ease and interval modifiers are,
  4. what the ease factor is, and
  5. how answers modify the interval.

It also talks in depth about "Ease Hell", what it is, and how to avoid it.

Now I finally understand what Anki's Learning Phase is all about (12:34), and how to best use it. Money quote:

Answering a card incorrectly in the learning phase does not change its ease factor.

The proposed settings for the Learning Phase are:

  • Steps (minutes): 15 1440 8640
  • Graduating Interval (days): 15

These numbers are based on the SuperMemo 2 algorithm (18:34), which is what Anki is actually using, just not with sane default. Watch the whole video for more details. It's worth it!

What I've learned about the Irish

I spent the last few days in Dublin, Ireland, visiting a very good friend of mine. These are a few observations I made during my stay:

  • The Irish consider themselves much less European than I had thought.
  • Mainland Europe is simply called "Europe".
  • They hate the English.
  • Until recently, Ireland had been living in the Middle Ages, as is accurately portrayed in the 1991 movie The Commitments.
  • They frickin' love The Commitments.
  • Germans are forgiven for not knowing anything about Rugby.
  • I have a better chance of catching an SC Freiburg match in Dublin than I do in Mannheim.
  • They know how to make a good breakfast.
  • They call Americano an Americano.
  • Guinness is owned by big alcohol and employs mob tactics to block other beer makers from getting their products into pubs.
  • Irish pub culture is as strong and real as it is advertised. Loving it!

Jason Swett: How I write characterization tests

Let’s say I come across a piece of code that I want to refactor but unfortunately

a) I don’t understand what it does and
b) it’s not covered by tests.

Yupp. That's a problem. How I write characterization tests shows a technique for how to deal with this situation. I, too, use tests to figure out how a piece of code actually works. Works better for me than actually reading the code. 🤨

Speeding up RSpec with bundler standalone and springified binstubs

Testing Rails with RSpec is slow, or at least it feels slow to me in all the projects I am working on. Any speed gain helps my productivity when it reduces the time I'm waiting between writing code and running tests.

There are quite a few posts out there that tackle this problem. But most are pretty old, and none really worked for me, so I'm not going to link to them.

The book I'm reading inspired me to look into the bundler --standalone command. Not that I really understand what's happening there, but at least I got a little bit of a speed bump out of it.

Here's what I did, and how I'm running my tests now.

Spring

First I made sure my Rails app is installed with Spring enabled. Luckily, this is the default. In order to later run RSpec from spring, I added the spring-commands-rspec gem to my Gemfile.

gem 'spring-commands-rspec'

Bundler

Next, I used bundler's standalone command,

$ bundle install --standalone --path .bundle

and then "springified" the installed binstubs.

$ bundle exec spring binstub --all

Problems

I encountered a problem with SQLite 1.4.0. I didn't investigate it further, but pinned the gem to version 1.3 instead.

group :development, :test do
  gem "sqlite3", "~> 1.3.6"
end

Afterwards I repeated the install command.

$ bundle install --standalone --path .bundle

Anytime you want to use bundle install, you now have to use bundle install --standalone instead. I created the bash alias bis for that.

vim-test

I recently started using the vim-test plugin. That plugin has a neat option that makes it use the springified binstubs.

" .vimrc

let test#ruby#use_spring_binstub = 1

Now, when I'm editing app/models/transformer_spec.rb and I hit the return key in normal mode, vim-test executes

$ ./bin/spring rspec spec/models/transformer_spec.rb

Because of spring, everything's faster and I have actually seen tests being executed in less than a second. Still not super fast but better than before.

Teach vim-rails about request specs

Slowly but surely I'm getting to know the really interesting Rails-related Vim plugins. First and foremost: vim-rails. A new feature that I started using recently is the :A alternate file command. Basically, this makes it super quick to jump from a model file to its model spec file. Or from a controller to its spec.

Since Rails 5.?, controller specs have fallen out of DHH's favor. We now use request specs. Unfortunately, vim-rails doesn't know about request specs. Fortunately, we can tell it about them.

vim-rails is very well documented, and :help rails-projections provides the solution to this problem. This is what I put in my .vimrc, and now :A jumps between my controller files and the related request specs.

let g:rails_projections = {
      \ "app/controllers/*_controller.rb": {
      \   "test": [
      \     "spec/controllers/{}_controller_spec.rb",
      \     "spec/requests/{}_spec.rb"
      \   ],
      \ },
      \ "spec/requests/*_spec.rb": {
      \   "alternate": [
      \     "app/controllers/{}_controller.rb",
      \   ],
      \ }}

Running RSpec with a single keystroke in a separate tmux session

This is a tiny update to Running Specs from Vim, Sent to tmux via Tslime from the thoughtbot blog. Go read it, and realize that it's six years old.

vim-rspec has not been updated in two years. It probably still works fine, but there's a new vim plugin that does what vim-rspec does, just better: vim-test.

I finally set this up yesterday:

" Add vim-test and tslime to vim plugins.
Plug 'janko/vim-test'
Plug 'jgdavey/tslime.vim'

...

" Configure vim-test to execute test command using tslime
let test#strategy = "tslime"

" Configure <CR> aka the Return key to run my test file.
nmap <CR> :TestFile<CR>
" I'm still figuring out which test commands make the most sense
" in my workflow. Right now, this feels pretty good.
nmap <leader><CR> :TestLast<CR>

Now, when I'm in normal mode and hit the return key, rspec gets executed for the current file in a different tmux pane (!!). What I didn't understand before using this was how it would select the correct pane. Turns out, it's super easy. On the first run, it asks me for the tmux session, window, and pane (if necessary). After that, it remembers and it always sends the test command there. Super cool!

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
end

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)
  end
end

Or 4 if I use the subject shorthand notation.

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

  it { is_expected.to eq 9 }
end

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])
    end
  end
end

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

Failures:

  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 }
  end
end

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.

Failures:

  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'

rspec-parametrized

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]
    ]
  end

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

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

Failures:

  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:

  • abstract_type, current version: 0.0.7 (2013)
  • adamantium 0.2.0 (2014)
  • concord 0.1.5 (2014)
  • proc_to_ast 0.1.0 (2015)

Conclusion?

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:

  • Property-based testing
  • GIVEN-WHEN-THEN
  • Utilizing --format documentation
  • FactoryBot.create vs. FactoryBot.build

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!