Tuesday, October 11, 2016

Rails Conf 2013 The Magic Tricks of Testing by Sandi Metz

k starts
right so I don't use my allotted time for this so please feel free to talk
about yourself
I'm gonna ask you a question and ask for an answer by show of hands and so this
is what the question is going to be what percent ok and it's not about your code
so you don't have a conflict of interest you're not going out yourself here in
front of her teammates the question is what percent of the code that you see is
tested at some person somewhere is is the code that she tested at a hundred
percent eighty percent 60-percent 40-percent 20-percent 0% write something
I want you to tell me about the code that you see how well it's tested so if
the code that you see is a hundred percent tested raise your hand
you are lucky people alright a person if the co DC is eighty percent tested all
right 6040 20-0
so happy if you're if you were in the fifty percent or less category raise
your hand
alright so now the talk starts we are the people who embrace testing where the
clan that drink the kool lead this is our public persona but you don't have to
look around the room very far to see that many people perfectly legitimate
completely respectable card-carrying members of our community say this about
their test
I hate my test some of your saying this i hate me test so why is it that you
hate your test where you hate them because they kill your productivity
they're slow
off we go to Twitter who knows when we'll be back
slow test kill us
you hate them because you can make a little change in your app and the
absolute works it's perfectly fine but it broke all the tests and they have to
go fix all those tests before you can come back and get real work done
it's frustrating this feels like such a waste of time if your tests are slow and
they break every time you change something
it is rational to begin to wonder if they're worth it
everyone says you should have them and they wait they probably save you just
often enough so that you're afraid to delete them all but the cold hard truth
is they make you miserable if this is where you are right now you are not
alone for many people the promise of testing has not been fulfilled
unfortunately it doesn't have to be this way you can have cost effective test
good tests aren't magic their magic tricks and since the tricks you can
learn them there's just a few tricks they're simple and straightforward
they're not obvious but they are simple and I'm gonna tell you these tricks in
the next 20 minutes and foremost of either get involved some form of every
programmers favorite thing leading tests i think you have too many tests and
you're testing the wrong sorts of things or at least you did back when you wrote
test you are too many tests and that was so painful that now that you now you
write too few or none at all
so today's talk is about unit test the magic tricks applied unit test this talk
is not about integration tests so integration tests are into interesting
right there's a big cloud I know you cannot i feel like i needed to important
in there is a big cloud that your app and integration tests like you poke one
side and a bunch of objects and messages go and then the answer some change comes
out the other and the
duration test does the Pope and checks the distance side-effect unit tests are
just the opposite
they narrow the focus down until the entire universe is a single object and
that object is all unit test news about so adamant at a molecular level your
unit test because it proves that every cell behaves correctly and the
integration test is proof that the Beast is alive so so this is what we want a
unit test we want them to be thorough we want them to prove logically and
completely that that single object under test is behaving correctly we want them
to be stable
we don't want to test a break every time you change an implementation detail in
the code we'd like them to be fast because we know what happens when they
are not and we want them to be few we want the code that we write for tests to
be the most parsimonious expression of the proof we don't want extra lines of
unnecessary tests code that we have to maintain so thorough stable faster this
is what we're looking for in our unit test achieving this finding this takes a
sort of clarity vision about your application so do a thought experiment
for me
close your eyes if you have to so imagine an app that you're working on
right now and construct a picture of it in your mind like the not the files on
disk with the running app in memory the objects and the messages that are
passing between like draw a picture in your head of that pattern
what does it look like yeah spaghetti
for many of us it looks like this are applications go up into these
complicated tangles of code and when our apps feel like this it's no wonder that
test escape our control we don't understand what's going on and the way
out of this ticket is to follow the message and the magic tricks i'm about
to show you the focus on messages
unfortunately the objects that were going to unit test they have a really
simple view of messages objects are black boxes they're like space capsules
they have an inside and an outside and they are very motivated to keep these
those two things apart the cold dark lonely outside is dangerous and it's a
matter of life and death to maintain a clear separation between outside and in
so for the point of view the object under test it only has a few things
about messages
it doesn't matter how many you have or how complicated they are every message
it knows about comes from one of three places the object under test receives
messages from others
these are incoming messages these incoming messages blast holes in the
container all the space capsule you have to be really careful and not let the
outside CN next it sends messages their outgoing messages again holes in the
containment you can't let the inside see out the third place where message
originated is the the object under test sends them to itself
now these messages are completely contained within the space capsule
they're invisible to anything on the outside so here's what we have these are
the three origins of messages messages from the point of view of the object
under test and then these messages they come in two flavors types they can be
queries or commands clear message you run a recorded message doesn't have side
effects so if you were to send plus to an integer a calculation is often a
really good example query message right you care about the thing you get back
but it doesn't change anything that any other part of your app can see in
contrast communities are really just the opposite a command message has a side
but but returns nothing that you depend on so sending saved to a subclass of
active record would be command message right in the database and all the other
parts of your app can see that every program here has probably been burned by
a message that was both a query and command you go to code base you don't
know very well they tell you to make a change you go find a likely message that
gives you the results that you want you use that message in your code you deploy
to production and then later you overhear someone wondering why you just
said 10,000 emails right like I expect this hidden side effects are the beam of
programmers now so what is it
however is it was just we do this all the time we have to complete command and
query the perfect example is popping something off a queue it's really hard
to think of a way to separate those I when I say pop I get the thing back
right that's the clearly part and it changes the queue so that other parts of
my app they look at the q they see a difference that's the coming in part so
it isn't evil to conflate commanding queries and we do it all the time in a
movie especially since there's always an automatic return which makes it easy to
chain messages together right that that's going to happen in our code bases
however these things get tested differently and so it's really important
to understand if you have el command or a query or both and so now that we have
those the three kinds of origins of messages and the two types we can make
this grid there are six cells on the spirit and every cell has a different
rule about how to test it
the point of this talk is to fill out this grid so we can start looking at
code now and I'm really sorry about you people way in the distance that you feel
free to stand up and walk forward if there's something you can't see if you
want to see the code samples the code samples that were about to look at I
made up their simple and really straightforward and conveniently enough
they exactly proved my point
there's a if you're interested in this Katrina and who may know from her
therapy refactoring talked last fall or her voice on the Ruby Rose she's gonna
talk to spring called zero confidence where she takes these rules and she
applies them to the test suite of an open source project on github
it's a cliffhanger as you might imagine so if you're interested in this have a
keep an eye out for her talk so filling out the grid
the first one is incoming query messages so we of course have a bicycle his wheel
there's a diameter message it's that it's implemented just like this so
diameter is a query it changes nothing but does the calculation returns a
result the way we the way we're gonna test it is like this we're gonna get a
new instance of the object and make an assertion about the value that sent back
when we send the message now I know that many of you and your have never seen
many tests it does exactly what you think okay
the only part of this it might be a little confusing is this assertion the
in-depth this is just an assertion about a value the in Delta parts that this is
the terms of float and so this is like it's within . a one of being 29 so
that's a really straightforward assertion just about a value just like
you're used to if you use our spec or whatever that other framework is this is
that is this is not an endorsement of the testing framework so it's a simple
mapping it has an incredibly simple test and that's all there is to it we have
the first rule test incoming query message by making assertions about what
they sent back about the result some people will say about state now I'm not
an academic so I'm always really confused about what they mean every time
I hear the word state i think North Carolina and so if I mean feel free to
use that worth it has meaning to you but don't worry about it if it doesn't make
sense we're talking about what gets sent back here to a check to desertion about
a value and so there you go
one down five to go but of course there's one more i'm going to just do
one more incoming career message before we move on across that chart here is a
gear class at the reference bicycle gear and implement this gear inches method
which if your cycles you know what that is and if you're not
you care so here's the implementation of the method says a private message to
itself and it sends a message to collaborating object this is a query
message there are no side effects nothing changes as a result of this
how should we test this it turns out the point of view the point of view to adopt
to answer that question is to think about sighting along the edges of the
space capsule if you're standing right there
all you can see is the messages and parameters as they come in and the
results as they go back out so if you stay in there the test that you write
looks just like this it looks exactly like the test before it sends the
message and it makes an assertion about the value that gets returned as a result
of that message sent
I can't see inside the space capsule so I don't care how this gets done
I just care about what comes back we want to test the interface not the
implementation if I test only the interface it means i can change the
implementation without breaking the test and that's our goal for testing so and
that's it for incoming query messages make assertions about with a return test
the interface and not the implementation alright moving on incoming command
messages gear also has the set cog message this is how when players change
gears on their bike
they'll send sitcom it takes an argument it is signed the that the value that
argument to this variable and there's an adder reader on it now don't worry that
there's that kind of looks like you could have second could have been added
accessory but I'm gonna make that method bigger later so bear with me on the fact
that I don't get hung up on the fact that there could be more in there so in
this case the this is a query command combo it returns the result here that's
the query part and it sets this value so that when other people sin called later
they see a different thing right I'm going to test the command part of this
and this test again is incredibly simple i'm going to get into gear
I'm gonna send the message that causes the side effect and then I want to make
an assertion about the bow
of the side effect the wall here
test incoming command messages by making assertions about direct public side
effects now public side effects that it's pretty clear what the public side
effect is direct i'm defining direct right now as it is the responsibility of
the last Ruby class involved and I'm just gonna ask you to live with that
idea for a little bit and we can talk about it more later on and so now that
we're here I you know here wait i love my you last time I did that someone
pulled one out of the audience and shine it on board this is not an invitation
so now that we feel that this first row and we're making assertions about values
i can tell you that we've reached the point where i can reveal a magical
secret we're done making assertions about values
there's no place else in this grid where we're going to test the value returned
by anything so incoming messages
it is the responsibility when your unit testing the receiver the incoming
message is the place where you make assertions about values you do that here
and nowhere else and it will become more clear why as you see the other examples
and so that's it for Row 1 wherein we're onto messages since itself the guarantee
is a method send a private message there's this ratio private method that
we're using how should be tested when you're all here so you probably know a
better question is how not to test it so some people are tempted to create an
explicit test for it to send the message and making a story about what comes back
think about this from sighting along the edges of the space capsule point of view
as far as the rest of your app is concerned this method does not exist if
the gear inches method of a public method in your public interface that
calls it if the test for guarantees is correct
this must be right if you this test this test is redundant it doesn't add any
it binds you to what you have now that while it is true it is true that
redundancy is sometimes something you might want to keep how cute are they not
in your test
alright so now let's look at the second Pat second any pattern for private test
so some people say okay let me say this
the people who who do these things who are they they're people who care right
the people who don't care or not writing these checks so this is not a criticism
this is an attempt to like shed some clarity on this issue so that the people
who care can do things that are less painful and more useful until I have
this private method here's what people say they say watch send it in the
guarantees method if i don't if I don't send it your just can't possibly be
right and so they take this perfectly good test that is complete proof that
it's okay and they do this you see it all the time they set an expectation
that an outgoing query message will that answer they set an expectation that a
private method will get sent this expectation is an over specification it
adds cost and provides absolutely no benefit it binds you to the current
implementation a test that insist that you continue to send an invisible
message creates a world where you can't improve the code without breaking the
test this expectation does not make it safe to refactor it makes it impossible
to do so so don't do this and the rules for private methods are don't Testament
don't make assertions about their values don't set expectations that you'll send
them now as we all know we live in a practical world and all rules are meant
to be broken
I break this role of time if you have a complicated private algorithm you may
not be able to get it running unless utd it if you're if you're dinking around
with a complicated private algorithm it can be really helpful to get an error
message near the offending line of code however what I would tell you is while
these tests might have value early on as time passes what they do is they keep
other people from being willing to improve your private code because they
do not want to screw with those tests when i leave when after i write tests
and private methods very often i just can't bear to delete them at that point
in time they're like my children right like I'm really attached to him and so
if i leave tested private methods in a test I segregate them i put them all
together in one place and then I compound my sins by putting a comment in
my tests and that comment says this if these tests fail delete them
all right there is a point if you keep a mirror on it's a slippery slope and it's
gonna be going to switch to wear instead of saving you money they're going to
cost you money and you should proactively delete those tests before
that happens don't let your private test keep people from improving your code
because they don't want to deal with those tests and so that's it for
messages sent yourself the rule the adult role is ignore them and the
practical rules do what saves you money and so now part 3 outgoing query
messages so first spoiler alert
so I'm going to shoot so one of the outgoing career message cell i'm going
to show you
we're going to derive rules that are just exactly like the rules we just
drive and i'm gonna show you examples that are going to give you deja vu
because they're gonna look exactly like the examples you saw so I'm gonna whip
through and then we're going to talk about why these things are the scene so
we've already looked at these two things these are both query messages to these
objects were testing gear so we're staying at gears point-of-view gear
sends has a collaborating object that's wheel so it sends this it since we the
diner to the wheel object and so as is always true here somebody's incoming is
somebody else's outgoing so if I'm standing over here my universe is the
universe where I'm aqua unit testing year that diner message to wheel is an
outgoing query message so how should we test this
well again the better question is how not to test it here are perfectly
contest that is complete proof that the guarantees method works correctly
sometimes people who cannot forget what they've seen inside the space capsule
feel honorbound to do this to make an assertion about the value that's
returned as a result of sending the outgoing query message now remember the
receiver of an incoming query is solely responsible for assertions that involves
state this test is redundant it duplicate a test that it's the exact
test that's already over in wheel and when you make this assertion hear you
you made it so that you cannot change the implementation without breaking the
test over and wheel
and then breaking the testing all of the places where you've also done this it is
unnecessary as proof of correctness of the result of gear inches again there
are times when you might want to keep your identity
I don't know even nobody ever really laughs at that picture and I don't
understand it I is it the shock you have my permission to laugh if you want it
yeah this dogs are their kind of alarming I agree it it's an anti-pattern
to do right to keep that reduce written to duplicate that test in the outgoing
countryside so here's another any pattern sometimes again people of good
will say well I surely have to send it
surely I must say that or it won't be correct this again is an over
specification it proves nothing
all it does is bind you to the current implementation and make it impossible to
change the code without breaking the test and so that's it for outgoing query
messages don't test them don't make assertions about what they returned it
don't set expectations that you'll send them now so these are just like messages
sent to self and the reason is that messages that objects the unit test
objects into itself and outgoing query messages messages that don't have side
effects it sends to other objects they both are without side effects so from
the singing side if you're sending a message that doesn't have side effects
it is invisible to the rest of your app and if if the message is invisible to
the rest of your app that on the sending side you should not test it that test is
a waste of time it's an over specification adds new proof and so
that's it for outgoing clear messages just the norm and so well that's that's
looking promising and then not much code yet alright so the last cell on the grid
outgoing command messages and in order to illustrate this I have to change my
example little bit you've already seen this stuff on the top the change here
like assume now that this is a game where players race bicycles and when
players change gears that has to bubble up some houses the game can change the
behavior of the bicycles a whole so the set cog message gets sent to gear and
when that happens i have this new sort of pseudo observer object and it's going
to have to get to sit change that tell the rest of the app points out that
it was changed and I'll just I'm just gonna make some fairly arbitrary changes
in the code i'm gonna inject the thing i'm calling observer it's not really a
strict observer pattern but it's gonna watch and then in the second method i'll
just fix it I'll just since it's gonna send change to itself and changes
implemented this way it's going to send the chainring in the COG out to the
observer message and so here's a picture of what just happened changed has side
effects and what I'm not gonna test that but just trust me that it does and let's
assume for the sake of this example that it rates in the database so if observer
gets changed its gonna do some saving the database that will be visible to
other parts of the app so to the to the observer changed is an incoming command
message and so of course it's an outgoing command from the object that
we're testing gear and because it's an outgoing command message we have
something new
this message has to get sent
unlike with our inquiry messages we have to send this message or the app will not
be correct it creates side effects upon which others depend and so it can be
really tempting here to test the side effect you can write code the trigger
the outgoing message and it makes assertions about what happens when you
ok so that think about what's going on here i am asserting that when i send
some message to some other object that it sends me messages to other objects
that eventually does something and my test is going to bind me to the thing
that happens this this create a dependency between you and every object
and message between you and that distance side effect and it might be a
long way away changing the database is not gears responsibility it should not
even know that that's happening reaching across a bunch of intermediate objects
and testing a distance side effect is an integration tests and this is an
integration test is just hiding in your unit test directory gears job is not to
do this thing it's just send this message and testing the message got sent
requires using mock you make a mock
you check the mock you set an expectation that you send message you
trigger the event and then in many tests there's a separate verify step that
makes them long ago this test does not depend on all the objects and messages
between you and the distance side effect this test depends on the message it
tests the thing for which gears responsible
it tests at the nearest edge now code that can tolerate change couples to
stability and testing testing in this way places a bet
the bet is that the API the nearest edge that message that you're sending is more
stable than the path then the distance side effect and the path to it so if you
test this with a mock you get fast and stable for free
you don't have to run all that code between you and the side effect and I'm
fully aware that i just used the words mock and stable in the same sentence
right and I I understand how you might have an objection but hang in there all
right so here's the rule test outgoing command messages by setting expectations
on them said setting expecting that you send them and their caveat here too as
sometimes the side effect is cheap and close and if it is I just plug in the
object right everything is simpler if you just use it i don't care if its
value object it's close but the further away the more the further away that side
effect and the more expensive it is to occur the better off you are to depend
on the stable edge instead of binding yourself to all the things between you
and that side effect and so here's the final grid this is it this is all you
have to do and so now if you're a minimalist if your unit testing
minimalist you got to use marks and I could hear some of you I can hear you
think in this aren't a fragile what happens what happens when observer stop
symphony changed so we know what happens
some of us do if you've been around long enough you know this is what happens it
hurt it hurts really bad and if you were writing Ruby and rails code a few years
ago when that mark everything me
you overtook us you may like me have created a universes where you have these
elaborate complicated sweets of tested all rain green where your apps for badly
ok we know we know better now we know better
this is not the fault of machs we just weren't using them right and here's the
a mock is a test double if it pleases the role of some object in your real app
so you have some role which is a separate thing I'm an object in my app
that plays a role I have a testable that plays that role if you get the fake
thing in the real thing both promise that they're going to implement a common
API and it is your job to ensure that your fakes that your doubles keep that
promise these magic tricks require mocks but if you're going to use them you have
to keep him in sync now you can try to do this by hand i can promise you that
will not work or you can get some help and it turns out in the last couple
years there's a ton of help
now i'm not going to talk about this where we're down where the 3-minute mark
here ok I'm not going to tell you anything about this except to say that
there's plenty of way now there are many things available in Ruby now that allow
you to create mocks and stubs to use mocks and stubs in your test doubles and
no confidently that they're not going straight from the API of the only other
thing I'll tell you about this is if you want been wanting to give a talk and
you're unable to find a topic do one on this there's like . there's this this is
the next wave i think of things that we want to do and we can really fruitfully
learn to use all these techniques so I'm asserting that you should use marks and
you can safely and that takes please accept that as a truth and so that's it
20 minutes 20 minutes and 26 seconds of magic tricks the minimal set of tests
you can you can write more unit tests and I'm specified but they're
unnecessary and every one of them adds a cost
this set is proof enough
so if you learn these rules you can experiment with him and once you
you can break them wherever it will save you money
the rules let you test everything in just one place they give you thorough
coverage in the fewest test possible they cite along the edges of the space
capsule so the tests are stable and they make it both safe and cheap to refactor
because those trust that collaborators we'll just do the right thing if you
send the right message there fast because they don't have to wait on all
those distant side effects these rules are simple and this is our job to find
the simplicity that lives at the heart of complexity like everything else in
programming test off and get worse right before they're about to get better and
if you stop before you reach simplicity you will never get test that you can
love don't give up midway down this path have faith that it's possible strive
until you achieve it believed in this getting better at testing takes practice
but if you persist to speak test can become the wall at your back instead of
the millstone around your neck and you can turn this thicket of insanity into a
beautiful garden of tests and go home every a say and I love my test
thank you