(joyful music)
- So a few more people than I expected but that's okay.
We'll roll with this.
So,
if you haven't already done this
head to the GitHub repo up there.
Github.com/Jesse-spevack/cleanrspec
fork and clone the repo.
And if you can complete the welcome survey.
So I kinda have an idea of who's in here
and what, what you're looking for in this presentation.
And then if you wanna get bonus points,
you can also grab a copy of a bingo board.
That's located in that getting started section.
And the idea here is like a workshop this size
is actually kind of difficult.
And the real tricky thing
is understanding the level of the participant, right?
So maybe I'm gonna pitch this way too high
or way too low.
Hopefully it's just right.
But what I wanna do
is just give everyone lots of different options
in this, in this context.
So if you're like an RSpec wizard,
grab that bingo board
and maybe, maybe this will be more entertaining for you.
Also I know this is kind of like a prime spot.
There's a lot of awesome talks happening right now.
So if you need to get up
and go to a different talk at some point,
I will take it very personally.
I have a camera set up.
And actually if you could just like tweet
or something, tweet at me like,
"Hey, I had a great time
at your, at your talk,
but going to a better one."
or something like that.
That'd be helpful.
(laughs) Okay, I'm gonna switch slides.
So if you don't have the repo,
make sure that the person sitting next to you has it
so you can get it from them.
Okay, so I'll introduce myself.
My name is Jesse Spevack.
I use he/him pronouns.
I am located in Denver, Colorado.
These are my twin five-year-olds, Eddie and Audrey.
So Colorado is awesome.
Raise your hand if you are local to Colorado.
- [Woman] (indistinct)
- Woow!
Yeah, a lot of Coloradans here.
That's awesome.
So I currently work at a company called Stripe
and we work on the economic infrastructure
of the internet (laughs).
I wish I could tell you more about Stripe
but this is,
day two
of my third week at Stripe.
So, maybe in a few months you can ask me about it
but pretty stoked about this.
Before Stripe, I kind of came to...
Actually before Stripe,
I worked at a company called Ibota*.
Are there any ibota people here?
Okay, cool.
One upfront, I see you.
Before Ibota,
I came into coding
and the tech world through a non-traditional background.
So I went to a program called Turing,
which is located in Denver or center around Denver.
Raise your hand if you're a Turing person here.
Yeah, all right.
So Turing,
right, at the time I went to Turing was 2016.
There's kind of this proliferation of code schools
of pretty
wide, a wide array of quality.
And I would say that Turing
is definitely on the end of a very high quality program.
And one of the things that's so high quality about it
is its mission.
Which is to bring folks from diverse backgrounds
from non-traditional backgrounds into this profession
and into high fulfillment technical career.
So I love my Turing people.
I love being a part of this community.
And I think Turing has had a huge impact
on this Ruby conference and the Ruby community in general.
Before I keep going, I also want to remember,
I wanna thank a few people.
So I need to thank the Ruby comp planning team
for putting this on.
Anyone in a red shirt in here who's volunteering.
Thank you so much.
If you see people in red shirts,
make sure you thank them.
I wanna thank confreaks
and the folks who are putting, doing the video and audio.
I really appreciate it.
And then of course
I wanna thank all of you for coming
and using this primetime of Ruby conf
to do some RSpec with me.
So thank you.
All right, raise your hand
if you've ever heard of the gilded rose kata
or let me ask a more general question.
Raise your hand if you have heard of a code kata before.
Okay, cool.
And also I'm supposed to say,
if um,
don't be afraid of the mics.
if you need to, if we're gonna, this...
Hopefully will be a little participatory.
And if you need to ask a question, please use the mic.
So anyway,
so the idea of a code kata
is it's like an exercise that you do to
learn a particular coding principle.
And there's a very famous code kata
called the gilded rose code kata
and it's famous for a couple of reasons.
So,
I guess one,
It has its origin story
as like a thing from world of Warcraft.
Raise your hand if you've ever played world of Warcraft.
Okay, so some people know about this game.
It came out in 2004, it was very very popular for awhile.
I was not addicted to it at any time.
And,
this kata takes some concepts
from this, this part of world of Warcraft,
this area in world war craft called gilded rose.
And it's very,
it's actually a lot more famous
because a very famous person, Sandy Metz
gave a talk a few years ago,
a few years ago called All the Little Things.
Now,
raise your hand if you've seen this talk from I think 2014.
Okay, so everybody,
if you are gonna leave this talk in the middle,
which is okay, the best reason to leave
would be to watch this talk (laughs),
which is on YouTube on con freaks
or on Railsconf 2014 confreaks playlist.
So,
what Sandy does in this talk,
which is amazing
is,
she goes through the gilded rose kata
to teach some really interesting object oriented design
practices.
So I thought that it would be interesting
to kind of use this code
that I thought some folks might have some familiarity with
to talk about good testing design
and good RSpec specifically.
So without belaboring that too much,
I wanna hopefully get a few things out of this workshop.
The first is I want you all to leave with
the idea of the purpose and benefits of testing.
I wanna talk,
get really clear on what the object under test is.
I wanna talk about how
testing can be used to document code functionality.
I want to make sure everybody understands
the three-phase test pattern.
And I want us to make sure that we're...
In my opinion,
we should be writing our tests to optimize for readability.
And then the last part
and I think that there were a few talks already today
about this but to use test doubles judiciously.
And we'll get into that.
Okay.
Just a little more disclaimer and,
prologue to all this.
The format of the workshop...
So I used to be a teacher for a while
and the format comes from my experiences as a teacher.
I've never taught in a classroom that looks quite like this
but that's exciting.
So the idea with this is I'm gonna try to show something.
Like I'm gonna do something up here, demonstrate something,
we're going to do something together
and then you'll get a chance to do it on your own.
And that's kind of this pattern
that we're going to follow through this workshop.
Maybe it'll work for, for you
because the you do portion
will be like right at what you need.
It'll be at like exactly the right level
but maybe it'll be too easy for you
or maybe it'll be too hard for you or whatever.
In those cases,
what I would say is the documentation and the read me
pretty tightly follows
like where I'm gonna be going in the workshop.
So if you need to speed ahead, you are welcome to
and if we kind of start moving ahead as a group,
you have that documentation available to you.
So here's where we're gonna start doing
some audience participation.
So I'm gonna appreciate the person
who's gonna be the most brave
and join me in talking to this whole group (laughs),
so my, my first question is why, why do we test?
Why should we, why should we even be here right now?
- [Man] To validate that our code does what our business
told us it should do.
- To validate what that our code does
what our business told us it should do.
Nice.
- [Woman] Catch bugs before QA.
- Yeah, catch bugs before QA, in the back.
- [Man] Keep me from breaking my own code.
- Keep me from breaking my own code.
That doesn't work for me but yeah.
- [Man] To document our code
so new developers coming in to go ahead
and run them and see what (indistinct)
- Yeah, document the code.
So I wanna...
I think that covers a lot of it.
And as we go through these exercises,
I want to just kind of center on those ideas of why we test.
So some folks might have different answers about why we test
and for those folks,
this actually might not...
The things that I'm prescribing
might not be the right solution for them.
But if you're on board with what these folks just said,
I think hopefully you'll get a lot out of this.
I also wanna distinguish between two types of tests,
there's unit and integration tests.
Unit tests,
tests like a specific small portion of business logic,
a unit of business logic
Typically like a single function or a single class
and an integration test
tests sort of the interaction of many things.
So,
a good example would be like
you know, if, of an integration test would be,
you know, if I poke a person and they have a reaction,
there's like a lot of systems in the body
that are responding to that poke.
If I was just testing
the interaction of a chemical moving from one cell
to another maybe that would be more of a unit test.
So going back to Sandy also, I forgot this disclaimer.
So there's like all these people in this conference
who have done amazing things in the Ruby community (laughs).
And they're like super smart
and I've been to a couple of their talks
and I just wanna be...
I think the thing that I'm hoping I bring is that...
I haven't written any amazing gems.
You probably haven't used code that I've written
but I've taken some deep dives into the world of testing
and hopefully I can share that with you.
So Sandy gave another talk
called the 'Magic Tricks of Testing'.
Has anybody seen this talk?
Even fewer.
Okay, three people, four people.
Someone saw it a little bit. (giggles)
Okay, this is another amazing talk
that I highly highly recommend
and is gonna kinda drive some of the conversation
we're having.
So Sandy talks about...
In addition to this idea of like a unit
in an integration test,
when we're specifically talking about unit tests,
there's kind of two types of I guess units
that we would be thinking about.
There are two types of methods that we might wanna test.
The first is a query method.
And a query method returns something and changes nothing.
And the second type is a command method.
And a command method returns nothing but changes something.
So when we have these two types of methods,
we're gonna test them in different ways.
So I'm gonna show you a method and I want you to decide
with the person sitting next to you
if it is a query method or a command method.
Okay, here it is.
Okay, talk to the person sitting next to you
for like 30 seconds.
Query method or command method.
(audience speaking indistinctly)
Okay, good conversation.
Thank you for engaging in that.
Who's got an idea?
Who wants to share what their partner said
or share what your partner discussed.
Yeah, go ahead.
- [Woman] Query.
- Query, great.
Does anyone wanna elaborate on that? (giggles)
Yeah, in the back or in the middle.
Yeah, for sure, for sure.
So definitely been in the situation
where something says you know,
something says count
and maybe under the covers it's not just counting
but popping something off or adding something.
But yeah, I think that's right.
Query, based on what we see,
this is most likely a query method.
How about this one?
(audience speaking indistinctly)
Okay, I think...
Can I call on you in the orange?
Okay.
- [Woman] (inaudible)
Right, so this person just said that it's a command method
because it's changing the state
and given the caveat of the previous answer
that maybe something weird is happening here.
Yeah, I think that's right.
So the question is,
how do you tell,
like how do you go about testing a query method
and how do you go about testing command method?
Right, so what I would say and I'm stealing this from Sandy.
When you have a query method,
you wanna test the return value, right?
You wanna say...
Right, if I wanna um,
test that workshop count X,
works the way that I think it should,
I'm gonna say the return value of workshop count
is what I think it should be.
There's 30 people in the workshop.
When I call workshop count I should get 30.
In um,
in a command method, you're testing the state change, right?
So instead of saying enroll returns a certain value,
I actually should test the state, the participants state
to figure out if this is working the way it should work.
So probably if I enroll one participant,
the number of participants should go up by one
and so my test is gonna be on that participants array thing.
And so Sandy put together this wonderful diagram
to kind of explain that
and what we just talked about is kind of that top row.
And then the bottom row
is where we're gonna get into test levels
in just a little bit.
Okay.
So we're about to jump into our first workshop activity
and the goal here is to understand
this concept of an object under test.
And I wanna go with
kinda my, just my experience here
which is a lot of times,
I'll be asked to implement some new code or understand
why something is doing what it's doing.
And I'll open up the test file
because they always say
like tests are a great documentation.
And if I need to figure out what is supposed to happen here,
I can just go look at RSpec file and it should be...
It should tell me more or less what's going on.
And for me, I've actually never had that experience.
Like it's very rare for me to go into an RSpec file
and gain more clarity about what should be happening.
Do you folks feel like they get a ton of clarity?
I guess raise your hand if you don't get a ton of clarity
when you open an RSpec file typically.
Yeah.
And at work, we sometimes host folks from Turing
to come in new crew, early career developers
who are finishing up their journeys at Turing.
And the thing that really brought this home for me
is I'll take someone who's never seen the code base
and I'll pick a feature that I think this can be solved
with one line of code or maybe just even one word.
And it always takes the longest amount of time
to explain the spec file to this poor person.
And 45 minutes will go by and I'm like, "Okay,
and then the third context block you'll see..."
And,
I think that hopefully
what we'll get from this is that...
Hopefully what we'll get from this is that these...
We can optimize the way that we write tests
to make it not actually confusing for ourselves
or for our new teammates that we're bringing up to speed.
Okay.
So I wanna show you some code.
This is totally
valid Rspec.
There's a keyword here called subject.
Raise your hand if you're familiar with the subject keyword.
I'm not gonna make you explain it.
Okay, a lot of folks are so we'll go very fast.
So Rspec somehow under the hood, figures out that...
We will default and give you the subject keyword
which maps to the workshop matched to the class
that you're testing.
And so in this case, we're saying we expect this workshop
to be a type of workshop, to be an instance of a workshop.
To me, I have a little bit of a problem with this,
with this test.
And the problem is (laughs)
that there's a mystery.
Yes, here.
There's a ghost in the room.
Basically, unless you have a lot of context
about how RSpec works.
It can be difficult to understand what this subject is.
So if someone is coming from like a Java code base,
hasn't used RSpec or they're brand new to RSpec in general,
they're gonna see this
and it just requires a little bit more knowledge
to understand what's going on.
Okay, so how can we improve this?
One thing we can do is call out explicitly
what subject refers to.
Now, these two pieces of code
are exactly identical functionally.
They're doing exactly the same thing.
The difference is that in this example,
I'm calling out on that first-line subject
is actually a new workshop.
This is okay.
I think that the better way
subject can also take in an argument
and you can name your subject,
I think this is even better
because again, it's making it even more explicit
what the,
what this test is about.
And so when I write an RSpec,
when I write RSpec,
the first thing I do is put the subject right upfront,
I give it a name
and then I reference that name in my subsequent tests.
And I'm trying to optimize for that situation
where someone new to Rspec,
where someone new to RSpec is coming in
and has to interpret this.
Okay.
So now I'm gonna ask you all to do something.
We'll see how far we get in this but if you go,
if you you open up the um,
the repo that I asked you to, to clone,
on line seven of the gilded rose spec
there's some
poorly written Rspec,
to demonstrate like how you can call out the subject.
How you can call out this object under test.
So take a second, take like two, three minutes
and see if you can reformat that test.
If you've got time commit, push it up, pull it
and then we can start looking at each other's code.
I won't put anyone up here on the spot
unless they asked to be
but let's see if we can rewrite that test on line seven
and then maybe we can talk about it
in about three minutes, I wanna say.
So let's take three minutes.
I'm turning my mic off
and if you need me just raise your hand.
Okay.
So another...
I was just...
I, I forgot to mention this.
Another possible extension activity
for the folks who feel super comfortable with this already
is you can actually start doing the gilded rose kata
if you get distracted.
And I heard in the front up here, it seems like I totally...
I made these bingo boards and I sort of, I sort of forgot
to say what to do.
So if you get bingo,
you can kind of wave your hand
and I will congratulate you on getting bingo.
And again, again, if this is...
If you feel like this is going too slow just jump in
and make yourself a bingo board.
Did anybody come up with a way to apply what we did
with the workshop example to our gilded rose example?
Does anybody wanna just call it out?
Yes, awesome.
So basically the, the big picture here
is get rid of subject here.
You can call it out up top and put the name gilded rose here
and go from there.
Is there a question or comment?
- [Woman] Do you ever use described class
instead of calling out gilded rose
or do you find that more confusing?
- I'm actually not familiar with that.
What is that?
- [Woman] So, (mumbles) instead of doing gilded rose
that new to describe the class.
- Oh! Oh! Oh! Oh! Described class.
The keyword described class, I see.
Yeah, so I like the describe class keyword a lot
because I, when I start a new...
Especially when I started a new file,
naming is hard
and if I, if I use describe class,
then I can change the name of the class
and not have to go through all my tests
and do like a fine command.
(woman talking indistinctly)
Okay, so the original answer is take subject from here,
move it up here, give it a name like gilded rose,
curly braces, gildedrose.new.
So you're saying the subject of this test
is a new gilded rose and then replaced subject here
with gilded rose.
So when the next person comes in here, they'll see,
"Okay, cool.
This is a test about a gilded rose
and not just some random subject keyword
that I need to know the internals of Rspec
to be familiar with."
Yeah.
- [Man] What are your thoughts on a local variable
instead of letter.
- No. (giggles)
We'll get to, we'll get to that.
Yeah, so...
Okay, so let's talk about describe context and it.
And I put together...
I did it like a scientific study of every developer,
every Ruby developer in the world
and every Ruby code base in the world and it turns out
that basically for every line of Ruby that's written,
if you break down the time spent writing it
versus reading it, you end up with this exact graph.
So the writing portion is the red portion.
It represents about 10% of the time.
And the reading it is about 90% of the time.
And actually the longer the code base is around,
the more that skews towards reading.
So if we are writing code that for the most part is red
and not written, we should be optimizing our code
for the reader and not the writer.
Okay, I'm gonna say that again.
If we're writing code that is mostly red,
we should be optimizing for the reader of the code,
not the writer of the code.
And that can be selfish because often we read our own code.
So the sort of next part here
is meant to demonstrate one way that you can optimize
for the reader when you're doing RSpec.
Okay, so we've gotten to the point
where we are describing this workshop
and we have this key,
we have this method in RSpec called describe
and I think this is super helpful.
You start out by saying,
here's the method that this test is going to describe.
And does anybody know
what the hash convention is there before enroll?
Has anybody seen...
I see two hands.
Does anybody feel comfortable sharing with the group?
Yeah, in the back?
Yeah, yeah.
Yeah, so this is a way to like...
It doesn't necessarily mean...
It doesn't have to be an instance method
but like the connotation there
is that enroll is an instance method on the...
It's a method that you can call on an instance of a workshop
and the opposite, a class method would be connoted
with just a dot.
So if I was describing how we go about creating a workshop,
I might start it this way.
And I think this is really nice
because it calls out really clearly.
This is the method that we're testing.
Okay, so this is often what I see
in the code that I that I've worked on in the past.
Here's this described method maybe
and then here's what it does
and it just does what it says.
And that is not...
This is optimization for the writer of the code.
It's very easy just to say, I'm just gonna type in roles
but coming back in here,
this doesn't actually tell me anything as the reader.
So I wanna advocate really strongly
that we take advantage
of writing out clear,
clear descriptions in it blocks.
So enroll actually adds a participant to a workshop.
Okay.
Sometimes you'll have examples like this
where you have one test that says,
"Okay, it adds a participant to the workshop
when there's room and it doesn't add a participant
to a workshop when the workshop is full."
Okay,
RSpec gives us a tool to specifically design for this.
So when our app is in a certain state,
we can show that with context.
So whenever I see the word, 'when' in an it statement,
I immediately think
I should be using, potentially using a context block.
So here, instead of those two long it blocks,
we have these context blocks called out where...
When there's room in the workshop,
we're gonna add a participant.
And when the workshop is full,
it does not add a participant to the workshop.
Does anyone have any...
Some people have feelings about this.
(laughs)
Some people don't like this at all.
Some people...
I'm curious, do folks have thoughts on this?
Exactly. So, what the participant just said was that
you can get granular control over the variables
in each of those blocks.
So, if we're going,
if we're continuing to go through this workshop example
at the top, I can have my subject, my workshops subject
and then within each of these it blocks,
I'm calling the workshop.
But then within each context,
maybe I'm doing something to the workshop
that's slightly different.
And that becomes very apparent in the context block.
So I think you're dead on.
Okay, let's look at line 11.
I'm gonna pull this up
and get hub.
Okay.
So line 11 we have,
a test for and...
You don't have to know what any of this stuff is.
In fact, Sandy does her whole gilded rose talk,
not even knowing at all
what the point of the gilded rose is.
So the point here
is not to understand necessarily what a normal item is
or what sell date is
but is there a way that we could maybe make this test.
That starts online 11 a little bit cleaner
with some describe it and context box.
And I'm curious to see where people put,
how people break that up.
'Cause you can do it in a couple of ways.
So why don't we take four or five minutes.
Let's take five minutes to do that.
And then we'll see your folks' needs.
If folks are brave enough to share.
Okay.
Hope you had a second to look at this test.
Does anyone want to share out what they did to incorporate,
describe it and context into this test on line 11?
Okay.
How about...
Okay, great.
- [Woman] I don't need you to describe but (mumbles)
- Passed.
- [Woman] Through the context (mumbles)
- Cool.
So,
again, I'm guessing,
I don't know if you have looked closely at the code
but I liked that you,
tried to really explain like what it actually means.
What actually happens when someone ticks a gilded rose.
What does that even mean?
Okay. The quality does seem cool.
That's way more helpful than how it was before.
Okay, so now we're gonna get to the three-phase
test patterns.
This is kind of common.
This isn't just,
this isn't just our RSPec.
This is a lot has been written about this.
And the idea is that
your test is broken up into three parts, three phases.
The first phase is the arrange phase where you set up
exactly what you need and only what you need.
It's a good idea to only set up what you need
and not extra stuff
because everything that you set up takes time to set up.
And if you set up a ton of stuff that you don't need,
your test is gonna run slower than it ought to.
Okay, so let's go back to this enroll example.
I've got a workshop.
So I'm sending up a new workshop and I'm saying,
it's got 15 seats just like this one.
And we have a,
I'm setting up a new participant, right?
'Cause I wanna test that I can enroll a participant
in a workshop and the appropriate side effect happens.
I'm gonna set up another participant, Sandy.
Maybe Sandy is here.
Okay.
The point here though is that, actually,
if you came across this test
as like an experienced developer,
you would look at this and assume it would be a totally
reasonable assumption to make that you need to have two
participants in order to, for enroll to work properly,
right?
When you're writing the, when you're,
when you are doing your setup,
anything that you set up,
the next reader is gonna come in and assume
that it's important, that it's there.
In this case, we actually only, to really test enroll
we only need one participant,
and putting this extra participant in confuses things.
It also potentially makes it a little bit slower.
Okay.
So now let's talk about how we can set the,
how we can actually pull this up until let statements.
So RSpec has this really nice...
I like this.
RSpec has this
way to declare variables within your example group
called, let.
So I can say, let workshop
and set this equal to a new workshop with 15 seats.
There's also let bang,
is anyone familiar with let bang?
A lot of folks, does anybody wanna be brave and,
and share someone who hasn't talked yet?
- [Man] It's a (inaudible)
- Right.
So let, so let is nice cause it's lazily loaded.
You could put let in a context block
or describe block and it won't actually run
until you call workshop.
Let bang will always run.
So let bang is actually equivalent to saying before,
right before all of these tests run,
create a workshop with 15 seats.
The difference between, the reason you would do before
versus let bang is with let bang you can just,
you can add a variable name.
You can reference workshop here.
With before, I'm not gonna be able
to reference that workshop.
- [Man] (indistinct)
- Say that again.
- [Man] Is before lazy?
- Before is not lazy.
So this, this will happen before if,
the before will happen in whatever block con you know,
depth of, of, of block it's in
in the example group and it'll run before each it
example in that example block,
not lazily.
So before is good like if you need to,
before is good if you need to set some state
that you're not referencing in the, in the test itself.
So if I needed to have a workshop in the background
that had 15 seats for like my whole conference to run in.
I was testing the conference, I might do it this way.
Okay.
So, kinda going back to,
to our example,
to run this enroll test,
I'm gonna have a workshop with 15 seats
and one participant.
I'm gonna skip this actually.
I wanna do this altogether.
So, the next step once we've set up the things that we need
is to do the act and assert so.
With three phases of test set up or arrange act,
and then assert.
So,
here's an example of a test that doesn't really follow
that arrange act assert paradigm.
In this case, we have some arrangement,
some assertion, right?
That first expect is an assert.
And then we have some more stuff like an act and then more
assertions.
And I see this all the time in the spec files that I,
that I open up.
The be a test that's like 50 lines long
or a hundred lines long.
And it starts with some setup and then they assert
some things and then there's some more setup,
and then they assert some more things.
And that to me is, is super confusing.
Cause it's hard to understand, like, what is actually,
what is this test? Like, what's the point of this test?
So here, if we're testing that it,
that we can enroll folks in the workshop when there is room,
then we don't really need that first
assertion there.
We need to just get rid of that
and,
we can,
demonstrate how we're gonna do that enrolling by calling it.
And then we're gonna make assertions on the,
out on what comes on,
on the state change that we get
from enrolling a participant.
So this is, I think, better than what we had before, right?
We had
this going in, in crazy order of setup
or a range,
assert
action act
and then assert again.
And we're, now we're following that pattern of setup act
assert.
I think that there's actually maybe one more step,
which is to get rid of extraneous assertions.
So
if,
if the first line there,
the first expectation fails, right?
If the,
workshop participants dot M,
dot empty actually equals true,
then the next one will also fail and vice versa.
So it's like these are duplicative tests.
They don't really add a ton of value.
So I'm gonna just try to keep it simple.
And I'm not trying to say that you should only have one
assert in every single one of your tests.
Some people say that, but I think you should have,
you should have only meaningful asserts in your tests.
Okay.
So just to summarize this,
we have a three-phase test pattern
where we set some stuff up.
We act on the thing and then we make an assertion.
And what I'd like everybody to do,
is
I'm gonna throw this up
line 11 of the gilded rose spec.
See if you can refactor that test
to follow those three phases.
So set up, act and then assert.
It's not following that right now.
I'll put that up and let's take,
we'll take five minutes
and we'll see if someone can be brave
and share what they do.
So our goal was to follow the three phase test pattern
of setting up,
acting and then asserting.
What feedback or what, what,
what did you change on this test
that I can, I can throw up here.
Cool. Send that gets rid of,
sort of an extra assert.
Does anyone add any context or describe here?
- [Man] Yes.
- Couple of folks,
does anybody want to share what they did?
- [Woman] (inaudible)
- So something like this?
- [Woman] Yeah.
- Did anybody...
So the,
this purchasement just said,
context based on sell date,
before sell date, after sell date.
Did anybody find like a different hinge point to,
to set their context on.
- [Man] Quality?
The quality?
- [Woman] The quality.
- [Man] Quality.
- Quality.
Can you say more about that?
- [Man] So there is one case where the quality is greater
than zero,
and then there's a case where quality is zero.
- Oh, I see.
Okay. So cell date is...
Okay, I see.
So given, given a starting quality of something,
yeah, got it.
Makes perfect sense.
Another thing that I was thinking of
is we have these like normal items here
and later on, we have some aged Brie items
and some sulfurous items.
I don't know what these are,
but that would be another kind of facet to, to switch on.
So we could say right context when given a normal item,
it,
you know, does X, Y, and Z on cell date or whatever,
let's go, we've got two questions or comments.
Further in the back.
Yeah.
So the question was, is it recommended to nest context?
So we can have a context for cell date,
a context for quality,
a context for this type like normal item or age sulfurous.
So my opinion would be no.
The reason I would say no
is because it becomes a little tricky to,
in a longer text file, in a longer spec file.
It becomes a little tricky to keep track of what variables
are available in each of those kind of like sub contexts.
I think in a shorter file, it makes, it makes sense.
But for me, I try to just limit it to one,
one context block,
deep. (laughs)
I think there was another question.
Yeah.
So the question is what's the difference between,
doing let bang gilded rose versus just let gilded roses?
Is that what you're saying?
- [Woman] No, the difference between using a let bang
versus not using let at all.
- Oh, I see.
So what, so, okay.
So I could do,
let bang gilded rose,
but this is basically, you're saying this is basically
the same thing as G R equals gilded, gilded rose.
- [Woman] Yeah.
Yeah. Once (inaudible)
- Yeah.
So that's a good question.
I think the advantage is one of readability and style.
So if you, do it the way that, that you just described here,
it becomes more of like a, this is kind of what like,
mini tests I think looks like.
I think that the let is, I guess,
for me, what I, what I,
for me, what I like about this
is it forces me to be intentional about setup, act, assert.
And if I have basically a separate space
where I'm setting stuff up,
that helps me personally.
But I, I think, you know, when you open up like a mini test
type a mini test file,
it often looks more like this.
You know, you don't have that, that let keyword,
unless you're using a special version mini tests.
Were there other, other questions or comments in the back.
- [Man] I think let is also memorize.
- Say that, say that again.
- [Man] I think let is also memorize.
- Correct.
So if you call it more than,
you're saying, if you, if you call it more than once
it's me...
Gentlemen at the back said that let is also memorize.
So if you call it
more than once, it's not going to redo code,
it just saves whatever was in the block.
- [Man] (inaudible)
- Right.
Yeah. I wish I wish I was an expert on that,
but,
(laughs)
Did you ever comment or question?
- [Man] Yeah, sure.
I guess there are different camps sign,
whether or not you should use let,
and I'm not on one side or the other.
Some people say you shouldn't use let at all
because, because of the mystery guest thing,
and it can be overdone.
Other people, like, like use let responsibly.
Even if you don't use let you can,
use a blank line to separate your arrange act and assert.
And that works really well too.
Oh,
one of the advantages of that is that it's all together
in the context of what you're looking at.
So when you're looking at a spec,
you can tell what's going on right there.
You don't have to start hunting around the file
and then using let allows for you to reuse
whatever you're setting up
instead of having to copy it over and over again.
So you can just use your judgment as you write specs.
- Right.
So I agree with everything you said.
I think the,
the key here is like,
you wanna have everything that you need to understand
the test right in front of you.
So if my let is for gilded roses,
20 lines up over here,
that's gonna be a little,
that's gonna add to the complexity of reading.
And in that case,
it would make more sense to just keep it up,
keep it all in that, in that type block.
Cool.
So we got two more things that I wanna talk about.
So we're running exactly on time.
Exactly how I planned it.
So, so we've got two more to do.
We'll probably end a little bit early,
and then we can really hash out if we should be using let
or not.
And, and, and kind of two, two people enter
one person leaves kind of situation.
So,
okay.
So the thing I want to talk about next is shared examples.
Raise your hand if you have seen this feature in the wild.
Some folks, a lot of folks.
Okay, cool.
All right, maybe you've seen shared context perhaps.
So similar idea.
I, I'm guessing I'm guessing most of you all will,
who are familiar with this will guess where I fall on,
on using shared examples and shared context
based on how things have gone so far,
but here, here's basically how it works,
for those who haven't seen this.
You define a shared example
by saying,
okay, I've got this, this workshop
and it's gonna take,
take in this capacity.
And then I'm in are, have this typical looking test.
But what this gives you is that you can call
this test many times with different capacities, right?
You can pass it in capacity and suddenly
writing your test looks like this.
You just say it behaves like a workshop and I pass it 15,
or it behaves like a workshop and I pass it 20 for capacity.
Okay.
So this is nice in the sense that if I,
if, if changing the capacity of my workshop dramatic
is something really important for me to test.
I can just have lots of lines like this, that,
that kinda do that. And that's easy to copy,
but that's an optimization for the writer of the test,
right?
That's that makes it faster for me to construct the test,
but it's definitely not a optimization for the reader.
And when these things fail,
the messaging that gets spit out by RSpec,
I find particularly opaque.
And so my recommendation is to never use shared examples
or shared context.
And I'll caveat that a little bit.
So on the one hand,
I think that, you know, obviously shared examples,
I think limit the re,
impact the readability of your spec file
in a, in a negative way.
And I think for that alone means that there's basically
nobody should be using that.
However, there are, maybe you're on a team
that documentation is like through the roof.
Maybe your team is like the A team of organization.
Maybe if you exist on that team
where there's like a really deep shared understanding
of what these tests are for and,
and, and what, what is kind of shared between them,
then maybe I can see using them,
but really,
I don't think there's a ton of reason to use them.
Sometimes you might have like a concern
where you have
like a, like a shared behavior between models
and your rails app,
or some shared module,
some module that you're including in a class
that shares behavior.
In those cases,
maybe you want to have some shared examples to,
that you can just say like, oh, it behaves like a workshop,
but really I think the reader, like,
what are you really gaining
from this?
I, I don't think you're gaining a ton of speed
in terms of how long it takes to write this.
And I think you're losing a lot of readability.
So let's
kind of refactor this really quick.
So instead of saying shared example, workshop capacity,
blah, blah, blah, blah.
I'm just proposing that we write this like a normal test.
And if we need to test multiple capacity,
multiple capacities,
we would have a second test under here,
that made it clear what was happening.
Okay.
So now I set up.
Okay.
So in the wild,
what I, I, I think, what this ends up looking like
is you have a bunch of it behaves like,
and then somewhere in the file,
you have some shared example that, that,
that is very tough to sometimes track down.
And sometimes these don't even need to be in the same files.
So that I think also makes it particularly difficult.
So I'd ask folks to try to do for the next five minutes
is see if you can,
scratch the itch and get rid of this
it behaves like shared context, rewrite the test,
and then we'll come back together in about five minutes.
Okay. So you've come into skilled rose spec file.
You found these shared examples.
You heard in a workshop at Ruby Confed,
some random guy doesn't like shared examples.
What did you do?
- [Steve] I probably went a little too context heavy,
but the first thing I did was I broke out a new context
based on the fact that the name was different,
because before we were testing
that the name was quote normal.
So I created a context
that said when the name is non normal.
Being that I didn't look too deeply into the method
being tested to know like specifically
the underlying function here.
And then I broke into three separate contexts.
When I said when the days remaining is positive.
And then I had one for when the days remaining is zero.
And then for when the days remaining is negative.
And then I felt a little stuck,
not stuck because I think again, I would go context crazy,
cause I would,
I would want to add as many descriptors to break apart
those separate behaves like,
and I didn't wanna have so many contexts
that you would get mad and yell at me.
So I stopped there.
(laughs)
- Steve. You're a very good student.
(laughs)
Thank you.
I think that makes a,
I think that makes a ton of sense in general.
Like,
to me this is really, I mean, you would never do,
you would never actually do this and maybe some of you would
do this in real life.
But I think that, I think that breaking it out,
putting, being very descriptive, you know,
when the days are positive, when they're right,
because otherwise it's like, what is this five?
What is this 10? What is this minus one?
So I, I a hundred percent agree there.
Cool.
Alright. We're gonna talk about our last topic,
which is test doubles.
Did anybody go to any of the other testing talks
earlier today?
Cool. What did they, what,
what were your big takeaways about,
I think there was one about,
specifically about test doubles about mocking.
Is that right?
- [Woman] (inaudible)
- I, I missed that one.
What was the big, what would you say the big takeaway was?
Okay. I have to watch this talk.(laughs)
This is great. So, okay.
I think, okay.
So for me, the, the,
the thing I think about is Justin Searls,
when it comes to test level.
So,
I'm guessing he's not in here.
Okay, good. (laughs)
Then I feel better.
So Justin runs a company called Test Double,
and he gave a talk called Breaking up with your test suite.
Has anybody seen that talk?
One person.
Okay, cool.
Everybody should watch this talk.
In it, he describes how he goes about using test doubles.
And I'm gonna,
I'm gonna just kind of try to summarize
this point about how do you use test doubles judiciously.
So he says the principle value of TDD
in this model,
and I'll explain what he's talking about there,
is discovery.
And so he's trying to describe like this,
method of TDD called, top-down or discovery,
discovery testing.
And the idea is that you're doing TDD in this model
to discover like how your code should be built,
to discover what you need.
And he goes on to say, discovery of tiny, boring,
consistent units that break big scary problems down
into small manageable ones,
is really like the utility of, of test driven development.
And to do this,
he makes heavy use of test devils of mocks and stubs,
right?
And then he gives some great examples where he has this huge
big domain that he can then kinda use test driven
development to break down into smaller components
and then solve that big problem.
So,
so all that is to say
that I agree,
(giggles)
that if you practice test driven development,
where you're doing kind of this top-down,
discovery driven development.
I think that that is a good use of,
of test doubles.
There's times where you should not use test doubles so
the, the key example here is going back
to that object under test idea.
If you ever see the object under test,
the class being described,
getting mocked or stubbed or having some behavior
or kinda asserted upon it,
I would say that that's a pretty, that's a big red flag.
And I see, I actually see this a lot.
I see folks saying the thing I'm testing should,
produce this response or whatever.
Let me, let me take a quick step back.
Can I get a show of hands,
who's familiar with the idea of a test double?
I don't wanna go too far down this.
Okay.
Does anybody want to give like a,
high level this is what a test double is
before I keep going?
- [Man] It's a stand in for an actual object.
- Stand in for the actual object, right?
And it comes from the,
like the stunt double in Hollywood.
That it's a stand in for the actual actor.
And you can basically say, you know, this,
the stand-in should be doing these things.
So in this case, in this example,
I've got the workshop and I expect it to receive notified.
I expect it to have notified called on it.
And then I run my test and I say,
workshop dot enroll participant.
And this would fail if,
when I enroll a participant
workshop dot notify is not subsequently called,
and it would pass if,
when I enroll at participant workshop dot notify is called.
So this is a big problem for me.
Does anybody wanna maybe venture a guess
of why this is not a great way to use test doubles?
- [Man] It prevents the actual method from being called.
- Right. Right.
It prevents the actual method from, from being called.
So that's, that's a problem.
But what if like notify was actually like,
calling your Twilio API to actually send a text message
or something?
- [Man] (inaudible)
- Yeah.
- [Man] (inaudible)
- Right.
So,
so one, one thing that you can do,
and I guess this gets to like
this idea of discovery testing.
It's like, if I need to test
that when I enroll a participant,
this notification is gonna happen.
This is like, I violated this idea that I'm,
using the sub,
the object under tests as a test double.
And this kinda raises a flag for me that maybe I'm like,
architecting is not correctly.
Maybe I'm designing this incorrectly.
Maybe I need something else that I can either use as a mock
that I could use as a mock,
as opposed to
adding this behavior or stuffing this behavior
on the thing that I'm actually testing.
So in our RSpec, you can do this expect any instance of,
and I can say, cool.
I've just discovered that I need this other thing,
not just my workshop, but I need this other thing.
Maybe it's called a messenger and I expect any incidents
of messenger to receive notify.
And then I call my code,
and if there isn't something on the implementation side
call the messenger, that's an error.
That is an instance of a messenger.
If that instance of messenger doesn't receive notify,
this test is gonna blow up and gonna tell me
that when I enroll a participant in my workshop,
I'm not getting the expected behavior.
So this is one way to do it.
Does anyone have any problems with this, this,
this way of doing it?
- [Man] (indistinct) implementation
(inaudible)
- (laughs)
So, so,
it is an implementation detail,
but what if like,
I think I'm trying to remember who it was,
but some, someone at the very beginning
was saying that,
the reason that we test is to make sure
that the business need is being met by the code.
And if my product person is like,
"Hey, Jesse, when you enroll someone in your workshop,
like they need to get a text message.
And if they don't our business fails, right?"
So in that case, you...
It isn't just an implementation detail.
Yes, okay.
So you're saying that I'm being to...
In this case, we're being too prescriptive about...
Tell me and if I'm not getting this right...
So I think that the thing that I would add to that
is I'm actually not a huge fan
of the expect any instance of,
I think it also suggests that there's like a design change
that you can make in the code, right?
If you find yourself reaching for this, any instance,
there's a danger there that maybe somewhere else,
like randomly, like in a really large code base
there's some other thing down the line,
some other instance of messenger that gets called note,
that we call notify element
and we could end up with a kind of a false passing test.
That's a problem.
So the design challenge
or the design that this could suggest is like a scary word,
like dependency injection.
Like,
can I somehow give my workshop
a messenger or can I like somehow stub that out?
So let's look at what,
another way to do that.
So,
another thing that I can do is say,
I have a messenger and I can set it equal to a double,
which just means it's like a stunt double.
It's a fake thing right now.
And when I have...
Oops!
When I have my messenger class,
at some point in the implementation,
I'm gonna expect it to receive new.
I'm gonna expect there to be a new instance of a messenger,
and I'm gonna expect it to receive that message new
with a participant.
And we can imagine that like further up
we define the participant and said,
purchase pin equals participant done new,
with Jessie.
And I'm going to expect that, that new,
that initialization of a messenger returns a messenger.
So I'm like kind of overriding that behavior of,
you know,
that standard behavior of when you initialize a messenger,
you get this instance.
Cause I wanna say,
I want,
I want it to actually give me this double at the top.
And then I can say that the messenger,
I expect the messenger double to receive that notify method.
So I think that,
I think that this is a lot better than,
this expect any instance of,
because in this case I could have some...
This test could pass accidentally.
Whereas this type, this,
this could probably would not test accidentally.
Another way to do this would be to actually like,
initialize a messenger here and pass that into our,
our workshop class
but that's, that's another, another conversation.
To folks.
How, how do folks feel
about the difference between expect any instance of
versus like actually using a mock there?
See a couple hands in the back.
So when we go,
first person who hasn't spoken as much.
- [Woman] Oh, I was just giving you the thumbs up.
- Oh, you're giving me the thumbs up.
Okay.
Then the, then my friend in the back.
So, okay.
So I think, I, I think I see what you're saying,
but please, please stop me
if, if, if this is not what you're saying.
I'm still too tied to the implementation
of having a new upper messenger here.
Is that_
- [Man] (indistinct)
- And in your opinion,
you would prefer to see
that like a private method is invoked on the workshop.
Okay so,
would you,
would you agree or...
Would you per,
agree or disagree that we should be testing
whether or not the workshop be even notifies.
So...
okay, maybe we're getting tied down in the, in the,
yeah.
In the individual, like imaginary code in both of our heads,
(laughs)
but in my,
I think I agree with you
because I think that's what I'm trying to do here,
where the, you know,
there's something that needs to be notified, right?
And whether or not it's a mess,
it's a separate messenger class or notification class
or whatever.
Whether or not that's initialized inside of workshop
or passed into workshop.
I sort of don't...
I mean that's like, kind of besides the point.
I guess the goal here is just to say,
this is very bad.
We don't wanna,
we don't wanna,
take the object that we're actually testing workshop
and stub behavior on it.
But if there is like a business critical message
that we want it, we wanna assert that as being sent,
this is one way to do it.
So, so maybe we can agree to disagree or agree to agree,
but I appreciate the, the, the participation there.
Okay.
So for the folks that all the way in the back
who maybe couldn't hear that,
you could get some brittleness here
and the brittleness is that if,
there isn't actually a notify method defined
on, on messenger, but you're stubbing it,
you could, you might, your test might pass,
but the code actually might not work.
Yeah, absolutely.
So that, that, that, that is why
we use test doubles judiciously because,
they're, they can lead to some brittleness.
They can lead to a testing implementation detail
that you actually don't care about,
and they can be too prescriptive.
So really, unless there's a very good reason.
And we're gonna go refer to the talks earlier today
to find out what those very good reasons are.
I would just say test,
use test doubles judiciously,
and watch Justin Searls talk.
Cool.
So we're a little bit early, but I think that that's okay.
I wanna just say how grateful I am to all of you all
for coming to this.
I hope it was okay.
You know, this is my family
we're, we're located in Denver.
You can follow me on Twitter at Planet Efficacy.
If anybody wants to talk about the gilded rose kata,
or Sandy, you know, fan out about Sandy or Justin,
or just talk about testing in general,
I'll be here for the next like 30 minutes or so.
And then you can find me probably having a beer
and yeah.
Thank you all so much for, for doing this with me.
I appreciate it. Especially the participation.
So thank you.
(audience applauding)