Category Archives: Programming

“Perfect is the enemy of the good” is a false dichotomy

“Perfect is the enemy of the good” is a very true and helpful saying that’s often applied to creative work.

For example, if I fret over the wording of a blog post and hesitate to hit publish because my writing isn’t perfect, then I’ve prevented myself from releasing value into the world. Better to just publish something that’s pretty good and get it out there.

But “perfect is the enemy of the good” is an egregiously counterproductive mindset in the context of software projects.

The reason is that it’s a false dichotomy. Software projects are always behind. All development teams are always under extreme time pressure. Practically every production codebase in existence is a crappy legacy codebase. So the normal day-to-day state of affairs for most teams is that they’re cutting corners and doing work they’re not proud of in order to maintain some semblance of productivity. (This is not a good way to work but it’s the common reality.)

When leadership comes to a dev team and asks them to perform a miracle, the choice the dev team is faced with is not “should we make it good or should we make it perfect?” Rather, the choice is between: a) business as usual, i.e. piling yet more shitty code on top of the existing pile of shit, or b) switching to “crisis mode” and committing whatever crimes and sins and atrocities are necessary in order to meet the desired timeline. The dilemma is not perfect vs. good but crappy vs. ghastly.

Yes, it’s of course important to actually ship features and meet commitments. But the way to ship features is to employ healthy engineering practices and to take on realistic scopes of work, not to do work of intentionally low quality.

What exactly makes “bad” code bad?

As programmers we often talk about “bad” code. But interestingly, I don’t believe I’ve ever heard someone actually define exactly what bad code is.

I find this unfortunate because saying code is “bad” is actually meaningless, especially to a non-technical manager. If a non-technical person hears me say that our code is “bad”, they might believe me, but they won’t understand what it means for the code to be bad or what it would take to make the code good.

If we can put our finger on exactly what we mean when we say “bad code”, maybe it will help us go from sounding like we’re complaining about the code to getting on the path to making the bad code good.

So I’m going to try to define what bad code means.

My definition of bad code

Bad code is code that is risky and/or expensive to change.

Let me break that down.

What does it mean for code to be risky to change?

Have you ever been scared to deploy a code change to production? I have.

What exactly are we afraid of when we’re afraid to deploy a change? Basically, we’re afraid of introducing a regression. If the code we’re working on is hard to understand, then that means we don’t understand the implications of our changes. That means that all the carefulness in the world can’t protect against accidentally breaking some part of the application when we make a change.

Consequently, we cause problems for customers. We cause customers to lose money. We make customers frustrated with our employer’s product. We make everyone trust the development team less. These are all genuinely bad things. If a codebase is sufficiently hard to understand that it poses these risks to the development team, then I would say that that code is bad.

What does it mean for code to be expensive to change?

The cliche that time is money is true. The longer it takes a developer to be able to understand a system, the more expensive that system is to maintain.

In addition to “bad code” another term that gets thrown around to the point of meaninglessness is “maintainability”. We often talk about code being “maintainable” or “unmaintainable” but rarely talk about what exactly that means. Another way to say the same thing would be to say that code has a low cost of maintenance or high cost of maintenance.

This is true of not just code but all parts of a system. If we use unclear variable names, convoluted database schemas, or methods and classes that are too big to quickly and easily grasp, then future maintainers of our system will have to spend more time (which means more money) to be able to understand the system.

The reason I’m talking about understandability is because in order to confidently change code, you first have to understand it to some extent. Have you ever made a one-line change that took literally three seconds to make but then spent 30 minutes studying the code and testing your work because you weren’t sure you understood the implications of your change? The longer that “studying and testing” period, the more expensive the code is to maintain.

What a “walking skeleton” is and why I always start projects with one

Every software project involves some amount of mystery and uncertainty. In fact, it’s often the case that a software project is hardly made of anything but mystery and uncertainty.

Some jobs are more mysterious than others. I was recently asked by a client to build a Rails application that contained CRUD interfaces for a Patient resource and an InsuranceType resource. That work was not very mysterious.

My client also asked me to set up staging and production environments for this application on AWS. This might sound easy but setting up a Rails application on AWS/Elastic Beanstalk is totally not straightforward, even though it’s 2018, and even though I myself wrote guides on how to get Rails working on AWS. The work of getting the application deployed to AWS was in fact mysterious.

Enter the Walking Skeleton

A Walking Skeleton is basically the thinnest possible slice of all the layers in an application. The idea is that you front-load the difficult/mysterious work in a project so you get it out of the way early while the stress level is low and the remaining budget is high.

I learned about the idea of a Walking Skeleton from Growing Object-Oriented Software, Guided by Tests. The name refers to the idea that your early application hardly does anything but it can still stand up.

The authors’ suggestion as I understood it was to set up both an application’s production environment and testing infrastructure as part of a Walking Skeleton, both of which can be mysterious/time-consuming/difficult to set up.

So in the case of my application, I might get my Elastic Beanstalk environment set up, create just one of my CRUD interfaces (whatever the simplest one is) and write a couple feature specs to exercise my CRUD interface. This will force me to figure out how to get AWS/EB set up (again, non-trivial) as well as install RSpec, Factory Bot, Capybara, etc. From that point on everything is just incremental.

The alternative to a Walking Skeleton

Failure to create a Walking Skeleton can contribute to ghastly project failure. If you’d like to hear one of my horror stories about this you can check out another article of mine, Deploy to Production on Day One.

How dependency injection can make Rails tests easier

“Dependency injection” is a fancy-sounding term. When I first heard it I assumed it referred to some super-advanced technique. It wasn’t until years later that I realized that dependency injection is a pretty straightforward technique once you understand what it is.

My aim with this post is to cut through the jargon and show you in simple terms what dependency injection is and why it’s useful.

But first: why are we interested in this topic?

Why bother learning dependency injection?

Depending on how it’s written, some code can be easy to test and some code can be hard to test. Code with entangled dependencies is hard to test.

Why is code with entangled dependencies hard to test? Imagine I have a class `Order` that requires instances of a class called `Payment` in order to function. Let’s then imagine that `Payment` needs some `PaymentType` instances (Visa, MasterCard, cash, etc.) in order to work.

This means that in order to test the class I’m interested in, `Order`, I have to bring two other classes into the picture, `Payment` and `PaymentType`, just to perform the test. And what if `Payment` and `PaymentType` in turn depend on other classes? This test is going to potentially be very tedious to set up.

The opposite of having entangled dependencies is having loose coupling and modularity. Modular, loosely coupled code is easy to test. A number of factors have a bearing on how modular and loosely coupled your code will end up. What I want to show you right now is how dependency injection can help make your code more modular and therefore more easily testable.

An dependency-laden Rails model

Let’s say you’re working on a legacy project that you recently inherited. There’s very little test coverage. You encounter an ActiveRecord model called `CustomerFile`. There’s a method called `parse` that evidently parses a CSV.

class CustomerFile < ActiveRecord::Base
  belongs_to :customer

  def parse
    rows = []

    content = File.read(customer.csv_filename)
    CSV.parse(content, headers: true) do |data|
      rows << data.to_h
    end

    rows
  end
end

Let’s focus on this line for a second: `content = File.read(customer.csv_filename)`.

Apparently a `CustomerFile` object has an associated `customer` object which in turn has a `csv_filename`. How exactly does `customer` get set? It’s not clear. Where exactly is the file that `customer.csv_filename` points to? That’s not obvious either.

We can try to write a test for `CustomerFile` but it probably won’t go very well.

RSpec.describe CustomerFile do
  describe '#parse' do
    let(:customer_file) { CustomerFile.new }

    it 'parses a CSV' do
      # How do we know what to expect?
      # Where is the file that "customer.csv_filename" refers to?
      # expected_first_row = ?

      expect(customer_file.parse[0]).to eq(expected_first_row)
    end
  end
end

Our attempt to write a test hasn’t proven very fruitful. The challenge of writing a test for this class is somewhat “uncomeatable”.

The reason it’s hard to write this test is that `CustomerFile` has a dependency inside of a dependency. We don’t know how to make a `customer`, and even more problematic, we don’t know how to make a CSV file for that customer.

Applying dependency injection for easier testability

Let’s imagine now that `parse` doesn’t require that we have a `customer` with `csv_filename` that points to some mysterious file on the filesystem somewhere.

Let’s imagine a version of `parse` that just takes the file contents as an argument.

class CustomerFile < ActiveRecord::Base
  belongs_to :customer

  def parse(content)
    rows = []

    CSV.parse(content, headers: true) do |data|
      rows << data.to_h
    end

    rows
  end
end

When we try to write a test now, we’ll see that it’s much easier.

RSpec.describe CustomerFile do
  describe '#parse' do
    let(:customer_file) { CustomerFile.new }
    let(:content) { "First Name,Last Name\nJohn,Smith" }

    it 'parses a CSV' do
      expected_first_row = {
        'First Name' => 'John',
        'Last Name' => 'Smith'
      }

      expect(customer_file.parse(content)[0]).to eq(expected_first_row)
    end
  end
end

In this case `parse` doesn’t know or care where the CSV content comes from. This means that we don’t have to bring the filesystem into the picture at all which makes writing this test very convenient. No `customer` object or `customer.csv_filename` value necessary.

If we want to use `parse` for real in the application we can just pass in the file contents like this: `parse(File.read(customer.csv_filename))`.

Conclusion

Modular, loosely coupled code is testable code. You can use dependency injection to help make your code more modular.

Atomic commits and testing

What atomic commits are and why they’re advantageous

I like to make Git commits very frequently. Looking at the Git log for a recent project, it looks like I tend to commit about every 5-15 minutes.

I find that the smaller my commits are the easier I make life for myself. I remember painful occasions in the past where I would do a big chunk of work, maybe two hours or so. Things would be going fine, going fine, and then all the sudden my work would collapse on itself and I wouldn’t be able to figure out how to get it back to a working state.

At that point my options would be either to scratch my head for the next hour to try to figure out what went wrong or to revert the whole two hours’ worth of work. I had painted myself into a corner. When I teach programming classes I see students paint themselves into a corner like this all the time.

This kind of stuff doesn’t really happen to me anymore. One of the main reasons is that I practice atomic commits.

“Atomic commit” is basically a fancy way of saying a commit that commits one and only one thing. It’s a single complete unit of work.

Students of mine often find it funny that I’ll make a commit after changing just one or two lines of code. But a commit is not just something I do after completing a piece of work. A commit is something I do before I start a piece of work.

Let’s say I have Chunk A and Chunk B, two unrelated pieces of work. Chunk A is a 30-second change. Chunk B is a 20-minute change. A lot of people might not bother committing Chunk A because it’s so small. But then Chunk A “comes along for the ride” when I’m working on Chunk B. If I screw up Chunk B and have to bail and revert and start over, then I also end up reverting Chunk A. Or if a week later I find out Chunk B introduced a bug and I need to revert Chunk B at that point, Chunk A gets reverted as well even though it has nothing to do with Chunk B.

These are the things I have in mind when I commit what might seem to others like a comically tiny change.

Atomic commits also make it easier to track down the source of mysterious regressions. Let’s say a feature was known to be working on January 5th and broken on June 11th and nobody knows when exactly the feature broke. Git bisect can make it very quick and easy to find out exactly which commit introduced the regression. At least, it’s easy if the team has been practicing atomic commits. If each commit is huge and contains multiple pieces of work, git bisect loses a lot of its usefulness.

By the way, I think “WIP” (work in progress) is one of the worst possible commit messages. First, it basically screams “This commit is not a complete unit of work.” Second, it’s about as vague as it gets. Committing “WIP” is basically saying “Rather than take 30 seconds to think of a meaningful commit message, I’d rather make you take several minutes to try to figure out what this commit is all about.” Please don’t commit “WIP”.

What atomic commits have to do with testing

I find it advantageous to make sure each commit leaves the application’s test suite in a passing state.

This way you’re free to check out any commit in the application’s history and you can have a reasonable expectation that the test suite will pass. (This becomes less true the further back in history you go, of course, but it’s at least true for the recent commit history which is usually the history you’re most interested in.)

Some developers don’t find it important to keep the test suite passing on every commit. When this is the case I might check out a certain revision and see that the tests don’t pass. Then I’m forced to wonder: is it supposed to be like this or was this an accident? There’s nothing more frustrating than getting tripped up by a failing test, finally asking someone about it, and getting the response, “Oh, yeah, that test is supposed to fail.” Allowing failing tests to be committed to the repo arguably defeats the purpose of having tests.

It’s okay for a commit not to add a test. It’s okay for a commit to add a passing test. But if a commit commits a failing test then the commit is not a complete unit of work, and the benefits of a repo full of atomic commits are to some extent lost.

I also often find it handy to use git bisect in conjunction with an application’s test suite. Using git bisect is all about going back to a certain point in time and asking “Does feature X work at this commit?” Sometimes the test that answers that question is a manual check. Sometimes it’s an automated test. If the team has a habit of making only small, atomic commits, using git bisect together with tests is a lot easier.

Deploy to production on day one

Sleeping at the office, nothing to show

You know a project is under serious pressure when people start camping out at the office.

In 2015 I got a job at an agency where they were doing a big project for a large Chinese electronics manufacturing company. The project had been underway for about six months.

Two of the developers, who I’ll call Dave and Eric, had been working on the iOS and Android app portion of the project. I was told that for a time they had been sleeping at the office at night in an effort to hit their timelines.

The application had a Rails backend. I have a vague memory that the person who wrote most of the Rails app wasn’t around anymore.

Six months into the project, with a good portion of the budget already spent, the agency had no working application to show their client.

There were a lot of problems with the way the project had been carried out up to the point I got involved. They gave the design work to a very junior designer. The designs didn’t make sense and were not implementable. In the apps that were built, many things were only partway done and very little was all the way done.

There was also no production environment. For whatever reason it was decided that the project would be hosted on AWS. I had never touched AWS at the time but simply because I was the least unqualified person on staff to do it, the task of getting the Rails app onto AWS fell to me.

It took me forever to get things working on AWS. But I finally got it. It was not a simple deployment. The nature of the application required servers in different regions of the world. Because AWS regions are independent, we had to devise a complex messaging system to keep data in sync across multiple regions. It was a nightmare. Compounding the nightmare was the fact that we were under intense time pressure. Everything we were doing was supposed to have been done weeks or months prior.

Done by Thanksgiving? “I think we can do it!”

One of my most vivid memories from this project was kind of a “go/no go” meeting with a group of high-up people at the agency. This meeting included myself, the agency president, several people with “VP” in their title (which was almost everybody at the agency) and a few others. There were maybe about 12 people in the room. At this point in time we had very little working in production and about six weeks before “pencils down” time. If we didn’t have the project done by Thanksgiving, the whole thing was for naught.

At this meeting we discussed what still needed to be done relative to the amount of time we had left. The scope was not very negotiable. My guess was that in order to get everything done in our scope we would need about six months. I remember that we went around the table at this meeting and had each person say whether they thought we could or could not hit our Thanksgiving deadline. “I think we can do it,” the first person said. “I think we can do it!” the next person said. Every single person in the meeting said they thought we could do it. Well I didn’t think we could do it. So I said, “No, I don’t think we can do it.” I could tell everybody was annoyed with me and thought I was being a stick in the mud. Six months of work when we only had six weeks left seemed “too bad to be true”. I agreed it sucked that we couldn’t get six months’ work of work done in six weeks. But the amount that it would suck to not get it done had no bearing on whether we could actually get it done.

$300,000+ down the drain

Turns out I wasn’t crazy. We got nowhere near getting the project done in time. The client spent (as far as I understand) multiple hundreds of thousands of dollars and got nothing.

How this sad story could have been avoided

Not every part of every software project is equally challenging. Some tasks, like creating a new CRUD interface in a Rails project, are really familiar and pretty trivial to implement. Others, like setting up an AWS environment across different regions and setting up a message queuing system so they can talk to each other, when you’ve never touched AWS in your life, are not so trivial.

When you have a mix of mysterious and non-mysterious work in a project you should do the most mysterious work first. Don’t save the super hard stuff for last.

You might think this is common sense. I would think so too. But surprisingly many people leave the most mysterious part of a project for the 11th hour.

This agency’s failed project might not have been quite such a spectacular failure if they had deployed to production on day one. If you deploy a mostly-empty application to production on day one, there’s way less stuff to have to get working. If something breaks in the process of setting up the production environment, you have way less stuff to sort through to figure out exactly where the problem lies. The stress level is also much lower at the beginning of a project.

Getting a production environment in place on day one makes it so every subsequent deployment is a non-event. Throughout the project you can do a number of deployments every week. If the project deadline is Thanksgiving there need not be a mad scramble to get things set up and working the day before Thanksgiving. You just deploy like normal. The workload might be heavier but the deployment process is uneventful.

For less risk and less stress, deploy to production on day one.

What does all this have to do with automated testing?

Like the authors of GOOS say, a healthy development process involves feedback loops ranging from seconds to months. These kinds of feedback loops may include:

  • Unit tests (seconds)
  • Entire unit + integration test suites (minutes)
  • Pair programming sessions (seconds to minutes)
  • PR reviews (minutes to hours)
  • Feature releases (days to weeks)
  • Entire development projects (weeks to months)

These feedback loops are enabled by practices like automated testing, pair programming, continuous integration and continuous delivery/deployment. These tests don’t exist in isolation but weave and synergize with each other. It’s hard to practice continuous deployment when you don’t have any tests. Continuous integration shines a light on your tests and forces you to pay attention to how healthy your test suite is. In addition to deploying to production on day one, it’s immensely helpful to get your testing infrastructure in place on day one and release a tiny feature complete with tests.

How I write model tests

I recently read a post on /r/ruby that asked for RSpec model testing best practices. I gave an answer, but also wanted to give some concrete examples, so here we go.

What follows is how I’d write a spec for a new model. I actually took a model from an existing application I wrote and just blew away the code so I could start over from scratch.

Once I got a ways into this post I realized that it was getting pretty long and I still hadn’t gotten very far into the “meat” of how I’d write a model spec. So I intend to write a follow-up post that goes more in-depth.

Starting point

Here’s what my `Restaurant` model and test look like when they’re empty.

class Restaurant < ApplicationRecord
end
require 'rails_helper'

RSpec.describe Restaurant, type: :model do
end

And just so you can see what the `Restaurant` class’s attributes are, here’s a snippet of `db/schema.rb`. Most of these attributes won’t come into the picture. We’ll mostly just deal with `name` and `phone`.

create_table "restaurants", force: :cascade do |t|
  t.string "name", null: false
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.string "phone", null: false
  t.integer "business_model_id", null: false
  t.index ["business_model_id"], name: "index_restaurants_on_business_model_id"
  t.index ["name"], name: "index_restaurants_on_name", unique: true
  t.index ["phone"], name: "index_restaurants_on_phone", unique: true
end

The first spec

To me the most natural thing to test is the presence of the restaurant name. I’ll write a failing test for presence of name using Should Matchers.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
end

When I run this spec, it fails, as I expect.

rspec spec/models/restaurant_spec.rb    
F                                  

Failures:                          

  1) Restaurant should validate that :name cannot be empty/falsy       
     Failure/Error: it { is_expected.to validate_presence_of(:name) }  
                                   
       Restaurant did not properly validate that :name cannot be empty/falsy.                                                                  
         After setting :name to ‹nil›, the matcher expected the Restaurant to                                                                  
         be invalid, but it was valid instead.                         
     # ./spec/models/restaurant_spec.rb:4:in `block (2 levels) in <top (required)>'                                                            

Finished in 0.29263 seconds (files took 1.34 seconds to load)          
1 example, 1 failure               

Failed examples:                   

rspec ./spec/models/restaurant_spec.rb:4 # Restaurant should validate that :name cannot be empty/falsy

I add the name validator to make the spec pass.

class Restaurant < ApplicationRecord
  validates :name, presence: true
end

And indeed, the spec now passes.

rspec spec/models/restaurant_spec.rb
.

Finished in 0.2793 seconds (files took 1.32 seconds to load)
1 example, 0 failures

Spec for phone presence

Now that name presence is taken care of I turn my attention to phone. The spec in this case is the exact same.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }
end

I run the spec and it fails. I then add a presence validator:

class Restaurant < ApplicationRecord
  belongs_to :business_model
  validates :name, presence: true
  validates :phone, presence: true
end

The spec passes now.

Phone number format validity

Unlike restaurant name, which could be pretty much anything, the phone number has to have a valid format. For example, “123” of course isn’t a valid phone number. I add a failing test for this case. I’m actually not sure what I expect the error message to be, so I just put “invalid format”. After I run the spec I can update the error message in my test to match the actual error message.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }

  describe 'when phone number is too short' do
    it 'is not valid' do
      restaurant = build(:restaurant, phone: '123')
      restaurant.valid?
      expect(restaurant.errors[:phone]).to include('invalid format')
    end
  end
end

As I expect, this test fails.

rspec spec/models/restaurant_spec.rb    
..F                                

Failures:                          

  1) Restaurant when phone number is too short is not valid            
     Failure/Error: expect(restaurant.errors[:phone]).to include('invalid format')                                                             
       expected [] to include "invalid format"                         
     # ./spec/models/restaurant_spec.rb:11:in `block (3 levels) in <top (required)>'                                                           

Finished in 0.33318 seconds (files took 1.31 seconds to load)          
3 examples, 1 failure              

Failed examples:                   

rspec ./spec/models/restaurant_spec.rb:8 # Restaurant when phone number is too short is not valid

Now I add a format validator using a regex I found on the internet.

class Restaurant < ApplicationRecord
  belongs_to :business_model
  validates :name, presence: true
  validates :phone, presence: true, format: {
    with: /\A(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}\z/
  }
end

The spec now fails because the expected error message, “invalid format” doesn’t match the actual error message, “is invalid”.

rspec spec/models/restaurant_spec.rb
..F

Failures:

  1) Restaurant when phone number is too short is not valid
     Failure/Error: expect(restaurant.errors[:phone]).to include('invalid format')
       expected ["is invalid"] to include "invalid format"
     # ./spec/models/restaurant_spec.rb:11:in `block (3 levels) in <top (required)>'

Finished in 0.40668 seconds (files took 2.03 seconds to load)
3 examples, 1 failure

Failed examples:

rspec ./spec/models/restaurant_spec.rb:8 # Restaurant when phone number is too short is not valid

So I update my expected error message.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }

  describe 'when phone number is too short' do
    it 'is not valid' do
      restaurant = build(:restaurant, phone: '123')
      restaurant.valid?
      expect(restaurant.errors[:phone]).to include('is invalid')
    end
  end
end

And now the spec passes.

rspec spec/models/restaurant_spec.rb    
...                                

Finished in 0.38825 seconds (files took 2.03 seconds to load)          
3 examples, 0 failures         

Tests for other phone cases

By adding this phone regex I’ve actually broken a rule of TDD: only write enough code to make the test pass. If you only write enough code to make the test pass, you know that all the code you’ve written is covered by tests.

In this case I “wrote some code” (copy/pasted a regex from Stack Overflow) that didn’t have tests. So I’m going to go back and add more test cases. One case will say, “when the phone number is valid, the restaurant object should be valid”. The other will say “when the phone number is all numbers, the restaurant object should not be valid”.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }

  describe 'phone' do
    describe 'when phone number is valid' do
      it 'is valid' do
        restaurant = build(:restaurant, phone: '(555) 555-5555')
        expect(restaurant).to be_valid
      end
    end

    describe 'when phone number is too short' do
      it 'is not valid' do
        restaurant = build(:restaurant, phone: '123')
        restaurant.valid?
        expect(restaurant.errors[:phone]).to include('is invalid')
      end
    end

    describe 'when phone number is all letters' do
      it 'is not valid' do
        restaurant = build(:restaurant, phone: '(AAA) AAA-AAAA')
        restaurant.valid?
        expect(restaurant.errors[:phone]).to include('is invalid')
      end
    end
  end
end

These tests all pass. There’s a little duplication in my test, though, so I’m going to refactor.

require 'rails_helper'

RSpec.describe Restaurant, type: :model do
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_presence_of(:phone) }

  describe 'phone' do
    let(:restaurant) { build(:restaurant) }

    describe 'when phone number is valid' do
      it 'is valid' do
        restaurant.phone = '(555) 555-5555'
        expect(restaurant).to be_valid
      end
    end

    describe 'when phone number is too short' do
      it 'is not valid' do
        restaurant.phone = '123'
        restaurant.valid?
        expect(restaurant.errors[:phone]).to include('is invalid')
      end
    end

    describe 'when phone number is all letters' do
      it 'is not valid' do
        restaurant.phone = '(AAA) AAA-AAAA'
        restaurant.valid?
        expect(restaurant.errors[:phone]).to include('is invalid')
      end
    end
  end
end

All the tests still pass.

rspec spec/models/restaurant_spec.rb    
.....                              

Finished in 0.30868 seconds (files took 1.37 seconds to load)          
5 examples, 0 failures

To be continued

What I’ve written in the post is representative of the start of what I’d put in a model test but it’s certainly not the whole thing. What if my model contains some non-trivial methods? This post is already getting kind of long, so I plan to continue this in a Part 2.

Things you can ignore when getting started with Rails testing

Here’s an incomplete list of tools and concepts you might encounter when first trying to learn Rails testing: Capybara, Cucumber, Database Cleaner, factory_bot, Faker, MiniTest, RSpec, system tests, Test::Unit, acceptance tests, end-to-end-tests, mocks, stubs, unit tests and TDD.

That’s a lot of stuff. If you’re like most humans you might look at this long list of things, feel confused about where to start, and say, “I don’t know what to do. I’ll just deal with this later.”

The challenge of getting started with testing would be much easier if you knew exactly what you needed to know and what you could safely ignore. What follows is a list of what you can safely ignore.

Framework Decisions

You don’t need to get hung up on which framework to use. You literally can’t go wrong. The principles of testing are the same no matter which testing framework you use. Plus you can always change your mind later.

When I first got started with Rails, the way I decided on a testing framework was very simple. I noticed that most Rails developers used RSpec, so I just picked RSpec.

(I actually used Test::Unit for a while before realizing most Rails developers used RSpec. So I just switched. It wasn’t a very big deal.)

Cucumber, Capybara and System Tests

Most Rails test suites have two main sub-suites: a suite of model tests and a suite of integration tests.

Model tests test the behavior of ActiveRecord models by themselves. Integration tests actually spin up a browser and do things like fill out forms and click links and buttons as if a human is actually using the application.

Between these two, the bar is lower for model tests. For integration tests you have to do almost all the same stuff as model tests plus more. For this reason I suggest that if you’re a newcomer to Rails testing that you start with model tests (or even just Ruby tests without Rails) and ignore integration tests altogether until you get more comfortable with testing.

So you can ignore Cucumber (which I don’t recommend using at all), Capybara and system tests, which are all integration testing tools.

View Specs

I’ve never written a view spec. To me they seem tautological. I’ve also never encountered any other Rails developer who advocates writing view specs.

If for some reason I had a wildly complicated view, I could see a view spec potentially making sense. But I haven’t yet encountered that case.

Helper Specs

I don’t tend to write helper specs because I don’t tend to write helpers. Helpers are a fairly peripheral area of Rails that you can safely disregard altogether when you’re getting started with Rails testing.

Routing Specs

Like view specs, I find routing specs to be tautological. I don’t write them.

Request Specs

Request specs are a great boon and even necessity if your application has an API or if your controllers do anything non-trivial. But I don’t think you should worry about request specs when you’re just getting started.

Controller Specs

Controller specs are deprecated in favor of request specs.

What Not to Ignore

The main thing I would recommend learning when you’re getting started with Rails testing is model tests.

When you’re learning about model tests you’ll naturally have to get acquainted with RSpec syntax (or whichever framework you choose), factory_bot and Database Cleaner (or analogous tools). But other than the actual testing techniques, that’s about it as far as model test tooling goes.

If you want to make life even easier on yourself you can learn just Ruby and RSpec with no Rails involved. Then, after you get comfortable with RSpec syntax and basic testing techniques, you can approach Rails testing with more confidence.

Why I recommend against using Cucumber

Around the time I first started using Rails in 2011, I noticed that a lot of developers, seemingly all Rails developers, were using Cucumber to assist with testing.

I bought into the idea—describing test cases in plain English—but in practice I found Cucumber not to be very valuable. In fact, my experience has been that Cucumber adds a negative amount of value.

In recent years I’ve noticed (although this is anecdotal and might just be my perception) that fewer codebases seem to use Cucumber and that fewer Rails developers seem to be on board with Cucumber. I had thought Cucumber was pretty much dead. But lately, to my surpise, I’ve seen Cucumber recommended to testing noobs more than a few times. Since I consider Cucumber to be a bad thing, I want to explain why I think so and why I don’t think other people should use it.

In my view there are two general ways Cucumber can be used: it can be used as intended or it can be abused. In the former case, I believe Cucumber has a small negative value. In the latter case I believe it has a large negative value.

Why Cucumber is bad when it’s not used as intended

Most production Cucumber scenarios I’ve seen look something like this:

Given a user exists with email "test@example.com" and password "mypassword"
And I visit "/sign_in"
And I fill in the "Email" field with "test@example.com"
And I fill in the "Password" field with "mypassword"
And I click "Sign In"
And I visit "/user/edit"
And I fill in the "First Name" field with "John"
And I fill in the "Last Name" field with "Smith"
And I fill in the "Age" field with "30"
And I click "Save"
And I visit "/profile"
Then I should see "John Smith, 30"

These kinds of tests, with fine-grained steps, arise when the developers seem to mistake Cucumber for a way to write Ruby in English. The above scenario provides exactly zero benefit, in my opinion, over the following equivalent Capybara scenario:

FactoryBot.create(:user, email: 'test@example.com', password: 'mypassword')

visit sign_in_path
fill_in 'Email', with: 'test@example.com'
fill_in 'Password', with: 'mypassword'
click_on 'Sign In'

visit edit_user_path
fill_in 'First Name', with: 'John'
fill_in 'Last Name', with: 'Smith'
fill_in 'Age', with: '30'
click_on 'Save'

visit profile_path
expect(page).to have_content('John Smith, 30')

The Cucumber/Gherkin version is no shorter nor more easily understandable.

To be fair to Cucumber, nobody who understands Cucumber advocates writing Cucumber scenarios in this way. The Cucumber creator himself, Aslak Hellesøy, wrote a post in 2011 saying not to do this. Other people have written similar things.

I think it’s telling that so many people have written blog posts advising against the very common practice of writing fine-grained Cucumber steps. To me it’s kind one of those gas station doors that looks for all the world like a pull door, so every single person who comes up to it pulls it instead of pushes it, feels like a dumbass, and then pushes it. So the gas station manager puts up a big sign that says “PUSH”, but most people don’t notice it and the problem persists. What instead should have been done is to make the push door look like a push door, without the big useless handle that you’re not supposed to pull. I get that the Cucumber maintainers tried to do that by removing `web_steps.rb`, but in my experience it didn’t seem to work.

And it doesn’t matter much anyway because Cucumber still sucks even if you don’t abuse it by writing fine-grained steps. I’ll explain why I think so.

Why Cucumber is bad even when it is used as intended

Here’s a version of the above Cucumber scenario that’s done in the way the Cucumber creators would intend. There are two parts.

First, the Gherkin steps:

Given I am signed in
And I provide my name and age details
Then I should see those details on my profile page

Second, the underlying Ruby steps:

Given /^I am signed in$/ do
  visit sign_in_path
  fill_in 'Email', with: 'test@example.com'
  fill_in 'Password', with: 'mypassword'
  click_on 'Sign In'
end

And /^I provide my name and age details$/ do
  visit edit_user_path
  fill_in 'First Name', with: 'John'
  fill_in 'Last Name', with: 'Smith'
  fill_in 'Age', with: '30'
  click_on 'Save'
end

Then /^I should see those details on my profile page$/ do
  visit profile_path
  expect(page).to have_content('John Smith, 30')
end

This is actually pretty decent-looking and appealing, at least at first glance. There are two problems, though. First, this way of doing things doesn’t really provide any clarity over doing it the Capybara way. Second, the step definitions usually end up in a single, flat file full of “step soup” where unrelated steps are mixed together willy-nilly.

Compare this again with the Capybara version:

FactoryBot.create(:user, email: 'test@example.com', password: 'mypassword')

visit sign_in_path
fill_in 'Email', with: 'test@example.com'
fill_in 'Password', with: 'mypassword'
click_on 'Sign In'

visit edit_user_path
fill_in 'First Name', with: 'John'
fill_in 'Last Name', with: 'Smith'
fill_in 'Age', with: '30'
click_on 'Save'

visit profile_path
expect(page).to have_content('John Smith, 30')

The sign in portion is usually abstracted away in Capybara, too, so the scenario would look more like this:

FactoryBot.create(:user, email: 'test@example.com', password: 'mypassword')

sign_in

visit edit_user_path
fill_in 'First Name', with: 'John'
fill_in 'Last Name', with: 'Smith'
fill_in 'Age', with: '30'
click_on 'Save'

visit profile_path
expect(page).to have_content('John Smith, 30')

That’s not too crazy at all. In order for Cucumber to be a superior solution to using bare Capybara, it would have to have some pretty strong benefits to compensate for the maintenance burden and cognitive overhead it adds. But it doesn’t.

So what do I recommend doing instead of using Cucumber? I think just using Capybara by itself is fine, and better than using Capybara + Cucumber. I also think Capybara + page objects is a pretty good way to go.

Rails testing resource roundup

.resource-item {
border-bottom: 1px solid #DDD;
padding-bottom: 50px;
margin-top: 30px;
margin-bottom: 50px;
}

Below is a list of testing resources I’ve either used myself or heard recommended. I tried to make the list as Ruby-centric as possible, although some resources from other ecosystems are so good that I didn’t want to exclude them.

I intend this to be a living list that grows over time. If you know of something that should be on this list but isn’t, please let me know, either in a comment on this page or via email.

Disclosure statement: None of the links below are affiliate links, although I do have a relationship with some of the authors/creators of these resources.

Section One: Ruby/Rails Specific Resources

Print Book: Rails 5 Test Prescriptions


Excerpt from Pragmatic Bookshelf summary:

“Does your Rails code suffer from bloat, brittleness, or inaccuracy? Cure these problems with the regular application of test-driven development. You’ll use Rails 5.2, Minitest 5, and RSpec 3.7, as well as popular testing libraries such as factory_bot and Cucumber.”

Details at The Pragmatic Bookshelf

Side note: you can also listen to my interview with author Noel Rappin on the Ruby Testing Podcast.

eBook: Everyday Rails Testing with RSpec


Summary from Leanpub:

“Real-world advice for adding reliable tests to your Rails apps with RSpec, complete with expanded, exclusive content and a full sample application. Updates for 2017 now available—RSpec 3.6, Rails 5.1, and more! Learn to test with confidence!”

Details at Leanpub

Screencast Series: Destroy All Software

Not everything on Destroy All Software (DAS) is testing-related but a lot of it is. I often see DAS recommended when people ask for testing-related resources.

Destroy All Software Catalog

Online Course Series: Upcase’s “Learn Testing” Courses

Summary from Upcase website:

“Test-driven development, or TDD, is the practice of writing your tests firsts, then using those tests to guide you as you write your actual production code. This may sound crazy, but it turns out that it makes writing code much easier. It provides a clear workflow and next steps while you’re building and has the added benefit of producing a test suite you can have confidence in. With these courses and videos we’ll teach you everything you need to know to get started with TDD.”

Details at Upcase

Print Book/eBook: Effective Testing with RSpec 3

Excerpt from Pragmatic Bookshelf summary:

“This definitive guide from RSpec’s lead developer shows you how to use RSpec to drive more maintainable designs, specify and document expected behavior, and prevent regressions during refactoring. Build a project using RSpec to design, describe, and test the behavior of your code. Whether you’re new to automated tests or have been using them for years, this book will help you write more effective tests.”

Details at The Pragmatic Bookshelf

Print Book/eBook: Practical Object-Oriented Design in Ruby

This isn’t specifically a testing book but I’ve seen it recommended a number of times as a book that will help you write better Ruby tests.

Excerpt from summary:
“[Practical Object-Oriented Design in Ruby] explains object-oriented design (OOD) using realistic, understandable examples. POODR* is a practical, readable introduction to how OOD can lower your costs and improve your applications.”

Details at Author’s Website

Section Two: Non-Rails-Specific Resources

Print Book: Growing Object-Oriented Software, Guided by Tests

This book is a classic in the testing world. I first read Growing Object-Oriented Software, Guided by Tests (GOOS) when I was clueless about testing. It helped me get oriented and learn what’s what. Among the most important concepts I learned from this book is the idea of a Walking Skeleton.

Details at Amazon

Print Book: Working Effectively with Legacy Code

I’ve worked on dozens of codebases so far in my career. Most of them have been legacy code. This book was super helpful in showing techniques like the Sprout Method technique to help get legacy code under control.

Details at Amazon