TestUnit - very straight unit test framework. Unit testing was used for ages.
Unit test consists of:
Every unit test is a workflow
Every spec is a requirement that consists of:
Example:
describe User do
subject { User.new(@valid_attributes) }
# or
subject { Factory.build(:user)
describe ".activated named scope" do
subject { User.activated }
end
end
Default subject is: described_class.new
And that's for a reason.
Benefits of subject:
Matchers:
describe User do
subject { build_user_with_factory }
it { should be_valid }
its(:profile) { should_not be_completed } # 2.0
# subject.profile.completed?.should be_true
its("tasks.first") {should be_a(FindFriendsTask)} # 2.0
# subject.tasks.first.is_a?(FindFriendsTask).should be_true
end
Internal matchers:
External libraries:
Beware: copypaste is not BDD (and is not programming)!
This is where the problem is!
Rspec is designed to describe context-oriented logic
Business logic is always context oriented
Describe non context-oriented logic in rspec has no benefits
Context-oriented software - from less context dependent to very context dependent:
def let(name, &block)
define_method name do
@assignments ||= {}
@assignments[name] ||= instance_eval(&block)
end
end
def let!(name, &block)
let(name, &block)
before { __send__(name) }
end
# sampe principle of lazy load for subject
def subject
if defined?(@original_subject)
@original_subject
else
@original_subject = instance_eval(&self.class.subject)
end
end
describe User do
describe ".activated named scope" do
subject { User.activated }
let(:activated_user) { <factory> }
let(:not_activated_user) { <factory> }
it {should include(activated_user)
it {should_not include(not_activated_user)
end
end
let(:user) { ... }
let(:community) { ... }
let(:membership) {
Membership.new(
:user => user, :community => community
)
}
Will create user and at once load it to the context
let!(:user) { ... }
describe Ticket do
context "after save!" do
it { should be_new }
context "after approved by admin" do
it {should be_approved}
end
context "after declined by admin" do
it { should be_declined }
end
end
end
Use to describe utitlity functions
describe BooleanExpression, ".run" do
subject {BooleanExpression.run(string)}
context "with '&' and both true" do
let(:string) { "true & true" }
it {should be_true }
end
context "with '&' and one false" do
let(:string) { "false & true" }
it {should be_false }
end
end
context "match scheduler" do
before(:each) do
school = Factory.create(:school)
@team = Factory.create(:team, :school => school)
@student1 = Factory.create(:student, :team => @team, :school => school)
@student2 = Factory.create(:student, :team => @team, :school => school)
@competition = Factory.create(:competition, :team => @team)
end
describe "upcoming_matches" do
it "should return upcoming match for particular student" do
@student1.upcoming_matches.first.id.should ==
@student2.upcoming_matches.first.id
end
end
describe "next_opponent" do
it "should return correct opponent for student" do
@student2.next_opponent.should == @student1
@student1.next_opponent.should == @student2
end
end
describe "current_match" do
it "should return started match for student" do
@student1.current_match.should be_nil
@competition.start!
@competition.matches.first.id.should == @student1.current_match.id
end
end
end
context "student" do
let(:school) { Factory.create(:school) }
let(:team) { Factory.create(:team, :school => school) }
subject do
Factory.create(:student, :team => team, :school => school)
end
it { should be_valid }
context "match scheduler" do
before { subject }
let!(:opponent) do
Factory.create(:student, :team => team, :school => school)
end
let!(:competition) { Factory.create(:competition, :team => team) }
its(:upcoming_matches) { should_not be_empty }
its(:next_opponent) { should == opponent }
its(:current_match) { should be_nil }
its(:next_match) { should == opponent.next_match }
context "competition started" do
before { competition.start! }
its(:current_match) {should == competition.matches.first}
end
context "after_destroy" do
before { subject.destroy }
its(:team) { should_not be_destroyed }
end
end
end
describe User do
it { should accept_values_for(
:email, "john@example.com", "lambda@gusiev.com"
)}
it { should_not accept_values_for(
:email, "invalid", nil, "a@b", "john@.com"
)}
end
https://github.com/bogdan/accept_values_for
There is a lot information on the web how to do that.
Use shared examples group to reuse tests
class User
include CommunityMember
end
describe User
it_should_behave_like "CommunityMember"
#Quotes can be remove in 2.0
end
shared_examples_group "CommunityMember" do
context do
it { should ..... }
end
end
In order to make your tests reusable you need to:
shared_examples_for "Traits::Dictionary::Core" do
describe "as Traits::Dictionary::Core" do
it {should_not accept_values_for(:title, nil)}
describe ".options_for_select" do
let!(:object) do
Factory.create(described_class.to_s.underscore)
end
subject { described_class.options_for_select }
it { should == [[object.title, object.id]]}
end
end
end
We can do all kinds of staff in SEG:
The following aspects are not touched in the book:
The schema not considered for Agile development.
And very good for elder projects.
The development lifecycle consists of the following steps:
Acceptance tests are used only after UI gets stable.
Always remember
We are agile. Your code might live not more than one day.
In order to describe business logic in agile development process we use the best tool designed for that - Rspec, that offers cool api:
That let implement the following ideas: