2
votes

I'm new to Ruby and RSpec, did a little research and saw there's a few ways to do sort of data driven enumerating tests from posts online but they don't got into a whole lot of detail like a full tutorial. So before I go review those online articles in detail again, thought I'd ask here instead first.

Here's sort of the setup I have based on the standard simple way of using RSpec (defined describe & it blocks, not importing the parts of RSpec to do the expectations only). Then I try to add in data driven ability to it:

require 'rspec'
require 'csv'

describe "test suite name" do
  before :all do
    #this CSV mapping method found online...
    @device_client = CSV.read path
    @descriptor = @device_client.shift
    @descriptor = @descriptor.map { |key| key.to_sym }
    @device_client.map { |client| Hash[ @descriptor.zip(client) ] }
  end

  @device_client.each do |client|
    describe "#{client[:test_scenario]}" do
      if "match some CSV field value"
        it "should run this kind of test" do
          #additional code as needed
          expect(some_actual).to eql(some_expected)
        end
      end

      if "match some other CSV field value"
        it "should run that kind of test" do
          #additional code as needed
          expect(some_actual).to eql(some_expected)
        end
      end

      it "some other test common to all CSV rows" do
        #stuff
      end
    end
  end

end

What I notice here is that @device_client is nil as it is structured right now (debugged with a "p @device_client" statement to dump contents out). To get it to have value, I have to enclose the hash inside an it block where it's in scope (which I typically have inside another describe block, though assume I can skip the extra describe).

How might I restructure the test to "read" the same (to the reader of the test) and function the way I intend it to? It's fine if the restructure means I can't use the standard RSpec format and have to require RSpec components differently (the posts online didn't seem to follow the simple/basic RSpec format).

I think my code is fairly straighforward to interpret. In case not, the intent is to use the CSV input to dynamically build the test. Each CSV row is a scenario that has multiple tests - 1 test differs based on a CSV field value, hence the ifs, the remaining tests are common to all scenarios. And we repeat this set for as many CSV scenario rows there are in the file. And the before all block is the global setup before we process the CSV data.

In the restructure, ideally I'd like to keep the describe & it text description blocks (or some equivalent to them) so that in the test results they show up describing the tests, not just a bunch of expects.

1

1 Answers

2
votes

Something like this should work:

require 'csv'

describe "test suite name" do
  device_client = CSV.read path
  descriptor = device_client.shift
  descriptor = descriptor.map { |key| key.to_sym }
  clients = device_client.map { |client| Hash[ descriptor.zip(client) ] }

  clients.each do |client|
    describe "#{client[:test_scenario]}" do
      if "match some CSV field value"
        it "should run this kind of test" do
          #additional code as needed
          expect(some_actual).to eql(some_expected)
        end
      end

      if "match some other CSV field value"
        it "should run that kind of test" do
          #additional code as needed
          expect(some_actual).to eql(some_expected)
        end
      end

      it "some other test common to all CSV rows" do
        #stuff
      end
    end
  end
end

Notable changes from your version:

  • No need to require rspec (RSpec will load itself when you run the rspec command).
  • I moved the CSV logic out of before(:all) and into the describe body. The problem with what you had before is that before hooks are run after all examples and groups are defined, just before executing a particular group. You need to leverage the CSV file contents during spec definition time (during which the describe block is executed).
  • I switched from instance variables to local variables. What you had before didn't work because the before(:all) hook runs in the context of an instance of the example group class, where as the describe block runs in the context of the class itself -- so they run in different contexts and don't have access to the same instance variables. Once we move the logic into the describe block you can just use simple local variables.
  • I added clients = to the last line of your before(:all) logic. What you had before was throwing away the result of device_client.map { ... } but I'm assuming that's what you want to use.

For more about the difference in scope between describe and the before block, see the section in the rspec-core README that discusses scope.