Category Archives: Programming

My general approach to Rails testing

My development workflow

The code I write is influenced by certain factors upstream of the code itself.

Before I start coding a feature, I like to do everything I can to try to ensure that the user story I’m working on is small and that it’s crisply defined. By small, I mean not more than a day’s worth of work. By crisply defined, I mean that the user story includes a reasonably precise and detailed description of what the scope of that story is.

I also like to put each user story through a reasonably thorough scrutinization process. In my experience, user stories often aren’t very thoroughly thought through and contain a level of ambiguity or inconsistency such that they’re not actually possible to implement as described.

I find that if care is taken to make sure the user stories are high-quality before development work begins, the development work goes dramatically more smoothly.

Assuming I’m starting with good user stories, I’ll grab a story to work on and then break that story into subtasks. Each day I have a to-do list for the day. On that to-do list I’ll put the subtasks for whatever story I’m currently working on. Here’s an example of what that might look like, at least as a first draft:

Feature: As a staff member, I can manage insurance payments

  • Scaffolding for insurance payments
  • Feature spec for creating insurance payment (valid inputs)
  • Feature spec for creating insurance payment (invalid inputs)
  • Feature spec for updating insurance payment

(By the way, I write more about my specific formula for writing feature specs in my post https://www.codewithjason.com/repeatable-step-step-process-writing-rails-integration-tests-capybara/.)

Where my development workflow and my testing workflow meet

Looking at the list above, you can see that my to-do list is expressed mostly in terms of tests. I do it this way because I know that if I write a test for a certain piece of functionality, then I’ll of course have to build the functionality itself as part of the process.

When I use TDD and when I don’t

Whether or not I’ll use TDD on a feature depends largely on whether it’s a whole new CRUD interface or whether it’s a more minor modification.

If I’m working on a whole CRUD interface, I’ll use scaffolding to generate the code, and then I’ll make a pass over everything and write tests. (Again, I write more about the details of this process here.) The fact that I use scaffolding for my CRUD interfaces makes TDD impossible. This is a trade-off I’m willing to make due to how much work scaffolding saves me and due to the fact that I pretty much never forget to write feature specs for my scaffold-generated code.

It’s also rare that I ever need to write any serious amount of model specs for scaffold-generated code. I usually write tests for my validators using shoulda-matchers, but that’s all. (I often end up writing more model specs later as the model grows in feature richness, but not in the very beginning.)

If instead of writing a whole new CRUD interface I’m just making a modification to existing code (or fixing a bug), that’s a case where I usually will use TDD. I find that TDD in these cases is typically easier and faster than doing test-after or skipping tests altogether. If for example I need to add a new piece of data to a CSV file my program generates, I’ll go to the relevant test file and add a failing expectation for that new piece of data. Then I’ll go and add the code to put the data in place to make the test pass.

The other case where I usually practice TDD is if the feature I’m writing is not a CRUD-type feature but rather a more of a model-based feature, where the interesting work happens “under the hood”. In those cases I also find TDD to be easier and faster than not-TDD.

The kinds of tests I write and the kind I don’t

I write more about this in my book, but I tend to mostly write model specs and feature specs. I find that most other types of tests tend to have very little value. By the way, I’m using the language of “spec” instead of “test” because I use RSpec instead of Minitest, but my high-level approach would be the exact same under any testing framework.

When I use mocks and stubs and when I don’t

I almost never use mocks or stubs in Rails projects. In 8+ years of Rails development, I’ve hardly ever done it.

How I think about test coverage

I care about test coverage enough to measure it, but I don’t care about test coverage enough to set a target for it or impose any kind of rule on myself or anything like that. I’ve found that the natural consequence of me following my normal testing workflow is that I end up with a pretty decent test coverage level. Today I checked the test coverage on the project I’ve been working on for the last year or so and the measurement was 97%.

The test coverage metric isn’t the main barometer for me though on the health level of a project’s tests. To me it seems much more useful to pay attention to how many exceptions pop up in production and what it feels like to do maintenance coding on the project. “What it feels like to do maintenance coding” is obviously not a verify quantifiable metric, but of course not everything that counts can be counted.

The difference between domains, domain models, object models and domain objects

I recently came across a question regarding the difference between domains and domain models. These terms probably mean different things to different people, but I’ll define the terms as I use them.

Domain

When I’m working on a software project, the domain is the conceptual area I’m working inside of. For example, if I’m working on an application that has to do with restaurants, the domain is restaurants.

Domain model

The world is a staggeringly complex place. Even relatively simple-seeming things like restaurants involve way more complexity than could be accurately captured in a software system. So instead of coding to a domain, we have to code to a domain model.

For me, a domain model is a separate thing from any particular code or piece of software. If I come up with a domain model for something to do with restaurants, I could express my domain model on a piece of paper if I wanted to (or just inside my head). My domain model is a standalone conceptual entity, regardless of whether I actually end up writing any software based on it or not.

A domain model also doesn’t even need to be consciously expressed in order to exist. In fact, on most software systems I’ve ever worked on, the domain model of the system only exists in the developers’ minds. The domain model isn’t something that someone planned at the beginning, it’s something that each developer synthesizes in his or her mind based on the code that exists in the application and based on what the developer understands about the domain itself.

Object model

The place where my domain model turns into actual code is in the object model. If my domain concepts include restaurant, order, and customer, then my object model will probably include objects like Restaurant, Order and Customer.

Domain object

Any object in my object model that also exist as a concept in my domain model I would call a domain object. In the previous example, Restaurant, Order and Customer would all be domain objects.

Not every object in a system is a domain object. Some objects are value objects. A value object is an object whose identity doesn’t matter. Examples of concepts that would make sense as value objects rather than domain objects are phone number values or money values.

One type of “object” that’s popular in the Rails world that I tend not to use is the service object, for reasons explained in the linked post.

Related reading: Domain Driven Design

How I wrote a command-line Ruby program to manage EC2 instances for me

Why I did this

Heroku is great, but not in 100% of cases

When I want to quickly deploy a Rails application, my go-to choice is Heroku. I’m a big fan of the idea that I can just run heroku create and have a production application online in just a matter of seconds.

Unfortunately, Heroku isn’t always a desirable option. If I’m just messing around, I don’t usually want to pay for Heroku features, but I also don’t always want my dynos to fall asleep after 30 minutes like on the free tier. (I’m aware that there are ways around this but I don’t necessarily want to deal with the hassle of all that.)

Also, sometimes I want finer control than what Heroku provides. I want to be “closer to the metal” with the ability to directly manage my EC2 instances, RDS instances, and other AWS services. Sometimes I desire this for cost reasons. Sometimes I just want to learn what I think is the valuable developer skill of knowing how to manage AWS infrastructure.

Unfortunately, using AWS by itself isn’t very easy.

Setting up Rails on bare EC2 is a time-consuming and brain-consuming hassle

Getting a Rails app standing up on AWS is pretty hard and time-consuming. I’m actually not even going to get into Rails-related stuff in this post because even the small task of getting an EC2 instance up and running—without no additional software installed on that instance—is a lot harder than I think it should be, and there’s a lot to discuss and improve just inside that step.

Just to briefly illustrate what a pain in the ass it is to get an EC2 instance launched and to SSH into it, here are the steps. The steps that follow are the command-line steps. I find the AWS GUI console steps roughly equally painful.

1. Use the AWS CLI create-key-pair command to create a key pair. This step is necessary for later when I want to SSH into my instance.

2. Think of a name for the key pair and save it somewhere. Thinking of a name might seem like a trivially small hurdle, but every tiny bit of mental friction adds up. I don’t want to have to think of a name, and I don’t want to have to think about where to put the file (even if that means just remembering that I want to put the key in ~/.ssh, which is the most likely case.

3. Use the run-instances command, using an AMI ID (AMI == Amazon Machine Image) and passing in my key name. Now I have to go look up the run-instances (because I sure as hell don’t remember it) and, look up my AMI ID, and remember what my key name is. (If you don’t know what an AMI ID is, that’s what determines whether the instance will be Ubuntu, Amazon Linux, Windows, etc.)

4. Use the describe-instances command to find out the public DNS name of the instance I just launched. This means I either have to search the JSON response of describe-instances for the PublicDnsName entry or apply a filter. Just like with every AWS CLI command, I’d have to go look up the exact syntax for this.

5. Run the ssh command, passing in my instance’s DNS and the path to my key. This step is probably the easiest, although it took me a long time to commit the exact ssh -i syntax to memory. For the record, the command is ssh -i ~/.ssh/my_key.pem ubuntu@mypublicdns.com. It’s a small pain in the ass to have to look up the public DNS for my instance again and remember whether my EC2 user is going to be ubuntu or ec2-user (it depends on what AMI I used).

My goals for my AWS command-line tool

All this fuckery was a big hassle so I decided to write my own command-line tool to manage EC2 instances. I call the tool Exosuit. You can actually try it out yourself by following these instructions.

There were four specific capabilities I wanted Exosuit to have.

Launch an instance

By running bin/exo launch, it should launch an EC2 instance for me. It should assume I want Ubuntu. It should let me know when the instance is ready, and what its instance ID and public DNS are.

SSH into an instance

I should be able to run bin/exo ssh, get prompted for which instance I want to SSH into, and then get SSH’d into that instance.

List all running instances

I should be able to run bin/exo instances to see all my running instances. It should show the instance ID and public DNS for each.

Terminate instances

I should be able to run bin/exo terminate which will show me all my instance IDs and allow me to select one or more of them for termination.

How I did it

Side note: when I first wrote this, I forgot that the AWS SDK for Ruby existed, so I reinvented some wheels. Whoops. After I wrote this I refactored the project to use AWS SDK instead of shell out to AWS CLI.

For brevity I’ll focus on the bin/exo launch command.

Using the AW CLI run-instances command

The AWS CLI command for launching an instance looks like this:

aws ec2 run-instances \
  --count 1 \
  --image-id ami-05c1fa8df71875112 \
  --instance-type t2.micro \
  --key-name normal-quiet-carrot \
  --profile personal

Hopefully most of these flags are self-explanatory. You might wonder where the key name of normal-quiet-carrot came from. When the bin/exo launch command is run, Exosuit asks “Is there a file defined at .exosuit/config.yml that contains a key pair name and path? If not, create that file, create a new key pair with a random phrase for a name, and save the name and path to that file.”

Here’s what my .exosuit/config.yml looks like:

---
aws_profile_name: personal
key_pair:
  name: normal-quiet-carrot
  path: "~/.ssh/normal-quiet-carrot.pem"

The aws_profile_name is something that I imagine most users aren’t likely to need. I personally happen to have multiple AWS accounts, so it’s necessary for me to send a --profile flag when using AWS CLI commands so AWS knows which account of mine to use. If a profile isn’t specified in .exosuit/config.yml, Exosuit will just leave the --profile flag off and everything will still work fine.

Abstracting the run-instances command

Once I had coded Exosuit to construct a few different AWS CLI commands (e.g. run-instances, terminate-instances), I noticed that things were getting a little repetitive. Most troubling, I had to always remember to include the --profile flag (just as I would if I were typing all this on the command line manually), and I didn’t always remember to do so. In those cases my command would get sent to the wrong account. That’s bad.

So I created an abstraction called AWSCommand. Here’s what a usage of it looks like:

command = AWSCommand.new(
  :run_instances,
  count: 1,
  image_id: IMAGE_ID,
  instance_type: INSTANCE_TYPE,
  key_name: key_pair.name
)

JSON.parse(command.run)

You can probably see the resemblance it bears to the bare run-instances usage. Note the conspicuous absence of the profile flag, which is now automatically included every single time.

Listening for launch success

One of my least favorite things about manually launching EC2 instances is having to check periodically to see when they’ve started running. So I wanted Exosuit to tell me when my EC2 instance was running.

I achieved this by writing a loop that hits AWS once per second, checking the state of my new instance each time.

module Exosuit
  def self.launch_instance
    response = Instance.launch(self.key_pair)
    instance_id = response['Instances'][0]['InstanceId']
    print "Launching instance #{instance_id}..."

    while true
      sleep(1)
      print '.'
      instance = Instance.find(instance_id)

      if instance && instance.running?
        puts
        break
      end
    end

    puts 'Instance is now running'
    puts "Public DNS: #{instance.public_dns_name}"
  end
end

You might wonder what Instance.find and instance.running? do.

The Instance.find method will run the aws ec2 describe-instances command, parse the JSON response, then grab the relevant JSON data for whatever instance_id I passed to it. The return value is an instance of the Instance class.

When an instance of Instance is instantiated, an instance variable gets set (pardon all the “instances”) with all the JSON data for that instance that was returned by the AWS CLI. The instance.running? method simply looks at that JSON data (which has since been converted to a Ruby hash) and checks to see what the value of ['State']['Name'] is.

Here’s an abbreviated version of the Instance class for reference.

module Exosuit
  class Instance
    def initialize(info)
      @info = info
    end

    def state
      @info['State']['Name']
    end

    def running?
      state == 'running'
    end
  end
end

(By the way, all the Exosuit code is available on GitHub if you’d like to take a look.)

Success notification

As you can see from the code a couple snippets above, Exosuit lets me know once my instances has entered a running state. At this point I can run bin/exo ssh, bin/exo instances or bin/exo terminate to mess with my instance(s) as I please.

Demo video

Here’s a small sample of Exosuit in action:

Try it out yourself

If you’d like to try out Exosuit, just visit the Getting Started with Exosuit guide.

If you think this idea is cool and useful, please let me know by opening a GitHub issue for a feature you’d like to see, or tweeting at me, or simply starring the project on GitHub so I can gage interest.

I hope you enjoyed this explanation and I look forward to sharing the next steps I take with this project.

Exosuit demo video #1: launching and SSHing into an EC2 instance

Update: this video is now out of date. See demo video #2 for a more up-to-date version.

Recently I decided to begin work on a tool that makes it easier to deploy Rails apps to AWS. My wish is for something that has the ease of use of Heroku, but the fine-grained control of AWS.

My tool, which is free and open source, is called Exosuit. Below is a demo video of what Exosuit can do so far which, given the fact that Exosuit has only existed since the day before this writing, isn’t very much. Currently Exosuit can launch an EC2 instance for you and let you SSH into it.

But I think even this little bit is pretty cool – I don’t know of any other method that lets you go from zero to SSH’d into your EC2 instance in 15 seconds like Exosuit does.

If you’d like to take Exosuit for a spin on your own computer, you can! Just visit the Exosuit repo and go to the getting started guide. And if you do try it, please tweet me or send an email to jason@codewithjason.com with any thoughts or feedback you might have.

Beware of “service objects” in Rails

Note: I no longer endorse this post, which was a rough first attempt to convey my thoughts on service objects. For my newer take on service objects, see my other post, How I code without service objects.

The good and bad thing about Active Record models

In the early stages of a Rails project’s life, the pattern of putting all the model code into objects that inherit from Active Record works out pretty nicely. Each model object gets its own database table, some validations, some associations, and a few custom methods.

Later in the project’s lifecycle, this pattern of putting everything into Active Record objects gets less good. If there’s an Appointment model, for example, everything remotely related to an appointment gets put into the Appointment model, leading to models with tens of methods and hundreds if not thousands of lines of code.

Despite the fact that this style of coding—stuffing huge amounts of code into Active Record models—leads to a mess, many Rails projects are built this way. My guess is that the reason this happens is that developers see the organizational structures Rails provides (models, controllers, views, helpers, etc.) and don’t realize that they’re not limited to ONLY these structures. I myself for a long time didn’t realize that I wasn’t limited to only those structures.

Service objects as an alternative to the “Active Record grab bag” anti-pattern

A tactic I frequently hear recommended as an antidote to the “Active Record grab bag” anti-pattern is to use “service objects”. I put the term “service objects” in quotes because it seems to mean different things to different people.

For my purposes I’ll use the definition that I’ve been able to synthesize from several of the top posts I found when I googled for rails service objects.

The idea is this: instead of putting domain logic in Active Record objects, you put domain logic in service objects which live in a separate place in your Rails application, perhaps in app/services. Some example service class names I found in various service object posts I found online include:

  • TweetCreator
  • RegisterUser
  • CompleteOrder
  • NewRegistrationService

So, typically, a service object is responsible for carrying out some action. A TweetCreator creates a tweet, a RegisterUser object registers a user. This seems to be the most commonly held (or at least commonly written about) conception of a service object. It’s also apparently the idea behind the Interactor gem.

Why service objects are a bad idea

One of the great benefits of object-oriented programming is that we can bestow objects with a mix of behavior and data to give the objects powerful capabilities. Not only this, but we can map objects fairly neatly with concepts in the domain model in which we’re working, making the code more easily understandable by humans.

Service objects throw out the fundamental advantages of object-oriented programming.

Instead of having behavior and data neatly encapsulated in easy-to-understand objects with names like Tweet or User, we have conceptually dubious ideas like TweetCreator and RegisterUser. “Objects” like this aren’t abstractions of concepts in the domain model. They’re chunks of procedural code masquerading as object-oriented code.

A better alternative to service objects: domain objects

Let me take a couple service object examples I’ve found online and rework them into something better.

Tweet example

The first example I’ll use is TweetCreator from this TopTal article, the first result when I google for rails service objects.

class TweetCreator
  def initialize(message)
    @message = message
  end

  def send_tweet
    client = Twitter::REST::Client.new do |config|
      config.consumer_key        = ENV['TWITTER_CONSUMER_KEY']
      config.consumer_secret     = ENV['TWITTER_CONSUMER_SECRET']
      config.access_token        = ENV['TWITTER_ACCESS_TOKEN']
      config.access_token_secret = ENV['TWITTER_ACCESS_SECRET']
    end
    client.update(@message)
  end
end

I think it’s far better just to have a Tweet object.

class Tweet
  def initialize(message)
    @message = message
  end

  def deliver
    client = Twitter::REST::Client.new do |config|
      config.consumer_key        = ENV['TWITTER_CONSUMER_KEY']
      config.consumer_secret     = ENV['TWITTER_CONSUMER_SECRET']
      config.access_token        = ENV['TWITTER_ACCESS_TOKEN']
      config.access_token_secret = ENV['TWITTER_ACCESS_SECRET']
    end

    client.update(@message)
  end
end

Isn’t it more natural to say Tweet.new('hello').deliver than to say TweetCreator.new('hi').send_tweet? I think so. Rather than being this weird single-purpose procedure-carrier-outer, Tweet is just a simple representation of a real domain concept. This, to me, is what domain objects are all about.

The differences between the good and bad examples in this case are pretty small, so let me address the next example in the TopTal article which I think is worse.

Currency exchange example

module MoneyManager
  # exchange currency from one amount to another
  class CurrencyExchanger < ApplicationService
    ...
    def call
      ActiveRecord::Base.transaction do
        # transfer the original currency to the exchange's account
        outgoing_tx = CurrencyTransferrer.call(
          from: the_user_account,
          to: the_exchange_account,
          amount: the_amount,
          currency: original_currency
        )

        # get the exchange rate
        rate = ExchangeRateGetter.call(
          from: original_currency,
          to: new_currency
        )

        # transfer the new currency back to the user's account
        incoming_tx = CurrencyTransferrer.call(
          from: the_exchange_account,
          to: the_user_account,
          amount: the_amount * rate,
          currency: new_currency
        )

        # record the exchange happening
        ExchangeRecorder.call(
          outgoing_tx: outgoing_tx,
          incoming_tx: incoming_tx
        )
      end
    end
  end

  # record the transfer of money from one account to another in money_accounts
  class CurrencyTransferrer < ApplicationService
    ...
  end

  # record an exchange event in the money_exchanges table
  class ExchangeRecorder < ApplicationService
    ...
  end

  # get the exchange rate from an API
  class ExchangeRateGetter < ApplicationService
    ...
  end
end

First, the abstractions of MoneyManager, CurrencyExchanger, etc. aren’t really abstractions. I’m automatically skeptical of any object whose name ends in -er.

I’m not going to try to rework this example line for line because there’s too much there, but let’s see if we can start toward something better.

class CurrencyValue
  def initialize(amount_cents, currency_type)
    @amount_cents = amount_cents
    @currency_type = currency_type
  end

  def converted_to(other_currency_type)
    exchange_rate = ExchangeRate.find(@currency_type, other_currency_type)
    CurrencyValue.new(@amount_cents * exchange_rate, other_currency_type)
  end
end

one_dollar = CurrencyValue.new(100, CurrencyType.find('USD'))
puts one_dollar.converted_to(CurrencyType.find('GBP')) # 0.80

Someone could probably legitimately find fault with the details of my currency conversion logic (an area with which I have no experience) but hopefully the conceptual superiority of my approach over the MoneyManager approach is clear. A currency value is clearly a real thing in the real world, and so is a currency type and so is an exchange rate. Things like MoneyManager, CurrencyExchanger and ExchangeRateGetter are clearly just contrived. These latter objects (which again are really just collections or procedural code) would probably fall under the category of what Martin Fowler calls an anemic domain model.

Suggestions for further reading

Enough With the Service Objects Already

I really enjoyed and agreed with Avdi Grimm’s Enough With the Service Objects Already. Avdi’s post helped me realize that most service objects are just wrappers for chunks of procedural code. What I wanted to add was that I think it’s usually possible to refactor that procedural code into meaningful objects. For example, in a piece of schedule code I recently wrote, I have a concept of an AvailabilityBlock and a mechanism for detecting conflicts between them. Instead of taking the maybe “obvious” route of creating a AvailabilityBlockConflictDetector, I created an object called an AvailabilityBlockPair which can be used like this: AvailabilityBlockPair.new(a, b).conflict?. To me this is much nicer. The concept of an AvailabilityBlockPair isn’t something that obviously exists in the domain, but it does exist in the domain if I consider it to be. It’s like drawing an arbitrary border around letters in a word search. Any word you can find on the page is really there if you can circle it.

Anemic Domain Model

Martin Fowler’s Anemic Domain Model post helped me articulate exactly what’s wrong with service objects, which seem to me to be a specific kind of Anemic Domain Model. My favorite passage from the article is: “The fundamental horror of this anti-pattern [Anemic Domain Model] is that it’s so contrary to the basic idea of object-oriented design; which is to combine data and process together. The anemic domain model is really just a procedural style design, exactly the kind of thing that object bigots like me (and Eric) have been fighting since our early days in Smalltalk. What’s worse, many people think that anemic objects are real objects, and thus completely miss the point of what object-oriented design is all about.”

Martin Fowler on Service Objects via the Ruby Rogues Parley mailing list

This GitHub Gist contains what’s supposedly an excerpt from an email Martin Fowler wrote. This email snippet helped clue me into the apparent fact that what most people call service objects are really just an implementation of the Command pattern. It seems to me that the Interactor gem is also an implementation of the Command pattern. I could see the Command pattern making sense in certain scenarios but I think a) when the Command pattern is used it should be called a Command and not a service object, and b) it’s not a good go-to pattern for all behavior in an application. I’ve seen engineering teams try to switch over big parts of their application to Interactors, thinking it’s a great default style of coding. I’m highly skeptical that this is the case. I want to write object-oriented code in Ruby, not procedural code in Interactor-script.

Don’t Create Objects That End With -ER

If an object ends in “-er” (e.g. Manager, Processor), chances are it’s not a valid abstraction. There’s probably a much more fitting domain object in there (or aggregate of domain objects) if you think hard enough.

The Devise gem code

I think the Devise gem does a pretty good job of finding non-obvious domain concepts and turning them into sensible domain objects. I’ve found it profitable to study the code in that gem.

Domain-Driven Design

I’m slowly trudging through this book at home as I write this post. I find the book a little boring and hard to get through but I’ve found the effort to be worth it.

Summary/takeaway

I find the last sentence of Martin Fowler’s Anemic Domain Model article to be a great summary of what I’m trying to convey: “In general, the more behavior you find in the services, the more likely you are to be robbing yourself of the benefits of a domain model. If all your logic is in services, you’ve robbed yourself blind.”

Don’t use service objects. Use domain objects instead.

How do I make testing a habitual part of my development work?

One of the most common questions asked by Rails developers new to testing is: how do I make testing a habitual part of my development work?

It’s one thing to know how to write tests. It’s another thing to actually write tests consistently as a normal part of your work.

In order to share with you how to make testing a habitual part of your development work, I conducted a poll among some of my peers in the Rails world to see what keeps them in the habit of writing tests consistently. I also examined my own motivations.

When I drew the commonalities among the answers, what I came up with was a trifecta not unlike Larry Wall’s three virtues of a great programmer. The trifecta is laziness, fear, and pride. Let’s examine each “virtue” individually.

Laziness

It might sound funny to name laziness as the first motivation for writing tests habitually. After all, tests seem like extra work. Writing tests consistently seems like something that would require discipline. But for me and many of the people who responded to my poll, it’s quite the opposite.

The alternative to automated tests

The alternative to writing tests isn’t just doing nothing. The alternative to writing tests is to perform manual testing, to let your users test your application for you in production, or most likely, a combination of the two. The alternative to writing tests is to suffer great pain and toil.

The laziness factor also extends beyond QA. I personally find that the process of writing features is often easier and more pleasant when I’m writing with the assistance of tests than when I’m not.

Mental energy

Mental energy is a finite, precious resource that (for me at least) starts full in the morning and depletes throughout the day. When I’m working I don’t ever want to use more than the minimum amount of mental exertion necessary to complete a task.

If I write a feature without using tests, I’m often juggling the “deciding what to do” work and the “actually doing it” work at the same time, which has a cognitive cost more than twice as much as performing those two jobs separately in serial. When I build a feature with the aid of tests, the tests allow me to separate the “deciding what to do” work from the “actually doing it” work.

It works like this. First I capture what to do in the form of a test. Then I follow my own instructions by getting the test to pass. Then I repeat. This is a much lighter cognitive burden than if I were to juggle these different mental jobs and allows me to be productive for longer because I don’t run out of mental energy as early in the day.

Code understandability

It’s more difficult, time-consuming and unpleasant to work with messy code than to work with clear and tidy code.

Being a lazy person, difficult, time-consuming and unpleasant work is exactly what I don’t want to do. I want to do work that’s pleasant, quick and easy.

Unfortunately it’s not possible to have clean, understandable code without having automated tests. This might sound like a hyperbolic claim but it’s not. I can prove it based on a chain of truths.

The first truth is that it’s impossible to write a piece of code cleanly on the first try. Some amount of refactoring, typically a lot of refactoring, is necessary in order to get the code into a reasonably good state. This is true on a feature-by-feature basis but it’s especially true on the scale of a whole project codebase.

The second truth is that it’s impossible to do non-trivial refactorings without having automated tests. The feedback cycle is just too long when all the testing is done manually. Either that or the risk of refactoring without testing afterward is just too large to be justified.

So, if it’s impossible to have good code without refactoring, and it’s impossible to do refactoring without tests, then it’s impossible to have good code without tests.

My extreme personal laziness demands that I only write neat and understandable code. Therefore, I have to write tests in order to satisfy my laziness.

Fear

Fear is another powerful impetus for testing. If I don’t write tests for my features, it increases the risk that I release a bug to production. Bugs cause me shame and embarrassment. I don’t want to feel embarrassment or shame.

Bugs may also have negative business consequences to the company I work for. This could negatively affect the company’s ability or willingness to pay me as much as I want.

When laziness doesn’t drive me to write tests, fear often does.

Pride

Lastly there’s pride. (I find Larry Wall’s “hubris” a little too strong a word.)

Sometimes, when I’m tempted not to write a test for a feature, I imagine another developer stumbling across my work in the future and seeing that there are no tests. I imagine myself sheepishly admitting to that developer that I didn’t bother to write tests for that feature. Why didn’t I write tests? No good reason.

As the arrogant person that I am, this imaginary interaction brings me pain. I really don’t like the idea that somebody else would look at my work and make a (legitimate) negative judgment.

I also want my work to be exemplary. If we hire a junior developer where I work, I want to be able to point to my code and say “This is how we do it.” I don’t know how I would explain that my test coverage is poor but I want theirs to be good.

Takeaways

I’m not driven to write tests out of discipline. I also don’t consider testing to be “extra” effort but rather an effort-saver.

The main forces that drive me to write tests are laziness, fear and pride. Mostly laziness.

How to write a test when the test implementation isn’t obvious

Why testers often suffer from “writer’s block”

When you write a test, you’re carrying out two jobs:

  1. Deciding what steps the test should carry out
  2. Translating those steps into code

For trivial tests, these two steps are easy enough that both can be done together, and without conscious thought. For non-trivial tests (especially tests that include complex setup steps), doing both these steps at once is too much mental juggling for a feeble human brain, or at least my feeble human brain.

This is when testers experience “writer’s block”.

Luckily, there’s a way out. When the implementation of a test eludes me, I consciously separate the job into the two steps listed above and I tackle each one at a time.

How to overcome writer’s block

Let’s say, for example, that I want to write an integration test that tests a checkout process. Maybe I can’t think of all the code and setup steps that are necessary to carry out this test. I probably can, however, think of the steps I would take just as a regular human being manually testing the feature in a web browser. So, in my test file, I might write something like this:

scenario 'create order' do
  # create a product
  # visit the page for that product
  # add that product to my cart
  # visit my cart
  # put in my credit card details, etc.
  # click the purchase button
  # expect the page to have a success message
end

Now I have all the high-level steps listed out. I can tell my brain that it’s relieved of the duty of trying to hold all the test steps in memory. Now my brain’s capacity is freed up to do other jobs. The next job I’ll give it is translating the steps into code.

I’ll start with the first line.

scenario 'create order' do
  electric_dog_polisher = create(:product)
  # visit the page for that product
  # add that product to my cart
  # visit my cart
  # put in my credit card details, etc.
  # click the purchase button
  # expect the page to have a success message
end

That was easy enough. Now I’ll move onto the next line.

scenario 'create order' do
  electric_dog_polisher = create(:product)
  visit product_path(electric_dog_polisher)
  # add that product to my cart
  # visit my cart
  # put in my credit card details, etc.
  # click the purchase button
  # expect the page to have a success message
end

And I’ll go down the comments, line by line, translating each line into Ruby code.

Not every step will be trivially easy. But at the same time, it’s unlikely that any individual line is all that hard. What’s hard is trying to solve 26 different problems at once in your head. The way to make progress is to break those problems down into tiny parts, get those parts out of your head and onto the screen, and then tackle the tiny parts one at a time.

How to deal with complex Factory Bot associations in RSpec tests

What’s the best way to handle tests that involve complex setup data? You want to test object A but it needs database records for objects B, C and D first.

I’ll describe how I address this issue but first let me point out why complex setup data is a problem.

Why complex setup data is a problem

Complex setup data can be a problem for two reasons. First, a large amount of setup data can make for an Obscure Test, meaning the noise of the setup data drowns out the meaning of the test.

Second, complex setup data can be a problem if it results in duplication. Then, if you ever need to change the setup steps, you’ll have to make the same change in multiple places.

How to cut down on duplication and noise

Unfortunately, I find that it’s often not very possible to take a complex data setup and somehow make it simple. Often, the reality is that the world is complicated and so the code must be complicated too.

What we can do, though, is push the complexity down to an appropriate level of abstraction. The way I tend to do this in Factory Bot is to use traits and before/after hooks.

Below is an example of some “complex” test setup. It’s not actually all that complex, but unfortunately examples often have to be overly simplified. The concept still applies though.

describe 'account with appointment soon' do
  it 'behaves a certain way' do
    account_with_appointment_soon = create(:account)

    create(
      :appointment,
      customer: account.customer,
      starts_at: Time.zone.now + 1.day
    )

    # assertions go here
  end
end

What if instead of this relatively noisy setup, the setup could be as simple as the following?

describe 'account with appointment soon' do
  it 'behaves a certain way' do
    account_with_appointment_soon = create(:account, :with_appointment_soon)

    # assertions go here
  end
end

This can be achieved by adding a trait called with_appointment_soon to the :account factory definition. Here it is:

FactoryBot.define do
  factory :account do
    customer

    trait :with_appointment_soon do
      after(:create) do |account|
        create(
          :appointment,
          customer: account.customer,
          starts_at: Time.zone.now + 1.day
        )
      end
    end
  end
end

That’s all that’s needed. We haven’t made the complexity go away, but we have moved the complexity out of our test so that our test is more easily understandable.

How do you tell which areas of a project’s test suite need attention?

A client of mine recently asked: “How do you determine, when first opening a project, which areas of testing need attention?”

There are three methods I use for determining what areas of a test suite need attention: 1) looking at the test suite code for “test smells”, 2) asking the team “where it hurts”, and 3) actually working directly with the project.

Looking at the code for “test smells”

Just as there are code smells, there are test smells. The book xUnit Test Patterns: Refactoring Test Code (which I highly recommend) has a list of test smells in the back. Some of these smells are detectible by looking at the code. Some of them can only be found experientially. Here are some that can be discovered by looking at the test code without running it.

Developers Not Writing Tests

This one is obvious. If there are very few tests in the test suite (or if the test suite doesn’t exist at all), then this is a clear opportunity for improvement.

Obscure Test

An Obscure Test is one where it’s hard to understand the meaning of a test just by looking at it, usually because the meaning is obscured by the presence of so many low-level details. These are relatively easy to spot.

Test Code Duplication

Duplication is also pretty easy to spot. It’s also often relatively cheap and straightforward to fix.

Asking the team “where it hurts”

Often a team is well aware of where their testing deficiencies lie. Common problems include:

  • Not enough tests
  • Not enough testing skills on the team
  • Slow tests
  • Flaky tests
  • Application code that’s too hard to test

These issues can be easily uncovered simply by asking.

Actually working directly with the project

This is often the most effective way of telling which areas of the test suite need the most attention, although it’s also the slowest.

One challenge in identifying areas of a test suite that need attention is that not all tests have equal value. The test that tests the contact page is not as valuable as the test that tests the checkout page. So effort spent improving the contact page test might result in a much better test but the exercise still would have been a waste of time.

Another challenge is that not all code is equally testable. Let’s say I have an application with zero tests and no testing infrastructure. Logic might seem to dictate that I should write a test for the checkout page before I write a test for the contact page because the checkout page is much more economically valuable than the contact page.

But if my application lacks testing infrastructure than the task of writing a test for the checkout page might be a prohibitively large undertaking because it would involve not only writing complex tests but also setting up the underlying test infrastructure. Maybe I’d be better off writing a test for the contact page first just to get a beachhead in place, then gradually work my way up to having tests for the checkout page.

These are things that might be hard to tell just by looking at the code. So when I start work on a new project, I’ll often use all three of these methods—looking at the code, talking with the team, working with the code—to determine which areas of the test suite need attention.

How to get a team to get behind an engineering project

A client of mine recently asked: “What’s the best way to get a team behind a future refactor/architecture goal that hasn’t happened yet?”

This is a great question. Before I answer how to do this successfully, let me point out three certain ways to fail in this endeavor. Then I’ll share the key to giving an idea its best shot at success.

Three ways to fail to get a team behind an engineering project

The effort fails because the project was a genuinely bad idea

I mention this just to get the obvious out of the way: bad ideas often fail and they of course ought to fail. Even though this scenario isn’t necessarily a happy one, there’s nothing unjust about it.

The effort fails because it was “pearls before swine”

Sometimes you have a good idea but, for whatever reason, the team you’re in or the leadership you’re under is made up of the kind of people who are behind the times or (to be blunt) just plain not very smart.

In 2013 I worked somewhere where I tried to help implement continuous integration, but my boss was dead-set against it. There’s nothing I could have done to have been successful in this case because my boss just wasn’t the kind of person who “gets it”. The only “solution” to this problem was to go work somewhere else.

This scenario is regrettable but it’s futile to try to fight against it.

The effort fails because, although the idea was a good one, the methodology of persuasion was bad

Of the three ways to fail at getting a team to get behind an engineering effort, this one is the most tragic. I had a good idea, I had a receptive audience, but I bungled the pitch and so my idea got killed.

The key to making any project idea maximally enticing

Think about how interested you tend to be in implementing other people’s ideas. Then think about how interested you are in implementing your OWN ideas. If you’re like most people, you’re of course much more interested in your own ideas than other people’s.

So the key to making an idea enticing is: get your teammates (or bosses or subordinates) to believe that the idea is theirs.

This is easier said than done. I’ll describe two ways of accomplishing this.

How to get your teammates to believe that your ideas are their ideas

I know of two ways to get someone to think that an idea of mine is actually an idea of theirs: 1) use some sort of psychological manipulation to trick or 2) find an idea of theirs that actually, legitimately matches an idea of mine.

The latter method is of course going to be the more successful.

Here’s an example. Let’s say I want to try to get the team to increase test coverage on our application. One way I could go about this is by saying, “Hey guys! We need to increase our test coverage! We need to start this project ASAP!” This method isn’t likely to be very successful. Any attempt to “sell” an idea tends to be met with an equal resistance.

A better way to approach it would be to start by saying, “Hey guys, how do you feel about the quality of our codebase in general?” Then I would listen to my teammates’ responses and make sure they felt heard. (It’s easier to influence someone else if you first let them influence you.) Then I would ask, “How do you feel about our test coverage specifically?” If the whole team unanimously agreed that our test coverage was fine, I’d probably stop right there. It’s impossible to get someone to spend time fixing a problem they don’t believe exists.

But if my teammates expressed some level of dissatisfaction with our current test coverage, I would ask them to elaborate. I would take notes on what they said. I would ask if these problems strike them as worth fixing. Then I’d ask them if they’d be open to coming up with some goals together around test coverage and a plan to achieve those goals. People are much more supportive of goals and plans they were involved in creating than goals and plans someone else came up with.

Key takaways

Uncover existing desires. If you have a desire to accomplish X, ask probing questions of your teammates (or bosses or subordinates) to see if they also desire X. Position the project not as an endeavor to realize your desires, but to realize their desires.

Involve the team in the roadmapping. The team will be motivated to help accomplish the goal in proportion to how involved they were in developing the goal.