Charlie Tanksley - Big Nerd Ranch Tue, 19 Oct 2021 17:46:38 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Using RSpec Shared Contexts to Ensure API Consistency https://bignerdranch.com/blog/using-rspec-shared-contexts-to-ensure-api-consistency/ https://bignerdranch.com/blog/using-rspec-shared-contexts-to-ensure-api-consistency/#respond Thu, 20 Mar 2014 15:10:17 +0000 https://nerdranchighq.wpengine.com/blog/using-rspec-shared-contexts-to-ensure-api-consistency/ Consistent API design is important. But it is incredibly easy to lose sight of this when you are actually writing the API. Here is the method I used to ensure consistency.

The post Using RSpec Shared Contexts to Ensure API Consistency appeared first on Big Nerd Ranch.

]]>

I recently had the opportunity to work on a fairly large
service-oriented application. One of my key responsibilities on the
team was to ensure that the main database application was well-tested. This project taught me the importance of a consistent API. Here I’ll share one tool I learned for ensuring that consistency.

Our team settled on a testing plan that involved writing tests for each API
endpoint, with the thought being that if we knew exactly how each of those
worked, then it should be easier to work with them in client apps (and
harder to break other client apps when making changes to
the database app).

One of the main things I learned while working on this project is how
important a consistent API design is. All successful PUTs should
return the same status code; all unsuccessful POSTs should return, e.g., JSON with the same structure. But it is incredibly easy to lose sight of this
when you are actually writing the API. When writing the
CommentsController#create action, I almost always had to peek at the
PostsController#create action just to make sure I was keeping
things consistent. This was tedious and error prone. Here is the method I used to ensure consistency in the APIs.

Sample controller code

Here are a pair of controllers we can use to drive discussion:

  # app/controllers/comments_controller.rb
  class CommentsController < ApplicationController
    def show
      render json: Hash[comment: Hash[id: params[:id]]]
    end
    def create
      render json: Hash[errors: {error: 'reason'}], status: 422
    end
  end
  # app/controllers/posts_controller.rb
  class PostsController < ApplicationController
    def show
      render json: Hash[id: params[:id]]
    end
    def create
      render json: Hash[error: 'reason']
    end
  end

A naive approach to testing

Here’s how I tested these four actions at the start of the
project:

  # spec/requests/comments_controller_spec.rb
  describe CommentsController do
    describe '#show' do
      let(:id) { '1' }
      let!(:json) {
        get comment_path(id)
        JSON.parse(response.body)['comment']
      }
      it 'returns the specified item' do
        expect(json['id']).to eq(id)
      end
      it 'responds with a 200 status' do
        expect(response.status).to eq(200)
      end
    end
    describe '#create' do
      let(:message) { 'reason' }
      let!(:json) {
        post comments_path
        JSON.parse(response.body)['errors']
      }
      it 'returns the error message' do
        expect(json['error']).to eq(message)
      end
      it 'responds with a 422 status' do
        expect(response.status).to eq(422)
      end
    end
  end
  # spec/requests/posts_controller_spec.rb
  describe PostsController do
    describe '#show' do
      let(:id) { '1' }
      let!(:json) {
        get post_path(id)
        JSON.parse(response.body)
      }
      it 'returns the specified item' do
        expect(json['id']).to eq(id)
      end
      it 'responds with a 200 status' do
        expect(response.status).to eq(200)
      end
    end
    describe '#create' do
      let(:message) { 'reason' }
      let!(:json) {
        post posts_path
        JSON.parse(response.body)
      }
      it 'returns the error message' do
        expect(json['error']).to eq(message)
      end
      it 'responds with a 200 status' do
        expect(response.status).to eq(200)
      end
    end
  end

These tests seem okay. They aren’t obviously wrong, at least. But there
is an error here that, I thought, was subtle. It’s easier to see when
we look at the results of bin/rspec -f documentation

  $ bin/rspec -f documentation
  CommentsController
    #show
      returns the specified item
      responds with a 200 status
    #create
      returns the error message
      responds with a 422 status
  PostsController
    #show
      returns the specified item
      responds with a 200 status
    #create
      returns the error message
      responds with a 200 status

My test descriptions are the same for all but one case (the status
code for the #create action). These test descriptions, at least
when I’m writing them in the moment, feel perfectly accurate and
complete to me. Yet they paper over huge inconsistencies in my
API.

Shared contexts to the rescue

Shared contexts are groups of
examples that you can call from within multiple describe blocks.
They should live in an appropriately named file in spec/support.
Note that they should not be named something_or_other_spec.rb;
rather, that should be something_or_other.rb. If nothing else,
shared contexts are going to highlight the inconsistencies in my API. Here are ‘shared contexts’ that capture what is going on in my specs.

  # spec/support/shared_api_contexts.rb
  shared_context 'a failed create' do
    it 'returns an unprocessable entity (422) status code' do
      expect(response.status).to eq(422)
    end
  end
  shared_context 'a response with nested errors' do
    it 'returns the error messages' do
      json = JSON.parse(response.body)['errors']
      expect(json['error']).to eq(message)
    end
  end
  shared_context 'a response with errors' do
    it 'returns the error messages' do
      json = JSON.parse(response.body)
      expect(json['error']).to eq(message)
    end
  end
  shared_context 'a show request with a root' do |root|
    it 'returns the specified item' do
      json = JSON.parse(response.body)[root]
      expect(json['id']).to eq(id)
    end
  end
  shared_context 'a show request' do
    it 'returns the specified item' do
      json = JSON.parse(response.body)
      expect(json['id']).to eq(id)
    end
  end
  shared_context 'a successful request' do
    it 'returns an OK (200) status code' do
      expect(response.status).to eq(200)
    end
  end

And here are specs that put these shared contexts to use:

  # spec/requests/comments_controller_spec.rb
  describe CommentsController do
    describe '#show' do
      before do
        get comment_path(id)
      end
      let(:id) { '1' }
      it_behaves_like 'a show request with a root', 'comment'
      it_behaves_like 'a successful request'
    end
    describe '#create' do
      before do
        post comments_path
      end
      let(:message) { 'reason' }
      it_behaves_like 'a response with nested errors'
      it_behaves_like 'a failed create'
    end
  end
  # spec/requests/posts_controller_spec.rb
  describe PostsController do
    describe '#show' do
      before do
        get post_path(id)
      end
      let(:id) { '1' }
      it_behaves_like 'a show request'
      it_behaves_like 'a successful request'
    end
    describe '#create' do
      before do
        post posts_path
      end
      let(:message) { 'reason' }
      it_behaves_like 'a response with errors'
      it_behaves_like 'a successful request'
    end
  end

The difference between the behavior is now pretty obvious. And it
becomes even more obvious when we check out the RSpec test output:

  $ bin/rspec -f documentation
  CommentsController
    #show
      behaves like a show request with a root
        returns the specified item
      behaves like a successful request
        returns an OK (200) status code
    #create
      behaves like a response with nested errors
        returns the error messages
      behaves like a failed create
        returns an unprocessable entity (422) status code
  PostsController
    #show
      behaves like a show request
        returns the specified item
      behaves like a successful request
        returns an OK (200) status code
    #create
      behaves like a response with errors
        returns the error messages
      behaves like a successful request
        returns an OK (200) status code

It’s clear here, for example, that PostsController#show and
CommentsController#show return items with a different shape, as it
were: the latter has a root element where the former does not. This
is very good information, even if we weren’t able to fix it now. But
since this is easy code and I have the time, let’s take a look at what
this API (and these tests) would look like if I could make all the
changes I wanted.

Bringing the API in line with cleaner shared contexts

If I want to enforce consistency, it seems to me that the simplest
thing to do is have a context for each branch of each controller
action. Then I can know that all my #index actions are parallel,
for example. I could write smaller, more reusable components, but my
hunch is that mixing and matching is going to be more trouble than it
is worth. So here are the tests I want:

  # spec/support/shared_api_contexts.rb
  shared_context 'a failed create' do
    it 'returns an unprocessable entity (422) status code' do
      expect(response.status).to eq(422)
    end
    it 'returns the error messages' do
      json = JSON.parse(response.body)['errors']
      expect(json['error']).to eq(message)
    end
  end
  shared_context 'a successful show request' do |root|
    it 'returns an OK (200) status code' do
      expect(response.status).to eq(200)
    end
    it 'returns the specified item' do
      json = JSON.parse(response.body)[root]
      expect(json['id']).to eq(id)
    end
  end
  # spec/requests/comments_controller_spec.rb
  describe CommentsController do
    describe '#show' do
      before do
        get comment_path(id)
      end
      let(:id) { '1' }
      it_behaves_like 'a successful show request', 'comment'
    end
    describe '#create' do
      before do
        post comments_path
      end
      let(:message) { 'reason' }
      it_behaves_like 'a failed create'
    end
  end
  # spec/requests/posts_controller_spec.rb
  describe PostsController do
    describe '#show' do
      before do
        get post_path(id)
      end
      let(:id) { '1' }
      it_behaves_like 'a successful show request', 'post'
    end
    describe '#create' do
      before do
        post posts_path
      end
      let(:message) { 'reason' }
      it_behaves_like 'a failed create'
    end
  end

Of course, these tests fail at the moment. Here is the updated code:

  # app/controllers/comments_controller.rb
  class CommentsController < ApplicationController
    def show
      render json: Hash[comment: Hash[id: params[:id]]]
    end
    def create
      render json: Hash[errors: {error: 'reason'}], status: 422
    end
  end
  # app/controllers/posts_controller.rb
  class PostsController < ApplicationController
    def show
      render json: Hash[post: Hash[id: params[:id]]]
    end
    def create
      render json: Hash[errors: Hash[error: 'reason']], status: 422
    end
  end

Now when I run my tests I see that all the endpoints are consistent:

  $ bin/rspec -f documentation
  CommentsController
    #create
      behaves like a failed create
        returns the error messages
        returns an unprocessable entity (422) status code
    #show
      behaves like a successful show request
        returns the specified item
        returns an OK (200) status code
  PostsController
    #create
      behaves like a failed create
        returns the error messages
        returns an unprocessable entity (422) status code
    #show
      behaves like a successful show request
        returns the specified item
        returns an OK (200) status code

Drawbacks of shared contexts

It would be unfair to sing the praises of shared contexts without mentioning some of the weaknesses we found. Shared contexts are easy to misuse (using them in cases where the context isn’t really similar is a mistake I made at one point).

But for our team, the most frustrating drawback was the way that shared contexts mess up your spec line numbering. If you have shared contexts in a spec file, you can’t run your specs by line number. bin/rspec spec/controllers/posts_controller:10 will not run the spec on line 10. I’m not entirely sure what is going on, but thanks to the shared contexts you are using, your spec line numbers aren’t what you think. So who knows what spec is actually on line 10. You can work around this by using :focus tags, but that is a new workflow for some of us, and does take getting used to.

The second major drawback is related to the first: when an example in a shared context fails, at least as of RSpec 2, the stack trace points you to the shared context file, not the line/file where the context is included. This means that finding a failing test requires more than finding the line in a file: you have to actually read the test description and find the spec that way. Again, not a huge problem, but certainly a workflow interruption that is worth considering.

Don’t lose sight of consistency

There are many virtues of an API. Consistency is clearly one, and it
is one that it is remarkably easy to lose sight of.

If you are already in the weeds on an inconsistent API, shared
examples can at least highlight your inconsistencies. Maybe those inconsistencies
aren’t so bad (maybe you always follow one of two patterns, say). Or
maybe they are much worse than you thought. Either way, it’s better to know
now and start working toward consistency.

If you are just starting an API project of any size, you can make
consistency in your endpoints easier by deciding up front how
different resourceful actions should behave, writing shared examples
for those cases, and then sticking with them. Every now and then,
abandon your dull test runner for the more verbose -f documentation
runner and make sure things are still looking right.

The post Using RSpec Shared Contexts to Ensure API Consistency appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/using-rspec-shared-contexts-to-ensure-api-consistency/feed/ 0
Experiments as play https://bignerdranch.com/blog/experiments-as-play/ https://bignerdranch.com/blog/experiments-as-play/#respond Mon, 11 Feb 2013 19:49:21 +0000 https://nerdranchighq.wpengine.com/blog/experiments-as-play/

The humanities scholar in me rebels when I hear the word “experiment.” To me, an experiment is a big, formal thing with a hypothesis and a control group and reproducibilty. Not really my thing. Yet I love when we conduct experiments at Big Nerd Ranch.

The post Experiments as play appeared first on Big Nerd Ranch.

]]>

The humanities scholar in me rebels when I hear the word “experiment.” To me, an experiment is a big, formal thing with a hypothesis and a control group and reproducibilty. Not really my thing. Yet I love when we conduct experiments at Big Nerd Ranch.

What gives? Well, the things we often call “experiments” as programmers aren’t really experiments at all.

Trials

Back in October 2012, I set out on an experiment that was really a trial. Having been a Vim user for years, I was curious about Emacs, so I decided to do an experiment where I would switch to Emacs full-time. I set an initial three-month time frame for making the switch. To me, this wasn’t really an experiment because there was no hypothesis, there were no conditions for success and it certainly wasn’t repeatable—the next developer who tries it might have a completely different experience with it. And at the end of three months, I was a full-time Emacs user. I still haven’t gone back to Vim and I’m not sure that I will. Changing editors was hard but interesting, and I may do it again. In reality, this “experiment” was a trial of Emacs for me. I wanted to learn it and take it for a spin to see how it felt. So I experimented with it.

Flexible changes

Some experiments are just changes that we are unsure about. At Highgroove Studios, we used initials for everyone in our chat room. With 20-30 employees, it was workable: you learned reasonably quickly who JW and JMW were. But once we merged with Big Nerd Ranch and nearly tripled our size, using initials stopped making sense. So someone proposed that we use full names everywhere, not initials. This wasn’t really an experiment; we were just trying out a new way of doing things. Why call it an experiment, then? Because we knew that it might not work, and we wanted to take a relaxed approach to the change. If you declare that “from now on, everyone uses full names” and update the intranet with the directive and then it doesn’t work out, then you have to go back and un-declare it, then update the intranet again. But if you couch it as an experiment and you see it is no good, you can just go back to business as usual.

Information-gathering exercises

The Ruby team at Big Nerd Ranch is broken into smaller teams of four to eight people. Every week, those smaller teams conduct code reviews of their work. The ways teams opt to do this varies, but it is fairly traditional for each member to review another member at the end of an iteration on Tuesday. My team thought there _might _be a better way for us to do code reviews, so we are trying another approach now.

One of our members champions “continual review”: reviewing, and being reviewed, every single day, rather than batch reviewing them at the end of the iteration. The advantage of continual review is that you have fewer commits to review at a time, and the code-writer gets faster feedback to work with. The problem with continual review is that it adds another thing you have to do every day. To try and approximate the continual review advantages while minimizing its disadvantages, one team member suggested doing code reviews twice a week, on Friday and Tuesday. This gets us a tighter feedback loop but doesn’t make us review every single day. After much discussion, we decided to experiment with this as a team.

This is much more of a traditional experiment: we do know what the other way looks like, and at the end of two or three iterations we will discuss how we liked this, its advantages and disadvantages, and how to go forward. I even think that most of us have a hypothesis (that two times a week will be better than one). But we aren’t committed to that outcome, and really want to see how things turn out.

Experiments and play

These three kinds of experiments are all reasonably narrowly focused process and/or tooling changes designed to make us a better, more productive company and individuals. Why are these “experiments” at all? Honestly, I think it is because part of us associates “experimenting” with “play,” and we want that bit of lightness in these changes. We don’t want our editor to be a drudge, we don’t want the boss to issue demands, we don’t want one team member to tell us what to do. So we play at it. We play with it. We give ourselves room to move, to change, to alter the plans. Experiments are really chances to play at work. They are places to take risks and chances without serious consequences. Experiments. They are a Very Good Thing. And a Very Fun Thing.

The post Experiments as play appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/experiments-as-play/feed/ 0
Wolfbrain Activate https://bignerdranch.com/blog/wolfbrain-activate/ https://bignerdranch.com/blog/wolfbrain-activate/#respond Wed, 31 Oct 2012 11:00:00 +0000 https://nerdranchighq.wpengine.com/blog/wolfbrain-activate/

I love our Wolfbrain logo, but I must admit that when I started at Highgroove it was a bit of an enigma to me. It’s a wolf? With a green brain that I can see? And we are hyper-specialists in Rails? I think I see…. Nope. I don’t.

The post Wolfbrain Activate appeared first on Big Nerd Ranch.

]]>

I love our Wolfbrain logo, but I must admit that when I started at Highgroove it was a bit of an enigma to me. It’s a wolf? With a green brain that I can see? And we are hyper-specialists in Rails? I think I see…. Nope. I don’t.

During my first week, our Director of Business Development explained it to me this way: wolves can function on their own, but they excel within the pack. Individually, Highgroovers are excellent developers, but we’re stronger together. Hence the wolf. To be honest, I don’t recall his explanation of the brain part. But I remember not buying it.

During this iteration, it struck me what the brain in the wolfbrain logo means. Think about what your brain does. Among other things, it stores and retreives information: you have an experience of walking to the store, your brain remembers it, and you now know how to get to the store. And where is your brain, that thing you use to store that information? In your head, of course. So the wolf’s brain is firmly situated in his head.

A brief diversion that will ultimately help in our explanation: there’s a theory in the philosophy of mind called the extended mind. Very roughly, the idea is that your mind can, and does, extend beyond your skull: in essence, at least some of the things that you use to store and retreive information are part of your mind. I don’t know if this is right or not, but it doesn’t seem obviously false to me. So let’s run with it.

Now, armed with a tiny bit of philosophy of mind, we can get to the good part: the explanation for why you can see that wolf’s brain!

Before I started working at Highgroove, I was one guy. I could read books and search for things online, but my knowledge was fairly limited. It was constrained by my ability to take in new information. But once I started working at Highgroove, the amount of information I could access increased dramatically. We can, and do, still read books and blog posts, watch screencasts, go to conferences, and search the web. But when Nerds run into a problem, the typical approach is to work on it for 30 minutes and then, if you are stuck, ask for help in our chat room. So all day long you see messages pop up like this:

  • Does anyone know of a gem to do X?
  • Can someone help me figure out why this test is failing?
  • Can someone pair with me for a minute on this X?
  • Does anyone have an example of X I can look at?

And every time, someone promptly replies. Gems are suggested, pairing sessions are volunteered and approaches are proffered. And these aren’t just random jokers on the internet offering advice. These are really smart people we trust, people whose experiences working on projects for companies like The New York Times, Audiogon, Five Point Partners and Mailchimp give them a wealth of knowledge that would take any one of us years to obtain. Instead of only having access to my past experiences (and Google!), I have easy access to the experiences of 20+ other developers. Our extended mind is huge. And very experienced. And crazy-smart. Augmenting my normal, internal mind with it is a very good thing for me, and it is a very good thing for our clients.

So there you have it: in my opinion, you can see the brain because it isn’t private, because its knowledge and experience is shared freely among the team. It is visible because its power can’t be hidden and its knowledge cannot be contained. It’s visible because we are proud of it. Wolfbrain activate!

The post Wolfbrain Activate appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/wolfbrain-activate/feed/ 0
ROWE: All About Respect https://bignerdranch.com/blog/rowe-all-about-respect/ https://bignerdranch.com/blog/rowe-all-about-respect/#respond Wed, 08 Aug 2012 12:00:00 +0000 https://nerdranchighq.wpengine.com/blog/rowe-all-about-respect/

Respect Poster (2005)

The post ROWE: All About Respect appeared first on Big Nerd Ranch.

]]>

Respect Poster (2005)

You already know that Highgroove is a ROWE (Results Only Work Environment). I am just a week into my tenure at Highgroove, but I’m inclined to think that the ‘R’ could just as easily stand for “Respect,” at least at Highgroove. That is, I think Highgroove could fairly be described as a Respect-Only Work Environment.

Respect is really about two things. You respect someone by recognizing their expertise and goals. You respect the person driving the car by not slamming on the ‘air brakes’ all the time. You respect your significant other by supporting their interests. Highgroove goes out of their way to do these things. That matters.

My last job was in academia. Said job afforded me little respect. My department trusted my expertise, but the university at large wanted all sorts of checks to make sure I was doing my job, and they couldn’t have cared less about my personal goals and interests. Highgroove is different. Very different. And it is beautiful.

Highgroove goes out of its way to treat employees like experts. On day one, you are tasked with setting up your computer. How you do that is up to you. We have a document that tells you what others use, but what you use is up to you. If that software costs money, the company will buy it. You don’t have to use one blessed set of tools; you use what will let you be great. These things seems kind of small, and I guess they are, but small things are powerful, and they signify much greater things. To me, they signify trust and respect.

Being respected is interesting. It makes you want to respect others. You don’t have to fight for what is yours. You can be generous. As a result of feeling respected by my employer, I feel respect for my clients. I want to treat them with the same kindness and trust I have been afforded. I want to recognize their expertise and their goals. I want them to feel as good as I do.

This respect has another result, one that kind of surprised me: over the weekend I was struck with an overwhelming feeling that I wanted to further the goals of the company. That is, I want to respect Highgroove by making sure that the company achieves its goals. I kind of don’t even care what those goals are. If they are important to the company, they are important to me.

That’s kind of how it should be, right? No matter what my wife’s goals are, I want her to accomplish them. I trust that she’ll have good goals because I respect and trust her. I trust that Highgroove has good goals because I respect and trust the employees in charge of setting those goals.

So there you have it. Respect is a powerful thing. At Highgroove we get it and give it. The result: happy developers, happy clients, and awesome results.

Image credit: bonifaceplymouth

The post ROWE: All About Respect appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/rowe-all-about-respect/feed/ 0