RSpec unterstützt isoliertes Testen. Das ist sinnvoll, wenn Tests sich in verschiedenen Kontexten wiederholen. Modules sind ein klassisches Beispiel dafür.
Anhand der beiden PORO Klassen Person und Product:
class Person
end
class Product
end
soll geteilte Logik getestet werden.
Eine vorschnelle Definition der Spezifikation der Business Logik für Person in der person_spec.rb:
require 'spec_helper'
describe Person do
subject { Person.new }
describe "attribute accessors" do
it "should have name"
end
describe "#to_s" do
it "should return name"
context "when name is undefined" do
it "should be blank"
end
end
end
Kurz gesagt, Person soll einen attr_accessor :name erhalten und zusätzlich soll es eine Aliasmethode to_s auf name geben.
Nun könnten die Spezifikationen ausgefüllt und deren Implemenation in Person eingefügt werden, damit die Tests grünes Licht geben.
Die gleichen Anforderungen gelten allerdings auch für Product. Es macht keinen Sinn, die gleichen Specs noch einmal für Product zu schreiben. Um die Specs mehrfach wiederzuverwenden, müssen sie ausgelagert werden. Die ausgfüllten Tests liegen in nun spec/lib/humanizable_spec.rb:
require 'spec_helper'
shared_examples_for 'Humanizable' do
subject { described_class.new }
describe "attribute accessors" do
it "should have name" do
subject.name = 'Alice'
expect(subject.name).to eq('Alice')
end
end
describe '#to_s' do
it "should be equal to name" do
subject.name = 'Alice'
expect(subject.to_s).to eq('Alice')
end
context "when name is undefined" do
it "should be blank" do
subject.name = nil
expect(subject.to_s).to eq('')
end
end
end
end
Interessant sind eigentlich nur 2 Punkte:
- die Tests werden mit shared_examples_for (‘Humanizable’) zur Wiederverwendung freigegeben
- described_class liefert den Kontext, in dem getestet wird (z.B. Person oder Product)
Der Rest is Formsache.
Jetzt müssen die Tests nur noch genutzt werden. Die ursprüngliche person_spec.rb ist nun natürlich sehr viel kürzer:
require 'spec_helper'
require 'lib/humanizable_spec'
RSpec.describe Person do
it_behaves_like 'Humanizable'
end
Es wird lediglich deklariert, dass die Klasse sich verhalten soll, wie ‘Humanizable’. Das Gleiche soll ja auch für das Produkt gelten. Die product_spec.rb profitiert ebenfalls von geteilten Specs:
require 'spec_helper'
require 'lib/humanizable_spec'
RSpec.describe Product do
it_behaves_like 'Humanizable'
end
Nach Durchlaufen der Testsuite:
$ rspec spec/person.rb
werden wie erwartet Fehler gemeldet, da die Implementierung noch fehlt. Sie wird in dem Ḿodul lib/humanizable.rb umgesetzt:
module Humanizable
attr_accessor :name
def to_s
name.to_s
end
end
und schliesslich verwendet:
class Person < ActiveRecord::Base
include Humanizable
end
class Product < ActiveRecord::Base
include Humanizable
end
Die Testsuite:
$ rspec
gibt grünes Licht:
Finished in 0.00394 seconds (files took 1.43 seconds to load)
6 examples, 0 failures