So now I need to try out unit testing with Rspec. I need to have a spec file for every class file, and a spec file is called a unit test, unit tests are for individual classes. I read about that in this stack overflow conversation and it mentions that unit tests allow for granular-testing. I guess sometimes developers test too large a unit sometimes. They said unit tests should test individual behaviors. I read this wiki about inversion control in regards to that, and making sure your unit tests don't become integration tests. Not sure I entirely understand that but just going to get started. It seems like they are just saying to carefully limit the scope of your unit tests to remove dependancies from you code. This stack overflow conversation helped me understand it. note to self: ask about this
I made this test in "airplane_spec.rb"
require_relative "../../lib/airplane"
describe Airplane do
describe '#flying?' do
context 'when the plane is flying' do
it 'returns true' do
expect(Airplane.new.flying?).to eq true
end
end
context 'when the plane is not flying' do
it 'returns false' do
expect(Airplane.new.flying?).to eq false
end
end
end
end
and in "airplane.rb" I started out with code, even though I knew it wouldn't pass.
class Airplane
end
OUTPUT:
FF
Failures:
1) Airplane#flying? when the plane is flying returns true
Failure/Error: expect(Airplane.new.flying?).to eq true
NoMethodError:
undefined method `flying?' for #<Airplane:0x007fdb6b338af8>
# ./spec/lib/airplane_spec.rb:7:in `block (4 levels) in <top (required)>'
2) Airplane#flying? when the plane is not flying returns false
Failure/Error: expect(Airplane.new.flying?).to eq false
NoMethodError:
undefined method `flying?' for #<Airplane:0x007fdb6a08c800>
# ./spec/lib/airplane_spec.rb:13:in `block (4 levels) in <top (required)>'
Finished in 0.00658 seconds (files took 0.40809 seconds to load)
2 examples, 2 failures
Failed examples:
rspec ./spec/lib/airplane_spec.rb:6 # Airplane#flying? when the plane is flying returns true
rspec ./spec/lib/airplane_spec.rb:12 # Airplane#flying? when the plane is not flying returns false
This means, yeah, I have an undefined method flying? and I also need to be able to set two airplanes to either flying or not. Let me go ahead and do that.
class Airplane
attr_accessor :flying
def initialize(status)
if
status == 'flying'
@flying = true
else
@flying = false
end
end
def flying?
@flying
end
end
SPEC:
require_relative "../../lib/airplane"
describe Airplane do
describe '#flying?' do
context 'when the plane is flying' do
it 'returns true' do
expect(Airplane.new('flyin').flying?).to eq true
end
end
context 'when the plane is not flying' do
it 'returns false' do
expect(Airplane.new('grounded').flying?).to eq false
end
end
end
end
OUTPUT:
rspec
F.
Failures:
1) Airplane#flying? when the plane is flying returns true
Failure/Error: expect(Airplane.new('flyin').flying?).to eq true
expected: true
got: false
(compared using ==)
# ./spec/lib/airplane_spec.rb:7:in `block (4 levels) in <top (required)>'
Finished in 0.02943 seconds (files took 0.16584 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/lib/airplane_spec.rb:6 # Airplane#flying? when the plane is flying returns true
..SOOO I have a failure here... what happened... yeah, I spelled flying wrong, ok! so I fixed that and the test is passing 2 examples.
Lets try something with a bit more complexity than an airplane- my gf.
SPEC!
require_relative './gf_unit_test/gf.rb'
describe Girlfriend do
describe '#pissed?' do
context 'when I say something stupid' do
it 'returns true' do
expect(Girlfriend.new('pissed').pissed?).to eq true
end
end
context 'when I dont say something stupid' do
it 'returns false' do
expect (Girlfriend.new('happy').pissed?).to eq false
end
end
end
end
CODE!
class Girlfriend
attr_accessor :pissed
def initialize(mood)
if
mood == 'pissed'
@pissed = true
else
@pissed = false
end
...and it NOT WORKING. it WON"T READ THE FILE. I need help getting this to work.. not reading file no matter what I do.
ok, I did get it working but it's really stupid. So I'd put my spec and code files in the same folder.. then I tried running "rspec" and it wasn't finding the file. So it took both stating require_relative 'gf.rb' AND saying rspec gf_spec.rb in terminal specifically, for it to find it. I guess the default "rspec" looks for a file in a spec folder... HOW would i ever have known that. I changed my file structure before i figured it out, and put my spec file in a spec folder and it worked so I put two and two together. jeez.
here's how i had to arrange my files to get it to work before figuring out that default thing:
tree
.
├── gf.rb
└── spec
└── gf_spec.rb
OUTPUT
gf_unit_test git:(master) ✗ rspec
..
Finished in 0.00654 seconds (files took 0.19184 seconds to load)
2 examples, 0 failures
CODE
class Larisa
def initialize(plan)
if
plan == 'travel'
@plan = true
else
@plan = false
end
end
def plan?
@plan
end
end
SPEC:
require_relative '../larisa'
describe Larisa do
describe '#plan?' do
context 'when larisa is making plans' do
it 'returns true' do
expect(Larisa.new('travel').plan?).to eq true
end
end
context 'when larisa makes no plans' do
it 'returns false' do
expect(Larisa.new('stay home').plan?).to eq false
end
end
end
end
OUTPUT:
rspec
.F
Failures:
1) Larisa#plan? when larisa makes no plans returns false
Failure/Error: expect(larisa.new('stay home').plan?).to eq false
NameError:
undefined local variable or method `larisa' for #<RSpec::ExampleGroups::Larisa::Plan::WhenLarisaMakesNoPlans:0x007f8e319313b0>
# ./spec/larisa_spec.rb:13:in `block (4 levels) in <top (required)>'
Finished in 0.01059 seconds (files took 0.27478 seconds to load)
2 examples, 1 failure
OK so... about this- I had a few variable and spelling mixups, I initialized plan? and was using plans?, etc. Good practice for repetition. Will do this again.
so theres a another kind of test I tried. first I made a card class with a couple variables initialized and passed through:
class Card
attr_accessor :suit, :number
def initialize(suit, number)
@suit = suit
@number = number
end
def facecheck
if @number == 'jack' || @number == 'king' || @number == 'queen'
return true
else
return false
end
end
def checker
if @suit == 'ace'
return true
else
return false
end
end
end
..Basically just wanted to make something easy-ish to test. Then I made a test that checks to see if the card drawn is a facecard (this file is called dance because I originally was going to make something else so just ignore that:
require_relative '../dance'
describe Card do
describe 'facecheck' do
context 'checks to see if the card drawn is a face card' do
it 'returns true' do
expect(Card.new('club', 'jack').facecheck).to eq true
end
it 'returns false if not a face card' do
expect(Card.new('diamond', 'sandy').facecheck).to eq false
end
end
end
end
OUTPUT:
.F
Failures:
1) Card facecheck checks to see if the card drawn is a face card returns false if not a face card
Failure/Error: expect(Card.new(10, 'sandy').facecheck).to eq false
expected: false
got: true
(compared using ==)
# ./spec/dance_spec.rb:11:in `block (4 levels) in <top (required)>'
Finished in 0.03972 seconds (files took 0.23347 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/dance_spec.rb:10 # Card facecheck checks to see if the card drawn is a face card returns false if not a face card
So here's what happened. Initially I had something in my code that looked like this:
def facecheck
if @number == 'jack' || 'king' || 'queen'
return true
else
return false
but this was making my test always return true because it was not checking to see if EXACTLY king and queen were passed through, it was looking at whether a string was passed through so it was returning true all the time. it should have looked like this:
def facecheck
if @number == 'jack' || @number == 'king' || @number == 'queen'
return true
else
return false
end
end
And so that made my test pass.
Then I worked on a few more to test out my card class.
describe Card do
let(:blah) {Card.new('club', 'king')}
describe '.new' do
it 'takes two arguments as arguments' do
expect(blah).to be_a(Card)
end
end
...This one just tests to see if a card is made when I pass two arguments through, and indeed it is. This is the first time I used to be_a..pretty exciting.
I also made the very similar test:
describe 'club?' do
it 'will be a suit that is a club' do
expect(blah.suit).to eq('club')
end
end
Here I was just practicing making stacked tests using the same variable :blah. I tried making one more that I didn't get to work:
describe 'facecheck?' do
it 'will be a facecard' do
expect(blah.suit).to be_a('club')
end
end
OUTPUT
rspec
....F
Failures:
1) Card facecheck? will be a facecard
Failure/Error: expect(blah.suit).to be_a('club')
TypeError:
class or module required
# ./spec/dance_spec.rb:48:in `block (3 levels) in <top (required)>'
Finished in 0.00844 seconds (files took 0.18251 seconds to load)
5 examples, 1 failure
Failed examples:
rspec ./spec/dance_spec.rb:47 # Card facecheck? will be a facecard
I know I'm getting my variables mixed up, will speak to nate or something.
So it seems like I spent as much time messing with getting rspec to read my stuff as I did actually trying to get the tests to pass. Another question. I'm not sure why I need the accessor AND the def to get this thing to work.. I dunno, I read this about filepaths and it did not answer my question.
...OK so update, I understand why I need the attr_accessor AND the @variable = variable... That's because the attr_accessor makes it so I don't need to RETURN anything while I'm making other methods in my class but I still need to initialize my @variable in the first place. I think that's the answer. (see below for what I'm referring to)
class Card
attr_accessor :suit, :number
def initialize(suit, number)
@suit = suit
@number = number