Category Archives: Programming

A systematic methodology for solving any programming problem

In order to fix any problem, you need to know two things:

  1. What exactly is wrong
  2. How to fix it

The beautiful thing is that if you have the right answers for both the two items above, you’re guaranteed to fix the problem. If your fix doesn’t work, then it necessarily means you were wrong about either #1 or #2.

The fastest way to solve any problem is to focus first on finding out what’s wrong, with reasonable certainty, before you move onto the step of trying to fix the problem. “Diagnose before you prescribe.”

Why it’s faster to separate the diagnosis from the fix

There are two reasons why I think it’s faster to separate the diagnosis from the fix than to mix them up. The first is that if you don’t separate the two jobs, you’re liable to resort to guessing.

Guessing has its place but guessing is more productive when the guesses are founded on a decent level of understanding of the situation. The beginning of a problem-solving endeavor is when your understanding is the lowest. At this time your guesses will be the least informed by reality. At best, you will happen to make some correct guesses by luck. More likely, you’ll just waste time. The worst case is that you lead yourself down fruitless and time-consuming paths because you prescribed before you diagnosed.

The other reason has to do with mental clarity. If you mix diagnosing with solving, you’re liable to get confused. You’re more liable to flail and throw things at the wall rather than advance methodically toward a solution. You’re likely to run out of mental “RAM” and subsequently “crash”, losing your whole train of thought. All these things are pure waste.

How to diagnose a problem

Step zero: a scientific mindset

Science is the pursuit of the answer to the question: “What is true?” Since reality is very complex and often misleading, scientists need to be very careful about what they bestow with the label of “true”. It’s surprisingly easy to get fooled. In order for any conclusion to be true, it must be based on premises that are also true, otherwise the conclusion is suspect.

When any new piece of information enters your awareness, you need to pass that piece of information through a filter of scrutiny before you allow yourself to call it a “fact”. Don’t just believe something is true because it seems true. Many things that seem true aren’t not only not true but wildly wrong (e.g. a geocentric universe). You must require some sort of justification for believing something is true, and the justification you require should be proportionate to that thing’s size and prominence inside of whatever you’re trying to figure out.

This is the type of mindset we need to adopt when problem-solving. We need to practice science.

Step one: state the symptom

The first step toward diagnosing a problem is to make a problem statement. A problem statement is simply an expression of what you know to be wrong based on what you see.

At first your problem statement might be vague because although you know what the symptom of the problem is, you don’t know the root cause. You might not even know in what area the root cause lies.

Resist the temptation to be overly specific in your problem statement. A vague problem statement might not seem very helpful, but at least it’s not hurtful. A problem statement that’s precise but untrue causes much more harm than a problem statement that’s true but vague.

Robert M. Pirsig put this quite nicely in Zen and the Art of Motorcycle Maintenance:

In Part One of formal scientific method, which is the statement of the problem, the main skill is in stating absolutely no more than you are positive you know. It is much better to enter a statement “Solve Problem: Why doesn’t cycle work?” which sounds dumb but is correct, than it is to enter a statement “Solve Problem: What is wrong with the electrical system?” when you don’t absolutely know the trouble is in the electrical system.

When you’re satisfied that you have a true problem statement, write it down. Writing down the problem statement allows you to clear your “mental RAM” so you can use that precious limited RAM for other things.

In the remainder of this diagnosis discussion I’ll use the example problem statement of “site is down”.

By the way, I must make a caveat here. The problem statement of “site is down” depends on me knowing that the site is down. It wouldn’t be valid for me to receive a phone call from someone telling me the site is down and then make a problem statement that the site is down. Literally everything must be scrutinized. Just because someone tells me the site is down doesn’t necessarily mean that the site is actually down. It could mean that that person’s wi-fi isn’t working, or that I’m being pranked. I must verify for myself that the site is down (by attempting to visit it myself, for example) before I accept it as a true fact that the site is down.

Step two: isolate the root cause

Isolating the root cause is usually done by degrees. The idea with isolating the root cause is to progressively refine the original problem statement until the problem statement is so narrow and specific that the root cause is obvious.

The way to progressively narrow down the problem is to pose yes-or-no questions and determine what the answer is for each. It’s kind of like playing a game of “20 questions”. When I play 20 questions I like to ask questions like “Is it man-made?”, “Does it use electricity?”, “Could I pick one up?” which are each broad but also each eliminate a broad scope of possibilities.

Unlike 20 questions, we can’t get the answer to each of our questions just by having someone tell us. We need to find the answers ourselves by performing some sort of test. This is another area where it’s very important not to let ourselves get fooled.

Each test we perform needs to only change one variable at a time (using the scientific sense of the word variable, not the programming sense). If, for example, you delete line 26 in a file, delete line 4 from another file, restart the server, and get an error message, you can’t conclude that deleting line 26 is what caused the error message. It might be that deleting line 4 did it. It might even be that none of your changes did it, some somebody else changed something without restarting the server, and your server restart only applied some previous unrelated change. You must poke at each item from several directions in order to be reasonably sure you have the truth.

You will also necessarily need to bring facts into the picture from outside your tests. Be very careful to distinguish “strong” facts from “weak” facts. For example, a user might tell you they saw an error message on page X at about 4:30 yesterday. There’s no particular reason to disbelieve the user, but you also can’t take their word for it either. They could be misremembering. They could be confused about what page they saw. They might even not understand the boundaries between your application and, e.g., the operating system, meaning that what you thought was a bug report was really just a Windows error that the user experienced. So take these sorts of “facts” with a grain of salt. Give them, say, 60% credence instead of 100% credence.

Also note that this step is “isolate the root cause”, not “think really hard to guess what the root cause might be”. Empiricism is much cheaper than reasoning. It’s much faster to find where a problem lies than to try to figure out what the problem is. You can save the “what” for after you’ve found the “where”. Once you’ve found the “where”, the “what” will be about 100 times easier.

Let’s continue with the “website is down” example.

Websites work because a user’s request hits a server and then the server successfully returns a response, which is then displayed by a browser. If the website isn’t working, the problem must lie somewhere in that request chain. (BTW, “request chain” is just a term I made up.)

Based on this fact, I can refine my problem statement to: Something is broken in the request chain. It’s important to note that when I say “request chain”, I mean it in the very broadest sense possible. I conceive that chain to include everything that happens between the user typing the URL and a page being displayed. Otherwise I risk excluding the true root cause from my search area.

At this point I haven’t really made my problem statement any narrower, but I have made it more specific without making it any less true. My problem statement is now sufficiently specific that I can identify some investigation steps.

I’ll now ask myself the following questions:

  1. Does the DNS name for my website successfully resolve?
  2. When I send a request to my site’s URL, does that request actually make it to a server?
  3. If the request makes it to the server, do I see my application logging something in response?
  4. etc.

I typically don’t list out a large number of steps in advance, but rather I list one or two investigation steps, carry them out, and then think of one or two more.

The “site is down” example I’m using is a real one that actually happened to me some time ago. My memory is that when I SSH’d into a server to investigate, I discovered that there was an “out of disk space” error happening, and indeed, the server was out of disk space.

So in this case I refined my problem statement further to: one of the web servers is out of disk space. In this particular case I was done. The problem statement was sufficiently specific that it was obvious what to do: kill this server and spin up a fresh instance (and also add some disk space monitoring so this never happens again!).

If I hadn’t reached the end of the diagnosis process so easily, I would have continued posing yes/no questions of yes/no specificity until I had a specific enough problem statement that I knew what the fix was.

How to fix a problem

You know you’ve found the diagnosis to a problem when your problem statement is sufficiently specific that it’s obvious what the problem is.

When it’s obvious what the problem is, it’s often obvious what the solution is too. But not always. Often there are multiple possible ways to fix a problem. Sometimes it’s not clear what the palette of possible solutions is, or how easy each one would be to implement.

Here’s how to proceed in those situations.

Step one: enumerate all possible solutions

List all the possible solutions to the problem without filtering for elegance, ease of implementation, or anything else.

Step two: rank the solutions from most appealing to least appealing

Put the most simplest, most elegant solutions at the top and the dirtiest hacks at the bottom.

Step three: try the most appealing solution

Ideally, the most appealing solution also happens to be easy to implement. If it’s not, then you have a decision to make.

Your decision is whether to power through and implement the elegant solution even though it’s time-consuming, or to give up and implement a less appealing solution instead.

The way I make this decision is to spend time and effort on a solution in proportion to how appealing the next-most-appealing solution is. If there’s another solution that seems equally good, I’ll give up fast since the other solution might give me an equally acceptable outcome more cheaply. If all the other solutions are awful hacks, I’ll spend as much time as needed getting my solution to work rather than resorting to a hack.

Step four: move on

If I’ve decided that my current solution is sufficiently tricky that “the juice is not worth the squeeze”, then I’ll move to the next-most-appealing solution. I’ll cross my current solution off the list (at least for now) and then start again at step three with my next solution.

Step five: regroup

If I’ve tried all possible solutions and nothing works, I’ll see if I can think of any more possible solutions. If so, I’ll repeat the process using my new list of potential solutions. If I can’t think of any more possible solutions, I’ll go back to my original list and repeat the whole process, but this time with more effort.

It always works

Following the above methodology basically always works. It’s not a question of whether this methodology will yield the answer to a problem, it’s a matter of how much time spent solving a problem is justifiable relative to the cost of leaving the problem unsolved. Sometimes it’s wiser just to leave the problem unsolved.

But most of the time this methodology leads to the answer quite quickly. If you follow these steps you’ll be able to solve virtually any problem, and in much less time than most other programmers, and with much less confusion and frustration.

When to use Factory Bot’s traits versus nested factories

When I wrote about nested factories in Factory Bot, I got a couple of questions to the effect of “why wouldn’t you use traits for that?”

In responses to these questions, I’ll lay out my method for deciding when to use traits versus nested factories.

“Is” versus “has”

My method is pretty simple: If the factory I’m considering has something, I use a trait. If the factory is something, I use a nested factory. Let’s look at a concrete example.

“Has” example (trait)

In the following example I want to create a special kind of user record, a user that has a phone number. The user is still conceptually a regular old user. The only difference is that this user happens to have a value for its phone_number attribute.

FactoryBot.define do
  factory :user do
    username { Faker::Internet.username }
    password { Faker::Internet.password }

    trait :with_phone_number do
      phone_number { Faker::PhoneNumber.phone_number }
    end
  end
end

When I want to create a user that has a phone number, I do so by doing FactoryBot.create(:user, :with_phone_number).

“Is” example (nested factory)

In the following example I want to create a special kind of user record, a user that is a physician. Conceptually, a physician user is not just a regular old user. This type of user is different in kind. A physician user has different capabilities from a regular user and is used in different ways from a regular user.

FactoryBot.define do
  factory :user do
    username { Faker::Internet.username }
    password { Faker::Internet.password }

    factory :physician_user do
      role { 'physician' }
    end
  end
end

When I want to create a physician user, I do so by doing FactoryBot.create(:physician_user). Note the contrast between this and FactoryBot.create(:user, :with_phone_number).

Takeaway

When deciding whether to use a trait or a nested factory, consider whether the record has something or if it is something. If it has something, use a trait. If it is something, use a nested factory.

Nested factories in Factory Bot: what they are and how to use them

Sometimes you want to be able to create records that are 95% the same as the “default” but have one or two small differences. Nested factories can be useful for this purpose.

Physician user example

Let’s use the following as an example. Let’s say that in the majority of your application’s tests that involve a User, you want just a regular old user. Maybe you have 30 such tests.

But for a handful of tests you want to use a special kind of user, a user that indicates that the person using the application is a physician.

In this scenario, the technical difference between a “physician user” and a regular user is that a physician user has a role attribute that’s set to 'physician'. Let’s say there are 6 tests that use a physician user, quite a small proportion of the total.

Options for addressing this challenge

You have the following options for addressing this challenge.

First, you could set the role to “physician” individually on each of your 6 physician user tests. Unfortunately this would be bad for all the reasons that duplication is bad. If the nature of your physician user were ever to change, you’d have to know that you’d have to make the change in all 6 of these places. This solution isn’t great.

Alternatively, you could alter the user factory to always have role set to “physician”. Under this scenario, every user would always be a physician user, even when it’s not necessary. Even if this would function properly from a technical perspective, it would be misleading to any maintainers of the test suite. For any test, it’s a good idea to create the minimum amount of setup data and no more. An outside observer has no way of knowing which parts of a test’s setup data are necessary and which aren’t, and so must assume it’s all potentially necessary. We wouldn’t want to send our future maintainers an untruthful message.

A third option is to create an alternate version of the user factory that inherits from the original, and use that version in our physician tests. In this scenario we’re not duplicating our setup steps, and we’re also not modifying our “default” factory. The name for this in Factory Bot is nested factories.

How nested factories work

Consider the following User factory.

FactoryBot.define do
  factory :user do
    username { Faker::Internet.username }
    password { Faker::Internet.password }
  end
end

In addition to the above, we can define a nested factory called :physician_user which will have all the same attributes as our regular users, but with the extra attribute of role with the value of 'physician'.

FactoryBot.define do
  factory :user do
    username { Faker::Internet.username }
    password { Faker::Internet.password }

    factory :physician_user do
      role { 'physician' }
    end
  end
end

To create a physician user we would simply invoke the following:

FactoryBot.create(:physician_user)

Unlike root-level factory names which map to Active Record models, nested factory names are fully arbitrary.

Takeaways

  • If you have setup data needs that are common across tests, it’s usually not a good idea to duplicate the setup. Duplication in test code usually has the same negative consequences as duplication in application code.
  • Each test should have as much setup data as is necessary for that test and no more. Otherwise a maintainer will have a hard time telling what’s necessary and what’s not.
  • Factory Bot’s nested factories can help us keep our test code DRY while continuing to adhere to certain good testing principles.

How to run RSpec with headless Chrome/Chromium on Alpine Linux

Why Alpine Linux?

When you Dockerize a Rails application, you have to choose which distribution of Linux you want your container to use.

When I first started Dockerizing my applications I used Ubuntu for my containers because that was the distro I was familiar with. Unfortunately I discovered that using Ubuntu results in slow builds, slow running of commands, and large image sizes.

I discovered that Alpine Linux is popular for Docker containers because it affords better performance and smaller images.

Alpine + Capybara problems

Alpine had its own drawback though: I couldn’t run my tests because it wasn’t as straightforward in Alpine to get a Capybara + ChromeDriver configuration working on Alpine.

The evident reason for this is that Alpine can’t run a normal Chrome package the way Ubuntu can. Instead, it’s typical on Alpine to use Chromium, which doesn’t quite play nice with Capybara the way Chrome does.

How to get Alpine + Capyabara working

There are three steps to getting Capybara working on Alpine.

  1. Use selenium-webdriver instead of webdrivers
  2. Install chromium, chromium-chromedriver and selenium on your Docker image
  3. Configure a Capybara driver

Use selenium-webdriver instead of webdrivers

The first step is very simple: if you happen to be using the webdrivers gem in your Gemfile, replace it with selenium-webdriver.

Install chromium, chromium-chromedriver and selenium on your Docker image

The next step is to alter your Dockerfile so that chromium, chromium-chromedriver and selenium are installed.

Below is a Dockerfile from one of my projects (which is based on Mike Rogers’ fantastic Docker-Rails template).

I’ll call out the relevant bits of the file.

chromium chromium-chromedriver python3 python3-dev py3-pip

This line, as you can see, installs chromium and chromium-chromedriver. It also installs pip3 and its dependencies because we need pip3 in order to install Selenium. (If you don’t know, pip3 is a Python package manager.)

Here’s the line that installs Selenium:

RUN pip3 install -U selenium

And here’s the full Dockerfile.

FROM ruby:2.7.2-alpine AS builder

RUN apk add --no-cache \
    build-base libffi-dev \
    nodejs yarn tzdata \
    postgresql-dev postgresql-client zlib-dev libxml2-dev libxslt-dev readline-dev bash \
    #
    # For testing
    chromium chromium-chromedriver python3 python3-dev py3-pip \
    #
    # Nice-to-haves
    git vim \
    #
    # Fixes watch file issues with things like HMR
    libnotify-dev

RUN pip3 install -U selenium

FROM builder AS development

# Add the current apps files into docker image
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install any extra dependencies via Aptfile - These are installed on Heroku
# COPY Aptfile /usr/src/app/Aptfile
# RUN apk add --update $(cat /usr/src/app/Aptfile | xargs)

ENV PATH /usr/src/app/bin:$PATH

# Install latest bundler
RUN bundle config --global silence_root_warning 1

EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0", "-p", "3000"]

FROM development AS production

COPY Gemfile /usr/src/app
COPY .ruby-version /usr/src/app
COPY Gemfile.lock /usr/src/app

COPY package.json /usr/src/app
COPY yarn.lock /usr/src/app

# Install Ruby Gems
RUN bundle config set deployment 'true'
RUN bundle config set without 'development:test'
RUN bundle check || bundle install --jobs=$(nproc)

# Install Yarn Libraries
RUN yarn install --check-files

# Copy the rest of the app
COPY . /usr/src/app

# Precompile the assets
RUN RAILS_SERVE_STATIC_FILES=enabled SECRET_KEY_BASE=secret-key-base RAILS_ENV=production RACK_ENV=production NODE_ENV=production bundle exec rake assets:precompile

# Precompile Bootsnap
run RAILS_SERVE_STATIC_FILES=enabled SECRET_KEY_BASE=secret-key-base RAILS_ENV=production RACK_ENV=production NODE_ENV=production bundle exec bootsnap precompile --gemfile app/ lib/

The next step is to configure a Capybara driver.

Configure a Capybara driver

Below is the Capybara configuration that I use for one of my projects. This configuration is actually identical to the config I used before I started using Docker, so there’s nothing special here related to Alpine Linux, but the Alpine Linux configuration described in this post won’t work without something like this.

# spec/support/chrome.rb

driver = :selenium_chrome_headless

Capybara.server = :puma, {Silent: true}

Capybara.register_driver driver do |app|
  options = ::Selenium::WebDriver::Chrome::Options.new

  options.add_argument("--headless")
  options.add_argument("--no-sandbox")
  options.add_argument("--disable-dev-shm-usage")
  options.add_argument("--window-size=1400,1400")

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.javascript_driver = driver

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by driver
  end
end

Remember to make sure that the line in spec/rails_helper that includes files from spec/support is uncommented so this file gets loaded.

How to run a PostgreSQL database in a Docker container

Why you would want to do this

There are two main reasons for Dockerizing an application: for production use and for development use.

When Dockerizing an application for development, sometimes you might want to package up every single thing in the development environment. For example, in a Rails app, you might want to run each of PostgreSQL, Redis, and Ruby/Rails inside containers.

The drawbacks of Dockerizing everything

But then again you might not want to do that. To continue with the Rails app example, it can be very slow to e.g. run your tests inside a Docker container every single time you want to run a test.

You may instead wish to run PostgreSQL and Redis inside containers but leave Rails out of it, and instead connect Rails to these various containerized components.

This particular blog post covers the most basic building block of the above scenario: running PostgreSQL inside of a Docker container. I’m not going to cover actually connecting an application to the database in this post. This post is not meant to give you a useful result, but rather to help you gain an understanding of this building block.

What we’re going to do

Here’s what we’re going to do:

  1. Create a Docker image that can run PostgreSQL
  2. Run a container based on that image
  3. Connect to the PostgreSQL instance running inside that container to verify that it’s working

Let’s get started.

Creating the image

The first step is to create a Dockerfile. Name this file Dockerfile and put it inside a fresh empty directory.

FROM library/postgres
COPY init.sql /docker-entrypoint-initdb.d/

The first line says to use the postres image from Docker Hub as our base image.

The second line says to take our init.sql file (shown below) and copy it into the special /docker-entrypoint-initdb.d/ directory. Anything inside the /docker-entrypoint-initdb.d/ directory will get run each time we start a container.

Here are the contents of init.sql, which should go right next to our Dockerfile.

CREATE USER docker_db_user;
CREATE DATABASE docker_db_user;
GRANT ALL PRIVILEGES ON DATABASE docker_db_user TO docker_db_user;

As you can see, this file contains SQL code that creates a user, creates a database, and then grants permissions to that user on the database. It’s necessary to do all this each time a container starts because a container doesn’t “remember” all this stuff from run to run. It starts as a blank slate each time.

Building the image

Now let’s build our image.

docker build .

Once this runs, we can run docker images to see the image we just created.

docker images

You should see something that looks like this:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              396bfb8e3373        7 minutes ago       314MB

Running the container

We can use the docker run command to run a container based on our image. There are a few arguments that need to be passed, which I’ve annotated below.

docker run \
  --name dev-postgres \             # the arbitrary name of the container we're starting
  -e POSTGRES_PASSWORD=mypassword \ # the arbitrary db superuser password, which must be set to something
  396bfb8e3373                      # the image ID from our previous docker build step

Connecting to the database

Finally, we can connect to the database to verify that it works.

docker exec --interactive dev-postgres psql -U docker_db_user

If running this command puts you inside the PostgreSQL CLI, you’re good!

Dissecting Factory Bot’s factory definition syntax

Mysterious syntax

If you’ve used Factory Bot at all, you’ve seen syntax like this:

FactoryBot.define do
  factory :user do
    first_name { 'John' }
    last_name { 'Smith' }
    email { 'john.smith@example.com' }
  end
end

When I was first getting started with Rails and I wasn’t very familiar with Ruby, I would look at these files and understand what’s going on conceptually but I would have no idea what’s going on syntactically.

Why I didn’t understand this code

There were three barriers to my understanding at that early stage of my Ruby experience:

  • Ruby’s optional parentheses feature, although it can help code be very expressive, can also make it hard for beginners to tell what’s what.
  • I didn’t understand Ruby blocks yet.
  • I didn’t know about dynamically-defined methods.

Let’s look at a modified example that will make some of the syntax clearer.

A modified factory definition example

Below is a version of the above factory definition that’s functionally equivalent but with two syntactical changes.

The first change is that I’ve included parentheses for all method calls. The second is that I’ve changed all block usages to do syntax instead of the shorthand {} syntax.

FactoryBot.define() do # define is a method
  factory(:user) do
    first_name do
      'John'
    end

    last_name do
      'Smith'
    end

    email do
      'john.smith@example.com'
    end
  end
end

You can see here that define and factory are each just methods. Each of the two methods takes a block. (If you’re not very comfortable with blocks yet, check out my post on understanding Ruby blocks.)

first_name, last_name and email are also methods that take blocks, speaking loosely. Before we can talk about those we need to talk about methods versus messages.

Methods and messages

When you call a method on an object, it can be said that you’re sending a message to that object. For example, when you call "5".to_i, you’re sending the message to_i to the object "5", which is of course a String.

In the above case, the message to_i also happens to be a method that’s defined on String. This doesn’t need to be the case though. We could send any message at all to String, String just might not necessarily respond to that particular message. That’s why we have the respond_to? method, to see what messages an object responds to.

The messages an object will respond to need not be limited to the methods that are defined on that object. An object author can use the method_missing method to allow an object to respond to any message that’s sent to it, and respond in any way that the object author chooses.

Factory definitions and messages

What’s likely happening with first_name, last_name and email is that Factory Bot is using method_missing to allow arbitrary messages to be sent, and if the message (e.g. the message of first_name) matches an attribute on the model that the factory is for, then Factory Bot uses the block passed with the message to set the value of that attribute.

Takeaways

  • Factory Bot’s factory definitions are made out of methods and blocks.
  • Adding parentheses to any piece of DSL code can often make the code clearer.
  • Ruby objects can be passed arbitrary messages, and objects can be designed to respond to those messages.

The purpose of private methods and when to use them

Certain concepts in programming seem to suffer from a poor understanding among many programmers. Private methods are one of those concepts. Mistaken and confused advice on this topic abounds online.

In this post I’ll attempt to clear up some misconceptions regarding private methods as well as give my own conception of what private methods are for and when to use them.

The wrong answers you’ll find online

Before I describe my conception of private methods, I’ll discuss some of the wrong answers you can easily find online in hopes of inoculating you against their wrongness.

Wrong answer: “private methods are for breaking up tasks into smaller parts”

As of this writing, if I google for “purpose of private methods” and click on the first result, the following Stack Overflow answer comes up highlighted:

Private methods are useful for breaking tasks up into smaller parts, or for preventing duplication of code which is needed often by other methods in a class, but should not be called outside of that class.

It can’t be said that the reason for the existence of private methods is to break up tasks into smaller parts or prevent duplication, because both those things can be achieved with public methods.

Except for the last part which says “should not be called outside of that class” (which we’ll get to shortly), this answer is wrong.

Wrong answer: “private methods are for removing duplication”

From the top answer on the second link on Google:

Private methods are typically used when several methods need to do the exact same work as part of their responsibility (like notifying external observers that the object has changed), or when a method is split in smaller steps for readability.

This is basically the same answer as the first, and it’s of course wrong for the same reason.

The real purpose of private methods

I’ve been making heavy use of private methods for years. To the best I’ve been able to determine, here’s why I do it.

  1. Private methods help reduce the size of an object’s API
  2. Private methods indicate what’s safe to refactor

Let me explain why each of these things is good.

Private methods help reduce the size of an object’s API

Complexity is the enemy of understandability. The simpler I can make my code, the easier it will be to understand, and therefore the cheaper my code will be to maintain.

Let’s imagine I have an object with 8 methods, all public. That means there are 8 different ways other parts of my program could potentially use my object, and 8 different ways that my object interfaces with rest of my program. All 8 of these interfacings are things I have to understand in order to understand my program.

You could loosely say that in the above scenario, I have an “understandability cost” of 8.

Now let’s imagine a different object which has only 2 public methods. It has some other methods too, but those are private. Now there are only two possible ways the rest of my program could use my object. This scenario has an “understandability cost” of 2.

I know that my “understandability cost” metric is silly and quite imprecise but I hope it illustrates the point. The smaller each object’s public API, the easier it will be to understand my object’s relationship with everything else.

Now let’s talk about refactoring.

Private methods indicate what’s safe to refactor

Every public method is susceptible to being called from some outside entity. If a method is being called from somewhere, I can’t delete the method or change its signature without also having to change every place where the method is called. The only thing I can do is refactor its contents.

Since private methods can’t be called outside the same class (not without some irresponsible hackery at least), I know that I’m free to refactor as much as I want and I won’t inadvertently break any outside code. It’s true that other parts of the same object might call my private methods and thus be affected by my refactorings, but it’s valuable to know that I won’t have to look any farther than that.

Private methods aren’t a code smell

If you google around about private methods for more than about 30 seconds, you’ll find a number of posts exploring the question of whether private methods are a code smell.

Don’t buy it. The arguments that private methods are a code smell fall apart under the least bit of scrutiny. Every argument I’ve seen applies equally to public methods.

Here are the arguments I’ve seen:

  • Private methods indicate that the class is doing too many things (Single Responsibility Principle violation)
  • Private methods have too many dependencies because they directly access or modify internal state

Both the above arguments could apply just as much to public methods. Public methods could indicate the class is doing too many things. Public methods can access internal state just like private methods.

When to use private methods

My rule for when to use private methods is very simple. If it can be private, it is private. In other words, I make all methods private by default, and only make them public if they need to be in order for my program to work.

Testing private methods

I wrote a whole separate blog post about testing private methods. The common question is: “should private methods be tested”?

My answer is yes, private methods should be tested, but only indirectly through their public methods.

In order for the public/private idea to work, private methods have to be private from all outside code, including test code. When tests directly call private methods, the private methods can’t be refactoring without also changing the tests, throwing away one of the main benefits of testing: the ability to refactor with confidence.

Takeaways

The two benefits of private methods are that they a) help reduce the size of an object’s API and b) indicate what’s safe to refactor.

Private methods aren’t a code smell.

If a method can be made private, it should be made private. Making a method public when it doesn’t need to be brings no advantage and only creates a liability.

Do test private methods, but only indirectly through their public interfaces.

Should I be doing test-driven development?

When I see questions from beginners regarding learning testing, sometimes they seem to conflate testing with test-driven development (TDD). People will say “I have such-and-such question about TDD” but really it’s just a question about testing, nothing to do with TDD specifically.

Other people sometimes ask questions about whether TDD is “better” than writing tests after.

In this post I’ll try to clarify what’s TDD and what’s not. I’ll also explain whether I think it makes sense for testing beginners to try to practice TDD.

Testing != TDD

First of all, at the risk of stating the obvious, testing and TDD aren’t the same thing. TDD is a specific kind of testing practice where you write the tests before you write the code that makes the test pass. (If you want to go deeper into TDD, I highly recommend Kent Beck’s Test Driven Development: By Example.)

Learning vs. incorporating

Another mistake beginners sometimes make is to conflate learning testing with incorporating testing as a habitual part of their development workflow. They feel like they need to start adopting testing practices into their workflow from day one, and if they fail to do that, then they’ve failed at learning testing.

I think it’s more productive to separate the jobs of learning testing and applying testing. It’s not like skiing, where you learn it and do it at the same time. It’s more like basketball, where you practice free throws in your driveway and build some skills that way before you try to play a real game in front of an audience. You’ll get farther in the beginning if you separate the practice from the application of what you’ve learned to production tests. When you get comfortable enough, you can take off the training wheels and get all your practice from writing production tests.

TDD is beneficial but optional

TDD is super helpful in certain scenarios but it’s not something you absolutely need to learn when you’re first learning testing. I think it’s completely appropriate to first learn the fundamentals of testing in general, and then start to learn TDD once you’ve developed a decent level of comfort with testing.

I don’t always practice TDD

I’m not an advocate of practicing TDD 100% of the time in Rails, even for experienced testers. The reason is that when I’m building a new feature, I often have little idea what shape that feature will take, and the most realistic way for me to hammer it into shape is to just start building it. Once I’ve built some of the feature, then I’ll start adding tests. So, a portion of the time, I write my tests after writing my application code.

The place where I find TDD most useful is for model code. I practice TDD in my models a high percentage of the time. Once I’ve put the broad strokes of a feature in place, I’ll usually use TDD to work out the fine-grained aspects of it.

Takeaways

  • Testing and test-driven development aren’t the same thing.
  • When you’re first learning testing, it can be helpful to separate learning testing from applying testing.
  • You don’t need to learn TDD when you’re starting out.
  • I don’t always practice TDD or even advocate practicing TDD 100% of the time. I myself practice TDD maybe 60% of the time.

How do I add tests to an existing Rails project?

One of the most common questions asked by developers new to Rails testing is “How do I add tests to an existing Rails project?”

The answer largely depends on your experience level with testing. Here are my answers based on whether you have little testing experience or if you’re already decently comfortable with testing.

If you have little testing experience

If you have little testing experience, I would suggest getting some practice on a fresh Rails app before trying to introduce testing to the existing Rails project you want to add tests to.

Adding tests to an existing project is a distinct skill from writing tests for new projects. Adding tests to an existing project can be difficult even for very experienced testers, for reasons described below.

At the same time, you probably don’t want to wait a year to learn testing before you start enjoying the benefits of testing on your existing Rails app. What I would suggest is to first start a fresh throwaway Rails app for the purpose of learning testing. Then, once you’ve gotten a little experience there, see if you can apply something to your existing Rails app. Then, if things get too hard in the existing app, switch back to the throwaway app so you can strengthen your skills more. Continue switching back and forth until you don’t need to anymore.

If you’re already comfortable with testing

Here’s how I suggest adding tests to an existing Rails project: 1) develop a shared vision with your team, 2) start with what’s easiest, then 3) expand your test coverage.

Develop a shared vision

Going from no tests to decent test coverage is unfortunately not as simple as just deciding one day that from now on we’re going to write tests.

The team maintaining the codebase needs to decide certain things, like what testing tools they’re going to use and what testing approach they’re going to use.

In other words, if the team wants to go from point A to point B, they have to decide exactly where point B is and how they intend to try to get there.

Start with what’s easiest

When adding tests to a codebase that has few or no tests, it might seem logical to start by adding tests where tests would be most valuable. Or it might seem logical to require all new changes to have tests. Unfortunately, both these ideas have problems.

The features in an application that are most valuable are also likely to be among the most non-trivial. This means that tests for these features will probably be relatively hard to write due to the large amount of setup data needed. Code written without testability in mind can also be difficult to test due to entangled dependencies.

Requiring all new changes to have tests also has problems. New changes aren’t usually independent of existing code. They’re usually quite tangled up. This brings us back to the same problem we’d have adding tests to our most important features: the setup and dependencies make adding tests difficult, sometimes prohibitively so.

What I would do instead is start with what’s easiest. I would look for the simplest CRUD interfaces in the app and add some tests there, even if those particular tests didn’t seem to add much value. The idea isn’t to add valuable tests right from the start but to establish a beachhead that can be expanded upon.

Expand

Once you have a handful of tests for trivial features, you can add tests for increasingly complicated features. This will give you a much better shot at ending up with good test coverage than trying to start with the most valuable features or trying to add tests for all new changes.

The mechanical details

If your existing Rails application doesn’t have any testing infrastructure, I would suggest taking a look at my how I set up a Rails application post. (Remember that it’s possible to apply an application template to an existing project.)

As you add tests to your project starting with the most trivial features, I would suggest starting with system specs as opposed to model specs or any other type of specs. The reason is that system specs are often more straightforward to conceive of and understand. If you’d like a formula you can apply to add system specs to almost any CRUD feature, you can find that here.

Then, as you get deeper into adding tests to your application, I would suggest two resources: Working Effectively with Legacy Code by Michael Feathers and my post about using tests as a tool to wrangle legacy projects. You might not consider your project a legacy project, but the techniques will be useful anyway.

What are all the Rails testing tools and how do I use them?

One of the most common questions for Rails developers new to testing is “What are all the Rails testing tools and how do I use them?”

I’ll explain what the major tools are but I want to preface it by saying that the most important thing to learn to be a successful tester is testing principles, not testing tools. If you think of testing like a taco, the tools are the tortilla and the principles are the stuff inside the taco. The tortilla is essential but it’s really only a vehicle.

The following are the tools I use for my testing.

RSpec

RSpec is a test framework. A test framework is what gives us a structure for writing our tests as well as the ability to run our tests.

There are other test frameworks but RSpec is the most popular one for commercial Rails projects. The second most popular test framework is Minitest.

Test frameworks differ syntactically but the testing principles and practices are going to be pretty much the same no matter what framework you’re using. (If you’re not sure whether you should learn RSpec or Minitest, I write about that here.)

Factory Bot

One of the challenges of Rails testing is generating test data. For example, if you’re writing a test that logs a user in and then takes some action, you’re going to have to create a user in the database at the beginning of the test. Many tests require much more complicated test data setup.

There are two common ways of generating test data in Rails tests: fixtures and factories.

Fixtures

Fixtures typically take the form of one or more YAML files with some hard-coded data. The data is translated into database records one time, before any of the tests are run, and then deleted afterward. (This happens in a separate test database instance of course.)

Factories

With factories, database data is generated specifically for each test. Instead of loading all the data once at the beginning and deleting it at the end, data is inserted before each test case and then deleted before the next test case starts. (More precisely, the data isn’t deleted, but rather the test is run inside a database transaction and the data is never committed in the first place, but that’s a mechanical detail that’s not important right now.)

Relative merits of fixtures and factories

I tend to prefer factories because I like having my data generation right inside my test, close to where the test is happening. With fixtures the data setup is too distant from where the test happens.

In my experience, for whatever reason, most people who use RSpec use factories and most people who use Minitest use fixtures. If you’d like to learn more about factories and fixtures, I write more about it here.

Capybara

Some Rails tests only exercise Ruby code. Other tests actually open up a browser and simulate user clicks and keystrokes.

Simulating user input this way requires us to use some sort of tool to manipulate the browser. Capybara is a library that uses Ruby to wrap a driver (usually the Selenium driver), letting us simulate clicks and keystrokes using convenient Ruby methods.

For more examples of how to use Capybara, go here.

VCR and WebMock

One principle of testing is that tests should be deterministic, meaning they run the same way every time no matter what.

When an application’s behavior depends on external services (e.g. a third-party API like Stripe) it makes it harder to have deterministic tests. The tests can be made to fail by an internet connection failure or a temporary outage of the external service.

Tools like VCR and WebMock can help smooth out these challenges. VCR can let us run our tests against the real external service, but capture all the service’s responses in local files so that subsequent test runs don’t talk to the external service but rather just go off of the saved responses. That way, even if the internet connection fails or the service goes down, the tests still work.

WebMock is a tool that serves a similar purpose, although I usually use it in a more limited way. I don’t consider my test suite to be deterministic unless it doesn’t talk to the network at all, so I use WebMock to enforce that my test suite isn’t making any network requests.

Tools I don’t use

Cucumber is a somewhat popular tool when it comes to acceptance testing. It’s my view that Cucumber adds an extra layer of complexity and indirection without adding any value. Here are some details on why I don’t recommend Cucumber.

I also don’t use Shoulda matchers. Shoulda matchers make it easy and convenient to write certain kinds of tests, but the kinds of tests Shoulda helps you write are not a good kind of test to write in the first place. Shoulda helps you write tests that test your code’s implementation rather than its behavior. Here are more details on why I don’t recommend Shoulda.

Takeaways

Rails testing tools take some time to learn, but the important part (and perhaps more difficult part) is learning testing principles.

If you’re just getting started with Rails testing, the next step I would suggest is to learn about the different types of Rails tests and when to use them.