Beruflich Dokumente
Kultur Dokumente
!. Model specs e
Well expand this outline in a few minutes, but this gives us quite a bit for starters. lts a simple spec
for an admiedly simple model, but points to our rst three best practices
Ea example (a line beginning with 1L) only expects on thing. Notice that lm testing
the 11rsLoamo and 1asLoamo validations separately. is way, if an example fails, l know its
because of that :ec:c validation, and dont have to dig through RSpecs output for clues.
Ea example is explicit. e descriptive string aer 1L is tenically optional in RSpec;
however, omiing it makes your specs more dicult to read.
Ea examples description begins with a verb, not should. S|ov|J is redundant here, and
cluers RSpecs output. Omiing it makes specs output easier to read.
With these best practices in mind, lets build a spec for the ooLacL model.
&UHDWLQJ D PRGHO VSHF
Open up the soc directory and, if necessary, create a subdirectory named mooo1s. lnside the
subdirectory create a le named cooLacL_socro and add the following
I roqo1ro `soc_oo1or`
2
8 ooscr1oo ooLacL EP
4 1L oas a va11o 1acLory
o 1L 1s 1ova11o u1LoooL a 11rsLoamo
1L 1s 1ova11o u1LoooL a 1asLoamo
7 1L roLoros a cooLacL`s 1o11 oamo as a sLr1oq
0 FOE
Well ll in the details in a moment, but if youd like you can now run the specs from your command
line
I $ rsoc soc/mooo1s/cooLacL_socro
You should see output similar to the following
I ooLacL
2 oas a va11o 1acLory (-00100 0oL yoL 1m1omooLoo)
8 1s 1ova11o u1LoooL a 11rsLoamo (-00100 0oL yoL 1m1omooLoo)
4 1s 1ova11o u1LoooL a 1asLoamo (-00100 0oL yoL 1m1omooLoo)
o roLoros a cooLacL`s 1o11 oamo as a sLr1oq (-00100 0oL yoL 1m1omooLoo)
!. Model specs
7 ooo1oq
0 ooLacL oas a va11o 1acLory
0 0oL yoL 1m1omooLoo
I0 /soc/mooo1s/cooLacL_socro4
II ooLacL 1s 1ova11o u1LoooL a 11rsLoamo
I2 0oL yoL 1m1omooLoo
I8 /soc/mooo1s/cooLacL_socroo
I4 ooLacL 1s 1ova11o u1LoooL a 1asLoamo
Io 0oL yoL 1m1omooLoo
I /soc/mooo1s/cooLacL_socro
I7 ooLacL roLoros a cooLacL`s 1o11 oamo as a sLr1oq
I0 0oL yoL 1m1omooLoo
I0 /soc/mooo1s/cooLacL_socro7
20
2I -1o1sooo 1o 00004o socooos
22 4 oxam1os, 0 1a11oros, 4 ooo1oq
Great! lour pending specslets make them pass.
As we add additional models to the contacts manager, assuming we use Rails mooo1 generator to
do so, this le (along with an associated factory) will be added automatically. (lf it doesnt go ba
and congure your applications generators now.)
*HQHUDWLQJ WHVW GDWD ZLWK IDFWRULHV
l wont spend a lot of time bad-mouthing xturesfrankly, its already been done. long story short,
there are two issues presented by xtures ld like to avoid lirst, xture data can be brile and
easily broken (meaning you spend about as mu time maintaining your test data as you do your
tests and actual code); and second, Rails bypasses Active Record when it loads xture data into your
test database. What does that mean` lt means that important things like your models validations
are ignored. is is bad!
lnter factories Simple, exible, building blos for test data. lf l had to point to a single component
that helped me see the light toward testing more than anything else, it would be lactory Girl`, an
easy-to-use and easy-to-rely-on gem for creating test data without the brileness of xtures. Since
weve got lactory Girl installed courtesy of the 1acLory_q1r1_ra11s gem we installed earlier, weve
got full access to factories in our app. lets put them to work!
Ba in the soc directory, add another subdirectory named 1acLor1os; within it, add the le
cooLacLsro with the following content
`https//github.com/thoughtbot/factorygirl
!. Model specs c
I -acLory01r1oo11oo EP
2 1acLory cooLacL EP 1
8 111rsLoamo Jooo
4 11asLoamo 0oo
o FOE
FOE
is unk of code gives us a [ocory we can use throughout our specs. lssentially, whenever we
create test data via -acLory(cooLacL), that contacts name will be John Doe. is is probably
adequate for our rst round of model specs, but l like to provide my specs with more random data.
is is where the laker gem comes into play. ldit cooLacLsro to include it
I roqo1ro `1a-or`
2
8 -acLory01r1oo11oo EP
4 1acLory cooLacL EP 1
o 111rsLoamo { -a-or0amo11rsL_oamo }
11asLoamo { -a-or0amo1asL_oamo }
7 FOE
0 FOE
Now our specs will use random, sometimes humorous names for ea generated contact. Notice
that l pass lakers 11rsL_oamo method inside a blolactory Girl considers these lazy aributes
as opposed to the statically-added strings our initial factory had.
Return to the cooLacL_socro le we set up a few minutes ago and locate the rst example (1L
oas a va11o 1acLory). Were going to write our rst specessentially testing the factory we just
created. ldit the example to look like the following
I roqo1ro `soc_oo1or`
2
8 ooscr1oo ooLacL EP
4 1L oas a va11o 1acLory EP
o -acLorycroaLo(cooLacL)sooo1o oo_va11o
FOE
7 1L 1s 1ova11o u1LoooL a 11rsLoamo
0 1L 1s 1ova11o u1LoooL a 1asLoamo
0 1L roLoros a cooLacL`s 1o11 oamo as a sLr1oq
I0 FOE
http//rubygems.org/gems/faker
!. Model specs v
is single-line spec uses RSpecs oo_va11o mater verify that our new factory does indeed return
a valid contact.
Run RSpec from the command line again and you should see one passing example, with three
pending.
7HVWLQJ YDOLGDWLRQV
Validations are a good way to break into automated testing. ese tests can usually be wrien in
just a line or two of code, especially when we leverage the convenience of factories. lets add some
detail to our 11rsLoamo validation spec
I 1L 1s 1ova11o u1LoooL a 11rsLoamo EP
2 -acLoryoo11o(cooLacL, 11rsLoamo OJM)sooo1o_ooL oo_va11o
8 FOE
Note what were doing with lactory Girl here lirst, instead of the -acLorycroaLo() approa,
were using -acLoryoo11o(). Can you guess the dierence` -acLory() builds the model and saves
it, while -acLoryoo11o() instantiates a new model, but doesnt save it. lf we used -acLory() in
this example it would break before we could even run the test, due to the validation.
Second, we use the ooLacL factorys defaults for every aribute except 11rsLoamo, and for that
we pass o11 to give it no value. ln other words, instead of the default name of }o|n Doe our ooLacL
factory would normally give us, it returns }o|n. is is an incredibly convenient feature, especially
when testing at the model level. Youll use it a lot in your testsstarting with models, but more in
other tests, too.
Run RSpec again; we should be up to two passing specs with two pending. We can use the same
approa to test the 1asLoamo validation.
I 1L 1s 1ova11o u1LoooL a 1asLoamo EP
2 -acLoryoo11o(cooLacL, 1asLoamo OJM)sooo1o_ooL oo_va11o
8 FOE
You may be thinking that these tests are relatively pointlesshow hard is it to make sure validations
are included in a model` e truth is, they can be easier to omit than you might imagine. lf you
think about what validations your model should have +|:|e writing tests (ideally, in a Test-Driven
Development paern), you are more likely to remember to include them.
ln addition, testing validations becomes more important when they are more complex than simply
validating presence or uniqueness. lor example, lets say we want to make sure we dont duplicate a
phone number for a usertheir home, oce, and mobile phones should all be unique to them. How
might you test that`
ln your oooo model spec, you might have the following example
!. Model specs 1c
I 1L ooos ooL a11ou oo11caLo oooo oomoors or cooLacL EP
2 cooLacL - -acLory(cooLacL)
8 -acLory(oooo, cooLacL cooLacL, oooo_Lyo oomo, oomoor 70ooooI2\
4 84)
o -acLoryoo11o(oooo, cooLacL cooLacL, oooo_Lyo moo11o, oomoor 70\
ooooI284)sooo1o_ooL oo_va11o
7 FOE
And make it pass with this validation in your oooo model
I va11oaLos oooo, oo1qooooss { scoo cooLacL_1o }
By the way, thats not a typo in the previous sample spec-acLory() is a shortcut for -ac
LorycroaLo().
Of course, validations can be more complicated than just requiring a specic scope. Yours might
involve a complex regular expression or a custom method. Get in the habit of testing these
validationsnot just the happy paths where everything is valid, but also error conditions.
7HVWLQJ LQVWDQFH PHWKRGV
lt would be convenient to only have to refer to cooLacLoamo to render our contacts full names
instead of creating the string every time; lets implement that feature in our ooLacL class now
I EFG oamo
2 |11rsLoamo, 1asLoamo|o1o
8 FOE
We can use the same basic teniques we used for our validation examples to create a passing
example of this feature
I 1L roLoros a cooLacL`s 1o11 oamo as a sLr1oq EP
2 cooLacL - -acLory(cooLacL, 11rsLoamo Jooo, 1asLoamo 0oo)
8 cooLacLoamosooo1o -- Jooo 0oo
4 FOE
!. Model specs 11
7HVWLQJ FODVV PHWKRGV DQG VFRSHV
lets test the ooLacL models ability to return a list of contacts whose names begin with a given
leer. lor example, if l cli S then l should get Sn:|, Svnner, and so on, but not }one:. ere are
a number of ways l could implement thisfor demonstration purposes lll show one.
lirst, lets say we oose to add this functionality via a class method like the following
I EFG TFMGoy_1oLLor(1oLLor)
2 uooro(1asLoamo .1-- ?, \1oLLor^)oroor(1asLoamo)
8 FOE
To test this, lets add the following to our ooLacL spec
I roqo1ro `soc_oo1or`
2
8 ooscr1oo ooLacL EP
4
o WBMJEBUJPO FYBNQMFT
!. Model specs 1z
7 1L roLoros a sorLoo array o1 roso1Ls LoaL maLco EP
0 sm1Lo - -acLory(cooLacL, 1asLoamo Sm1Lo)
0 ooos - -acLory(cooLacL, 1asLoamo Jooos)
I0 ooosoo - -acLory(cooLacL, 1asLoamo Jooosoo)
II
I2 ooLacLoy_1oLLor(J)sooo1o -- |ooosoo, ooos|
I8 FOE
I4
Io 1L roLoros a sorLoo array o1 roso1Ls LoaL maLco EP
I sm1Lo - -acLory(cooLacL, 1asLoamo Sm1Lo)
I7 ooos - -acLory(cooLacL, 1asLoamo Jooos)
I0 ooosoo - -acLory(cooLacL, 1asLoamo Jooosoo)
I0
20 ooLacLoy_1oLLor(J)sooo1o_ooL JODMVEF sm1Lo
2I FOE
22 FOE
is spec uses RSpecs 1oc1ooo mater to determine if the array returned by ooLacLoy_1oL
Lor(J)and it passes! Were testing not just for ideal resultsthe user selects a leer with
resultsbut also for leers with no results. However, a problem is brewing in our speccan you
spot it`
'5<HU VSHFV ZLWK EHIRUH DQG DIWHU
Our spec currently has some redundancy We create the same three objects in ea example. Just as
in your application code, the DRY principle applies to your tests (with some exceptions; see below).
lets use a few RSpec tris to clean things up.
e rst thing lm going to do is create a ooscr1oo blo +:|:n my ooscr1oo ooLacL blo to
focus on the lter feature. e general outline will look like this
I roqo1ro `soc_oo1or`
2
8 ooscr1oo ooLacL EP
4
o WBMJEBUJPO FYBNQMFT