Effective Testing with RSpec 3, From Writing Specs to Running Them

Notes from Effective Testing with RSpec 3, chapter 2.

Chapter two walked us through creating a spec to guide the development of our class. This time we explored the useful ways RSpec runs tests. We learned about structuring specs with tags and context blocks. Then filtering what specs are run.

Tags

We can assign meta data to an example block using hash value syntax. Rspec can then filter these tests to only run with or without them.

In ‘Identifying Slow Examples’ we used profile to identify the last two examples as our slowest tests. We can mark these tests as ‘slow’ using the code below:

RSpec.describe 'The sleep() method' do
  it ('can sleep for 0.1 second') { sleep 0.1 }
  it ('can sleep for 0.2 second') { sleep 0.2 }
  it ('can sleep for 0.3 second') { sleep 0.3 }
  it 'can sleep for 0.4 second', slow: true do; sleep 0.4; end
  it 'can sleep for 0.5 second', slow: true do; sleep 0.5; end
end

We can then run just the ‘slow’ tests, or exclude them using command line options or configurations:

# exclude slow tests from our run
rspec --tag ~slow

# only run the slow tests
rspec --tag slow

# Configure Rspec to auto exclude tests marked as slow
RSpec.configure do |config|
  config.filter_run_excluding slow: true
end

Effective Testing walks us through configuring RSpec with `filter_run_when_matching`, but there are many filters we can use to configure RSpec.

In addition to slow, we can use meta-tags to filter

  • Required: Features that have contractual obligation to be available. You may never use TDD to ensure a Partners link is available on your front page. You may want a set of tests for ease-of-mind on your business relationships.
  • Components & Stories: Tests that cover a general idea or the next milestone.
  • Smoke tests: quick check to ensure application is functional.

Prepare Tests with Pending

Another way of filtering specs is with pending. Pending is way of categorizing specs that are not yet ready. It allows us to sit down and completely think out the attributes of a class.

  RSpec.describe 'blog post' do
    it 'has a title'
    it 'has a subtitle'
    it 'has content'
  end

This is easy to knockout and easy to read. I don’t have to know how I am going to accomplish this behavior but get’s things out of my head. Making me less likely to forget the big picture.

Pending goes further into detail. We can mark a test with pending and provide a helpful message.

it​ ​'is light in color'​ ​do​
  pending ​'Color not implemented yet'​
  expect​(coffee.color).to be(​:light​)
end​

#Rspec output
1) A cup of coffee with milk is light in color
  # Color not implemented yet
​  Failure/Error: expect(coffee.color).to be(:light)”

#Rspec output once completed
1) A cup of coffee with milk is light in color FIXED
  Expected pending ’Color not implemented yet’ to fail. No error was raised.
​   # ./spec/coffee_spec.rb:42”

# Excerpt From: Myron Marston, Ian Dees. “Effective Testing with RSpec 3.” iBooks. 

If we forget to remove pending after completing a feature, RSpec will remind us to clean up our test. The authors point out that this is handy for bugs. An unexpected failing test can be marked as ‘pending’ with the issue tracker in the description.

Example Filter & Dry Run

In ‘Running Just What You Need’ we learn about the −−example or -e flag. This is exciting because combined with −−dry-run we can easily explore our specs as documentation. In the last chapter, the authors pointed out that let safeguards us from memoization gotchas. Curios about what let is expected to do I can quickly find out.

rspec --example let --dry-run
...
#let
  raises an error when referenced from `before(:all)`
  yields the example
  raises a useful error when called without a block
  caches a nil value
  caches the value
  raises an error when attempting to define a reserved method name
  generates an instance method
  raises an error when referenced from `after(:all)`
  does not pass the block up the ancestor chain
  when the declaration uses `return`
    can get past a conditional `return` statement
    can exit the let declaration early
  when overriding let in a nested context
    can use `super` to reference the parent context value
  when included modules have hooks that define memoized helpers
    allows memoized helpers to override methods in previously included modules
...

47 examples are returned. This is more than we wanted, but it’s much easier to scan 47 examples instead of thousands. It’s also easier to read as it is just the authors intentions. There are no stack traces, profiling, or meta-data.

Effective Testing with RSpec 3, Getting Started

Notes from Effective Testing with RSpec 3, chapter 1.

Effective Testing opens with explaining that Behavior & Test Driven Development provide us with design guidance, a safety net, and documentation. All of these are great reasons but my favorite is something I heard from the Ruby Rouges podcast,

“You are not paying me to write tests. You are paying to guarantee my code is doing what you are paying for.”

A part of Getting Started is installing Ruby & the RSpec libary, which you would expect is a single gem. RSpec has separated it’s behavior into 3 gems: Core, Expectations, & Mocks. The idea is that you can mix and match libraries with other test frameworks and mocking tools. I was curios if anyone had taken advantage of this and found ‘Zverok with Ruby’ who wrote about using RSpec libraries for easier to read conditionals, exploring a code base, method validations, and more.

By the end of Getting Started we will have coded out a spec to describe the Sandwich class. We will also have explored three different ways to condense our code and make our tests easier to read:

Hooks are a way to run setup code before, after, and around a test block. The idea is to create a standard environment where each example, context, description, etc. runs with the same values and state. One thing I didn’t see in this chapter was filtering hooks, which seems really useful.

Helper methods, the second way of condensing our code, is just plain ruby. The authors remind us that we’re not forced into RSpec and if it’s easier to hack something out we should just do that.

This section then ends on let. A useful way to initiate and reuse an instance variable: let(:sandwich) { Sandwich.new("delicous', []) }. This is the most common way to condense code. It protects us from misspelling the sandwich variable in our examples, it’s only run when called, and easy to refactor.

Getting Started concludes by suggesting we look at rspec --help to discover available options. Here are a few things I explored:

  • dry-run: We can dry-run tests to print out expectations. This is really useful for documentation. If I’m curios about a service or object I can run it’s specs dry and read them as the intended use without waiting for tests to run.
  • formats: Results can be printed in a variety of ways. Not just for communicating to me, the developer, but to another audience viewing them through a browser or JSON API.
  • filter: Tests can be filtered when run. This is essential for rapid testing. When I’m working on a section of code, I only want to run the relevant tests. I won’t run the entire suite until after I’ve completed a task.

Nand2Tetris – The ALU Chip

In Chapter 2 of Nand2Tetris we build the Arithmetic Logic Unit chip. It has 2 16-bit inputs and 1 16-bit output. While providing the inputs you also specify the arithmetic operation you want to perform; . This ALU chip is designed to become the centerpiece of HACK.

In addition to ALU’s there are FPU’s (Floating Point Unit) and CPU’s (Central Processing Unit). ALU’s are used to build these more complex chips, which may contain many ALU’s.

John von Neumann proposed ALU’s in 1945 but components were large and expensive for the type of ALU we are building now.
Initially, they performed operations on single bit data. Now that we have integrated circuit (IC) transistors we are able to build ALU’s so complex they can handle operations in a single clock cycle.

Someone in my group documented errors you may come across building the ALU. Hope this helps, https://github.com/SeaRbSg/nand2tetris2017/tree/master/jwfearn/tips.