[Joe] My name's Joe Purcell.
I work at
a company called Bounteous.
We have a few different offices,
but two of them are here in Chicago.
I've been
working with PHP for,
well, since about 2003.
Probably for the last six years or so
I've focused mostly on Drupal.
We're hiring, so
if you're looking, come
talk to me afterwards.
Something else that's
new, I am a new parent.
That is--
- Congrats.
- Congrats.
(audience applauds)
- [Joe] It's interesting,
'cause like your day-to-day
is just very different.
- [Man] Is this your first?
- [Joe] This is the
first, yeah, for us, yeah.
- [Man] How old?
- [Joe] He's six months old.
- [Man] Oh, it's starting to
get really fun then. (laughs)
- [Joe] (laughs) Yeah, so
I'm enjoying that a lot,
but it's funny, 'cause when
I think about who am I,
certainly professional
things that are relevant,
but sometimes the personal
bits are fun to talk about.
A few years ago, I was
inspired to add this slide,
and I wanted to add this
again to this presentation
to just say thank you to people
who helped get me into Drupal.
Coming to a camp like this
is a great opportunity
to just say hi to
somebody you've never met,
and who knows?
Maybe the next thing you know,
you'll be writing your
own contrib modules,
or contributing back, or
maybe you find a solution
to some problem you're working on,
but I've personally found
the Drupal community
to be very helpful.
So I throw this up there just
as encouragement to you all
to meet new people.
I am expecting that you all have
some experience with unit testing.
This is an intermediate talk.
It'll be more advanced for
some, less so for others.
If you're not, like if
you would fall in the,
I'm more of a novice with
unit testing, that's okay.
I'll try to take it a bit slower
with the parts that are really relevant
and pause for a question or two.
And then for those who have
done a lot of unit testing,
what I encourage you to do is
to take the ideas I'm sharing,
mull them over, and let me
know if it jives with you,
if it resonates with how you
think about unit testing.
In The Mythical Man-Month,
it's a book published by
Fred Brooks years ago,
this is actually on the cover in it,
he's talking about how
projects large and small
have gone through challenges, whether it's
budget,
timeline,
you launch the product and
it's no longer relevant.
If you've been doing this long enough,
you've certainly gone
through this experience
where you feel like the
gravity of the codebase
or whatever the challenges
are in the project
are just weighing you down, and so,
doesn't matter if you
like push really hard
and you get one paw up out of the tar pit,
another paw is sinking down again.
And I like this to frame up the problem
that we're talking about,
which is quality software,
and unit testing is one way
that we can help ensure
quality in our work.
I find myself surprised
by running into the same
problems over and over again,
but if we're to solve them,
then we need to be clear
about what the problems are
and the solutions that we're proposing,
so something that you'll hear
me talk about several times
over and over again is about
coming to a clear definition
of what unit testing is
that we all agree and adopt.
There's two things I want
you to come away with.
One is what is unit testing,
and the other is when it's valuable,
'cause not every scenario
is going to be a good
scenario for unit testing.
So if you can come away
and you can articulate these two things,
I'll consider it a successful session.
The way we'll go about this
is I'll start with a definition.
I'll talk about two principles.
I consider these principles
because regardless of the language
or testing framework you're working with,
these two ideas are going to be relevant,
and if you can understand
them and execute them,
you will be able to do unit testing well.
I'll go through two examples
using those principles.
I'm actually going to skip
the test smells section
just for a matter of time,
and I'll talk about
motivations at the end.
The motivations is the fun part.
I will say this is more of a,
I don't wanna say it's a dry talk,
'cause I personally
find it really exciting
to talk about the nitty-gritty
details of coding,
but I'm not intending this to
be like really entertaining.
So there's going to be a
lot of knowledge-sharing,
so please speak up if you have a question.
Yeah.
- [Man] Since you're skipping test smells,
can you take a minute
to describe what it is?
- [Joe] Yes, I certainly will, yep.
I actually have one slide I
will go over with test smells.
Okay, definition.
Raise your hand if you feel frustrated
by the inconsistency of
definition about unit testing
and other types of testing.
You feel like it's a bit ambiguous.
So about half the room or more?
Okay.
So in my journey through unit testing
and other types of testing,
I'm repeatedly frustrated
by the definition.
I find that in conversation
you end up in these circular,
like circular logic, and then you realize,
oh, we're not talking about unit testing,
we're really talking about integration
or functional or
UI testing.
One challenge with the definition is that
pretty much every testing
framework has its own definition,
either implicit in
how it talks about testing
methodology, or explicit.
I've gone through every book
that I could find about unit testing.
Pretty much everyone has
a different definition.
Blog posts, podcasts, everybody's
talking about unit testing
in at least nuanced in different ways,
sometimes very different.
What I'm going to present here
is what I would consider
a consolidation, or a,
but what is common that I
see across those definitions,
so what you take away I
do expect will be relevant
even if your team
or the blog post you're
reading treats it differently,
the concepts here should apply.
So what is a unit test?
I've found the Wikipedia
page most often quoted,
which is interesting, 'cause it's like
just a huge paragraph description.
In the xUnit testing book,
they actually put the
definition in a glossary
at the back of the 1,500-page book.
I wanna bring that to the front.
Martin Fowler
in a post phrased it as it
being a situational thing.
Let the team decide for their purposes
and understanding of the
system what a unit test is.
I like this because it
gets at the heart of it.
Ultimately, your team
needs to own unit testing
if you're gonna do it well.
But he also calls out
that the software development community
simply hasn't settled on
well-defined terms around testing,
and I want to do better,
I think we can do better,
so I'm gonna take a crack at it.
Here's my proposed definition:
A unit test verifies a unit's correctness
against a specification.
So what I like about
this is it gets back to
the roots of formal verification
and mathematics.
Some of those terms are used there.
You can think of a unit as a function,
a method on a class, or a script.
I think the third one, sometimes
we don't consider this,
but you could consider
a script a unit of code
'cause it's something you can invoke.
It's one thing, and if
you've used a step debugger,
it might be helpful to think of it
as one frame in the call stack.
So here, this is getting a cache ID.
You could think of that as one thing,
or you could go up the stack and say
the middleware's handle method is a unit.
Very simple example.
Here's a function called increment.
It takes a parameter
and it returns that
parameter plus one integer.
So it's incrementing by one, and there's
the test here.
This is a simple test.
Most of my examples are gonna be PHPUnit.
So this shows a very basic way to test it,
to verify that if you
pass the integer one,
it returns the integer two.
So here in our test, we call
increment with the integer one,
and we're writing an assertion
that says the result of
calling increment with one
is the integer two, and
so this should pass.
When you're looking through
the Drupal codebase,
and when you're talking
about unit testing,
I think it's helpful to talk
about the levels of testing.
The simplest or lowest level
is what I'll call solitary unit testing.
I'll explain what I mean by solitary,
but you'll see the UnitTestCase in Drupal.
I think that kind of corresponds
to what we're talking about there,
or there might be sociable unit testing,
which is a little higher level,
and you'll find the KernelTestBase class
is used for a bit higher
level than the unit tests,
and then functional testing,
which BrowserTestBase
would correspond to that.
The thing that's confusing,
all these can be done in
unit tests in PHPUnit.
So bear with me
as we take a step back
from specific frameworks
to understanding high-level conceptual,
like what are we talking
about with unit testing.
I'll do a quick analogy.
I don't know if this'll stick,
but maybe for some of you it'll help.
This is a photo of a
bridge that's been built,
and they're testing it
by driving a coal train across the bridge,
and there's a person
underneath inspecting it
to see if there's any faults
in the concrete or whatever.
This is not a unit test.
Once you have the whole thing built
and you're testing the thing as a whole,
that is not a unit test.
But, so in this image, I show a steel beam
that maybe is used in that bridge,
and before it goes into
the bridge, you're testing,
you're doing a stress test on the steel
to make sure it meets some kind of,
whatever the regulation is,
so maybe it's amount of
pressure put on the beam.
So does that make sense?
- Yes.
- Okay.
Principles.
If you don't come away with anything,
pay attention to this part,
'cause these two principles,
I think if you can really understand
and adopt them with your team,
I think it'll make unit
testing go more smoothly.
How many of you know
what a test double is,
if I use the phrase test double?
A couple of you?
That is the first concept.
I use this image.
If you haven't seen Black Panther,
the Marvel film, I encourage you to do so.
It's a really good movie.
On the left you see Danai,
who's playing the character of Okoye,
and on the right you'll see Janeshia,
who's the stunt double for her.
So some of the action scenes in the movie
are actually played by someone
who kinda looks like Danai,
but has some of the skills and training
to do some of those stunts.
What's kinda interesting about Janeshia,
she was a professional wrestler.
She was part of the US
national bobsled team,
she did tae kwon do and gymnastics,
and for the film she would
train like eight to 10 hours
every day for weeks
leading up to the film.
But the point of this
image is that a test double
is two things that look very similar,
but they're different,
so one is meant to impersonate the other.
The reason we use test doubles is cases
where we have indirect inputs,
and I'll show an example
of what that looks like.
You might also call this shared state,
so it's information that is used
by different areas of the codebase.
An easy example would be
pulling data from a database.
The other scenario where
you use test doubles
is indirect output, so your side effects,
so maybe you're writing to a database.
You might use a test double there.
You can think of it as anything
that makes it not a pure function,
and if you don't know
what a pure function is,
make a note to go read about it.
PHP is not...
There are some languages
that are purely functional languages,
and it's interesting to read about them
and the problems that are introduced
when you have a language
that's not purely functional like PHP.
An example of an indirect input,
so we have a function called get_product.
In it,
let's assume that this is loading
that product from the database.
That's an indirect input.
Why is this a problem?
Well, when you execute
that function under a test,
if you don't have a database,
there's an exception.
There's no database to read from.
So a test double can
be a replacement for
having a live database.
An indirect input would
look something like this,
where we're calling
increment_product_favorite_amount.
I'm making this example up,
but just imagine you pass
a product to this function
and what it does is it
increments the favorite count
and then saves that product.
Well, if you try to
execute that under a test
and there's no database, it'll
throw an exception, right?
There are five different
types of test doubles
that you can use.
A dummy is never called.
It's just a class or piece of information
that's needed to actually
instantiate the class
or unit under test.
A fake, it's similar to a
dummy, but it is called.
So a dummy, you never call it,
you never interact with it,
but maybe it's a parameter
that has to be passed in,
but a fake, you would actually
call it and interact with it,
and a fake you would likely
have hardcoded information
into the test double.
A stub
is similar,
but typically a stub
lets you provide indirect inputs
in your test,
so you can vary what
the indirect inputs are.
A spy, it's similar to a stub,
but it's also gonna capture
your indirect outputs,
and we'll show an example
of what each of these are,
so just kind of take a
moment to absorb these.
And then the last one is a mock,
and it provides indirect inputs
and it verifies indirect outputs.
Something that makes me disappointed
is when people say mock and
they really mean test double,
because mock is just one type of five.
There's five different
types of test doubles.
So let's go through some examples.
So imagine we have a test, and
in this test we wanna verify
that this voter
class
by default grants the created permission,
and so we set up a token dummy
'cause this is just a
piece of data that's needed
when I call the vote method.
We create another dummy
and pass that in to our vote method.
Something to call out here
is that the token dummy,
the first one, in
PHPUnit, you call getMock,
and what it does is it returns
an object that is a double
of the class you pass in.
So what's cool about that is
if you don't want to
have to deal with some of the parameters,
like construction parameters
of the class you want to dummy,
you can do that by just calling getMock.
You can look up some examples
if you wanna know more about that.
Or maybe you can just
pass in a standard class,
so the object dummy in this
example, it required an object,
but by default it's never
called, so I have to pass it in,
'cause if I don't, it's gonna
fail, but I don't necessarily
have to have information on the object.
So this is an example of
where a dummy can be used.
In this test
I'm verifying that
a BinaryFileResponse by default
is setting the X-Accel-Redirect
to the path of the file.
So here I set up a fake file,
and you can see this is a fake file class.
Now imagine,
I'm not showing the code
here, but imagine I had gone
and created this class called FakeFile,
and it has all the methods and such on it,
but it's never like reading
and writing from disk.
It just, even if there's a read method,
it just returns some hard-coded values.
I pass that into the BinaryFileResponse,
and what that does, it
allows me to run an assertion
that says that header is the
path of the file that I said.
So fakes can be used when...
I think a file system is
a really good example,
'cause if you write a fake file
you can use it in all kinds of tasks
and you never actually
have to have a file system.
A stub
lets you provide indirect inputs.
So in this case, what I'm showing here
is that this authorization_checker class,
the isGranted method
will return true.
So I mentioned that with PHPUnit,
you can get a double of a class.
Another thing you can do
once you have that double
is you can say, all right,
this method is gonna return
a particular value, and you can do that
with whatever method is on that class.
So in this case, we do it
so that when we instantiate our controller
and check if it's granted, we
wanna assert that that's true,
and granted, this example
is a bit contrived,
but you can see how stubs, all it is
is you're passing in
indirect information, right?
I did not pass true into
the test controller,
I passed an object that
had a method on it.
A spy.
There's a library called Prophecy,
and you can use it in
conjunction with PHPUnit
to capture indirect outputs.
So in this case, what we're doing is
we're creating a fake user,
or a double I should say,
a test double of a user.
You pass that into this profile class,
and then we're gonna assert
later in the last two lines,
we're gonna assert that
when you call getFullName,
it's gonna call the first and last name.
Now you're not asserting that like...
This avoids having to
like create an actual user
with a first and last name
and validate the output is,
like the full name is
the first and last name.
This lets you just know like, okay,
when I getFullName, it's
calling these two methods on it.
Again, a bit contrived, but
hopefully you get the idea.
A spy is capturing what
methods were called.
A mock does something similar.
This looks similar to a stub,
but there's one difference.
Here we're saying that the getFirstName
should be called one time.
So here's the getFirstName method.
We're even specifying what
value should be returned,
and then we're expecting
that the last name is gonna be called,
and what's different between
a mock, a stub, and a spy,
yes, they're all
providing indirect inputs,
but a mock on execution will,
it'll throw a failure
if this is not called.
So a mock does its
assertion during runtime.
One thing, and this is one reason
I don't care for mocks much,
is your assertion is not clear.
You would have to read in
your setup in your test,
you would have to read there
for where the assertion is.
We'll come back to why
that's a problem later.
What should you double?
So when should you use test doubles?
There are different approaches,
but I'm gonna categorize it as
it depends on if you're doing
solitary or sociable unit testing.
So going back to our bridge analogy,
when you take a unit
and you're only testing that in isolation,
that is very solitary, so
it's similar to that example
where you have a steel beam
and you're testing its endurance.
Sociable would mean that
you have more things,
more code being executed during
runtime that you're testing.
So how do they differ?
I actually took this idea from Jay Fields.
He wrote a book called Working
Effectively With Unit Tests,
and how he describes the difference
is that sociable testing
will cross some boundaries,
and we'll talk about what a
boundary is here in a second,
but solitary will never cross boundaries.
With sociable testing,
you'll have one or more
concrete classes during test,
but with solitary, you only have one.
So during solitary testing,
you only have the unit you're testing
that's instantiated and running.
A boundary is a database, a queue.
It could be a network call,
it could be a collaborator.
So if you're looking at a bit of code
and you see it calling a
method on another class,
that other class would be outside
the boundary of that unit.
You might think of this
as single responsibility.
That may be another way
to think of a boundary.
So if it's calling a
method on another class,
that other thing is not the responsibility
of the unit you're testing.
What does one concrete class mean?
This means that any dependency
you have in a class,
you would use a test double for.
So if you're testing a class
that's got four constructor parameters,
each of those should have a test double
if you're doing solitary testing.
It also means the collaborators
that return objects
should return test doubles.
So if you have a collaborator
that returns another collaborator,
that class should also be doubled,
'cause again, it's solitary, it's lonely,
nothing else is going on.
Statics violate this.
So Drupal uses a lot of statics.
If you use Laravel, it
has a lot of static calls.
Strictly speaking, if
you have a static call
in the unit that you're testing,
that would violate this
principle of solitary testing.
One caveat to that is a value object.
So if you have a class that
has no behavior at all,
all it's doing is just returning data,
I would say that that's an exception.
So you can have that class
instantiated during runtime
and still follow this
principle of solitary testing,
'cause in that case I
would consider that object
to be a data construct and
not a class that has behavior.
At this moment, your
head might be exploding,
and you may not be sure if
it's a whole bunch of knowledge
or just confused, so let me
recap what we just covered
and then you can ask some questions.
So a unit test verifies
a unit's correctness
against a specification.
Use test doubles for indirect inputs,
and there's five of those.
A boundary is an indirect input or output.
With sociable testing you
can cross some boundaries
and have multiple classes during the test.
And in solitary, you
never cross boundaries
and you only have one concrete
class during the test.
Any questions?
Everybody tracking okay?
Okay.
- I have a question.
- Question.
- [Joe] Yeah, please.
- [Woman] So would you
be testing with that also
from this like testing for outliers?
Like for that first one, you had,
you were doing something very specific,
you're expressing very specifics about it.
Would you also have as
part of unit testing
testing your outliers which
you're not thinking about,
or is that part of a
different type of testing?
- [Joe] So the question
is with unit testing,
would you test for outliers,
or maybe you could phrase it
as positive versus negative testing like,
I know the specification of
my test, I pass an integer
and it should return
that integer plus one,
but should you have a test that says,
what if I pass a string,
what's the return value there?
I would argue that that type of unit test
is often more valuable
than the positive case,
and you probably have more unit tests
about the negative cases
than you do the positive.
Error handling and edge cases
in software is important.
I think in web development,
many times it goes overlooked
because of the criticality, right?
Some websites, it's not,
like the impact of one of those edge cases
might be pretty small,
but in other areas of
the software industry,
that's really important, like aviation.
If you're not testing for
an unexpected scenario,
that's a huge liability, right?
So does that help?
- [Woman] Yeah, it is part--
- [Joe] So yeah, the
short answer is yes, yeah.
Any other questions before we move on?
And we'll talk a bit more about that.
Okay.
All right.
Principles by example.
So I pulled these from Drupal.
I'm gonna go through these a bit quick,
so
I won't pause for questions.
This is more for us to
look at these things
and exercise those two
principles that we saw.
So when I look at this batch storage class
and I look at this load method,
and say I want to unit test
it, there are a couple things
that would make this
difficult to unit test.
Not sure if you see those things yet,
but I'll call them out.
This method assumes that you have
the ability to start a session.
Do you have session storage set up
before you're executing this?
Sometimes your testing framework
already handles this for
you, but if it doesn't,
you need to account for that.
It's also assuming you have a database.
Do you have a database
when you're running this?
How does that impact the
speed of the test when
you're running it to have it?
It assumes that you have some
service to get a CSRF token.
Let's go through an example using a mock.
So here's our test, and
the test that I want,
or my specification, is
that I just wanna verify
that loading a batch is
going to start the session.
So here, I mock the session start.
So this is
saying that the start
method is getting called,
and this is actually
our assertion, remember,
'cause a mock actually does
the assertion of your test.
I stub the input from the database
so that I don't have to
have a database running.
I dummy the CSRF token 'cause
I don't actually need a value,
and then lastly I execute
the class under test.
So this works, right?
This is a unit test that works,
but can we make the
assertion more apparent?
This is where knowing your
test doubles can be helpful.
So let's try it using a spy.
So here we have a whole bunch of setup,
and I don't call this out,
but you can see where I'm using a spy.
That first line is creating a test double
using the Prophecy framework,
and this line I've highlighted
we're calling the class under test,
and our assertion is
called out here at the end.
So this is called, this practice
of arrange, act, assert,
means that all of your setup
is done at the top of your test.
You're acting on the unit under test,
and the null assertions hap at the end.
So you remember how I said
that I don't care for mocks too much?
This is an example.
You can't really do arrange,
act, assert using a mock,
but you can do it using a spy,
so it makes it a little more clear.
A second example, this one's
going to be more complicated,
and if you have trouble
tracking, that's okay.
We'll run through it and I wanna
go on to some other things.
So this is a maintenance mode subscriber
that I pulled out of Drupal 8.
There's a lot going on here.
See if you can spot the indirect
inputs, indirect outputs.
There's a few of them here.
We have a static call, two of them.
We have a function call.
We can't solitary unit test this,
so what I'm gonna propose
is that we refactor it
so that we can unit test it.
But before we refactor,
we wanna have a test
to make sure that we're not
breaking anything, right?
So long story is write a higher-level test
that maybe adds the database.
Delete the untestable code.
We'll re-implement it later.
Break the class into two responsibilities.
So we're gonna have one
that's just checking,
or one that's just generating
the maintenance page
because we don't wanna assume
that every page is gonna be HTML.
It might be JSON, it might be XML.
And the other responsibility
is gonna be determining
if the maintenance mode page
is turned on and should apply.
We'll write our unit test,
and then eventually delete
our higher-level test
'cause we don't need it,
'cause we now have a unit test.
I'm just gonna show kinda
how this would play out
with separating the responsibilities
and writing the unit test.
So responsibility one,
one thing that's cool about PHPUnit,
you don't actually need an
implementation of an interface
to use a test with it,
'cause remember, I said
getting a test double,
it just creates a double of the class,
so as long as my implementation
is looking for the interface
and not the concrete class,
I can write a whole test
suite just with the interface
and not having any concrete
classes that actually exist.
So let me go back.
So this here is, this class
is gonna be responsible
for getting the response,
so it's gonna read the Accept headers,
detect if it needs to return JSON,
or XML, or HTML, or whatever,
and return the response.
I'm refactoring the
maintenance mode subscriber
to just check
that the maintenance mode applies
and whether the user is exempt
from that maintenance page.
Everything else is now
handled by that other class.
So now we can test it pretty easily.
I say easy, but unit tests
with doubles are difficult.
So here we get a test
double for our account.
We have three dependencies to account for.
The account,
we stub the maintenance mode,
so we say maintenance mode is turned on.
We stub the response.
We stub the request, and
then call the unit under test
and make an assertion.
So the thing we're asserting here is that
if the maintenance mode is turned on,
that a applicable maintenance
mode page implementation
can set the response.
What's great about this,
so what we just did
is like we took this class
that'd be hard to test
and we made it testable
by doing that refactoring.
Our class does one thing now.
It's good, single responsibility,
solid programming.
We can test one thing.
The class has three
dependencies instead of seven,
so we're now decoupling our code.
It's more readable, it's easier to test,
and it's abstracted so non-HTML
responses can be returned.
So I don't know if you
read it, but anyway.
Let's see, what time do
we gotta be out of here?
We got 10 minutes?
- 2:30.
- [Joe] So we got about 10 minutes?
Okay.
Yeah, 10 minutes?
Okay.
- No, we've got till 2:30.
- 2:30 is--
- 20 minutes.
- 20 minutes.
- 20 minutes.
- [Joe] Great, great, okay.
I love these Boromir memes.
You don't simply write unit tests.
I think that on a high level,
many people are onboard
with unit testing is good,
but once you dig into code,
you can see that it is,
some code is really hard to test.
When you have a whole
library or a codebase
where you're using TDD, or you
simply have a legacy codebase
and you want to write unit tests for it,
you're gonna run into problems,
and that's where you get into test smells.
We won't cover all of them.
So there's a book by Martin
Fowler called Refactoring,
and in it
he itemizes different
code smells.
If you look in the xUnit testing book,
they talk about test smells.
Both cases are when you're looking at code
and working with code
and you find something
that's really stinky,
it's hard to work with,
that's a test smell.
So it doesn't necessarily
mean that there's something...
It doesn't always mean
somebody did something wrong,
but it smells.
So the example that
Martin Fowler refers to
is if you have a kid,
a baby, and you need,
like when do you change a baby's diaper?
The advice that he got was,
well, if the diaper stinks,
you should change it.
So the principle of test
smells applies here too.
So if the test is, or
code is hard to work with,
then change it.
So I won't go through all the
test smells because of time,
but I left the slides in
here, and at the last,
at the end I'll give you
a reference to the slides
so you can look at them later.
So if you're doing unit
testing on your team,
maybe check this out.
There's some what I think
is good advice in there
that I pulled from other
people, it's not mine.
Motivations, so this is the fun part.
Actually, I don't wanna show that yet.
Let's look at this picture.
So this picture is of a bridge,
and I got this from a friend of mine,
and imagine you're in a remote area.
The bridge is made of wood.
There's plenty of wood around.
The bridge might be
mostly pedestrian traffic.
It looks like the bridge
that it's going over,
it's not like a big cavern
or gorge or something
where if someone fell off the bridge
they'd be severely injured.
It's a relatively safe
area, let's pretend.
How valuable would it be to
spend months of preparation on
and lots of money building
a steel bridge here?
Well, maybe steel isn't abundant.
Maybe it's easier to,
like if a board in this
wooden bridge is worn out,
they could like easily replace it.
So I'm like painting this
like fictional scenario.
I don't know if it's
actually true of this bridge.
Unit testing wouldn't be very valuable,
or at least it would have lower value
because the impact and the
cost of it would be low.
Bear with me.
Let's contrast that scenario with, say,
one of the bridges downtown Chicago
where it's a two-level bridge,
you have pedestrian traffic
and vehicle traffic on the lower level,
and above it you have the L train.
And the L train, or the
CTA, there's 750,000 people
who go across this every day on average.
There's a huge impact if anything fails.
If there's a single bolt that comes undone
and causes a failure in this bridge,
that's a huge deal, right?
So in these two scenarios
that I'm trying to paint,
it's like there are scenarios
where unit testing has a high
impact and a lot of value,
and I think that there
are some that are not.
Give me feedback if you
think that analogy works.
That was like something
that I've been working on.
- [Mike] On that first picture,
I wouldn't necessarily drive
my car or truck across that.
I'd run out and jump up and down.
(Joe laughs)
I'd pretest it.
A lot less expensive testing, right?
- [Joe] Yeah, sure.
- [Mike] Get out there and look at it.
- [Joe] Yeah, so the comment
is looking at this bridge,
I wouldn't necessarily drive my car on it.
- [Mike] Until I tested it.
- [Joe] Yeah, until you tested it.
- [Man] But with a really cheap--
- [Joe] Yeah, right.
Yeah, like the train test
that I showed earlier
where like you build the thing
and then run a train across
it and see what happens.
- [Mike] If I was with you, you'd say,
hey Mike, go out there.
(audience laughs)
And if you come back,
then we'll drive across.
(audience laughs)
- [Joe] But let me spell
it out in a different way,
so let's go beyond analogy.
Rule of three, if you have three classes
or three areas of code that's
using a particular unit,
unit testing would have a lot of value.
If you're writing a library,
if you're writing something
that other people are using
in their composer, JSON,
please unit test.
If you have legacy code that
you're planning to refactor,
unit testing can help make sure
that you're not breaking
things as you're refactoring.
If there's a high amount
of necessary complexity,
so encryption, compression,
certain algorithms, maybe
a sorting algorithm,
sometimes there's code
that doesn't really
make sense to decouple.
Unit testing is gonna have a lot of value,
'cause you can't break it
out into simpler parts.
Exploratory work, test-driven development,
there can be a lot of value to
forcing yourself to sit down,
write a unit test, and then write the unit
that implements the test
or makes the test pass.
Going back to the bridge in Chicago,
if there is even a high
cost to very minor bugs,
unit testing is gonna have a lot of value.
If you're writing in a framework,
so this is kinda similar to
if you have a composer dependency,
you wanna make sure that
your framework code is solid.
When does it have low value?
If you're writing glue code.
So what I see glue code is
is like if you're writing
maybe like a theme hook
or something in Drupal,
I don't know that
there's a lot of value in
unit testing that.
What you really care about is
you want to be able to assume
that Drupal's doing its job,
and that you're wiring up something
so that the presentation is correct.
In that scenario, you
probably have more value
from a higher-level test
'cause you don't have like
a lot of unique complexity
in the units of code you're writing.
There are some exceptions,
but just broad stroke,
if you're writing glue code,
unit testing probably has low value.
Maintenance cost is significantly higher
than the cost of a bug.
Also applies to some websites.
You're writing code in a platform.
This is probably similar to the first one,
but I'm just saying that like,
if you're writing code that's
stitching things together,
you would probably benefit more from,
say, an integration test than a unit test.
There's a more valuable form of testing,
like using a integration or
UI testing.
Solitary or sociable.
I had to throw this in here.
So sociable unit testing, I think...
- (laughs) Awesome.
- I think what helps,
so this image is somebody made
these cabinets, and you can't
open a drawer all the way
because the cabinet comes at a corner
where the drawers butt
up against each other.
You may have tested to the extreme
each one of these drawers individually,
but until you've put things together
and tried it in combination,
those unit tests were useless.
I'm not gonna say
useless, that's too harsh,
but they didn't surface this problem
when you have the whole
application working together.
So that's one value of sociable testing,
but it's hard to debug.
It requires a lot of setup and teardown.
You might need the database, right?
It requires a lot of test doubles.
If you've ever had to write
a KernelTest in Drupal 8,
you'll know what I'm talking
about when it comes to
sociable tests can be difficult.
Solitary, it allows higher code coverage,
so you have more control over
the scenarios you're testing.
It promotes decoupling.
Remember the example we went through,
we were factored and split things out
to make it easier
to test.
It executes faster and enables
better software design.
Simon Honeywell has a book called
Functional Programming in PHP,
and he made a comment
that stuck out to me.
It should not be underestimated
how much easier these functions,
he's referring to pure functions,
how easier they are to test
than their state-filled counterparts.
So when you're writing code
and you want it to be testable,
consider if you can avoid
shared state and side effects.
I would say that solitary unit testing
forces you to write good code.
All that said, if you know
the story of the Lorax,
there's the Once-ler
who comes into this area
that has lots of resources,
and he builds and builds and builds,
and essentially destroys all
the resources that are there.
And the Lorax is pleading
with him, saying,
"Look, I know you're
focused on production,
"you're focused on churning out that code,
"but unless you pause and
like consider the impact of
"lobbing code into a release,
"you may be foregoing
"severe implications of
the problems in your code."
And
the Lorax says,
"Unless someone like you
cares a whole awful lot,
"nothing is going to
get better, it's not."
Talking about unit testing is great,
talking about software quality is great,
but ultimately it's gonna come down to
the people, the developers,
who are close to the code
to speak for it.
So in the story of the Lorax,
it says the trees have no tongues.
Code doesn't speak for itself.
You all are the experts of
the code, you speak for it.
An easy example, a friend of mine,
he sent me a Slack message,
this was a while ago,
and he said, "This is
why Drupal 8 is good."
And he was referring to someone
who had pushed back on a
code submission saying,
"This is simply too much
code for a blind commit."
Supporting comments won't
help, we need tests.
This is it.
This is where like that
Lorax hat comes into play.
You are the one to
really make a difference.
If you have a codebase
and you don't really know where to start,
I'll give some suggestions,
and this really applies to like frameworks
and places where unit
testing has high value.
There's a tool called PhpMetrics.
For this graph, I actually
used an older version of it,
'cause it lets you generate this chart
where it shows cyclomatic
complexity along the y-axis.
Cyclomatic complexity is a measurement of,
essentially a measurement of
how many different paths
there are through a function,
so it's a metric of how
complex your code is.
Efferent coupling is along the x-axis,
and this means how many
dependencies are there
in the unit you're working with.
The more dependencies you have,
then the more doubles you need
if you wanna do solitary testing.
I've been doing this analysis
on Drupal 8 for a while.
This one's on Drupal 8.6.
Since 8.3, this plot has not changed much,
but I wanna call out a few things.
You'll see there's some
entity-related classes
that have high complexity
and have a lot of coupling.
I think those are good examples
where you may wanna look at that.
You may say, look, we
don't wanna decouple this.
It's too complex, we just
wanna keep this in this class.
But what I'm saying is, if
you wanna look for areas
where unit testing might have high value,
high complexity, lot of
coupling, if you unit test those,
potentially break them
out and decouple them,
then you can focus on
making more clear assertions
and assumptions about
what your code is doing.
Yeah, and then I call out
a few different examples.
There's Form Builder.
ArchiveTar is a class that
has really high complexity,
almost no coupling, so it doesn't need,
it's not depending on
a lot of other classes,
but high complexity,
probably 'cause of all the
compression it's doing.
But if you look at that,
it's like one big file.
There might be benefit
to breaking that out
into separate files so
that you can unit test it.
Symfony, yeah.
- Five minutes.
- Oh, five minutes, okay.
Great, I'm almost done.
Symfony is really successful with reuse.
If you go around to different
frameworks and platforms,
like Drupal is using
some Symfony components.
Why is Symfony so successful
in being reusable?
I'm not saying this is like truth,
but I have some observations.
It's good object-oriented code.
If you read some of those Symfony classes,
they're pretty clean,
not too many cases where you look at it
and you're just, you throw up your hands.
You may not be familiar with
it, but not like ArchiveTar.
No surprise, the tests are mostly,
if not entirely, solitary unit tests.
Not all of them, but
there's some kernel tests,
but I wonder if we did
more solitary testing
if our frameworks and libraries
would benefit a lot from it.
So remember the two principles,
test doubles, solitary versus sociable.
Thank you.
Please provide feedback.
Rate this session.
Here's a link to the slides.
I'll come back to that.
Contribution Day is on Saturday.
You don't have to know code to contribute.
We'll be doing some
Drupal 9 readiness work,
so come join that.
If you're new to contributing,
there's a training by AmyJune
at 10 o'clock to noon.
We have like four minutes for questions.
Yeah.
- [Woman] So what do you use on your team
to help like automate ensuring
that you have proper test coverage?
Which ones do you find
that's most useful for you?
- Depends on the--
- Could you repeat
the question?
- [Joe] Oh yeah, yes,
thank you, thank you.
I usually remember.
What tools do I find useful for testing?
- [Woman] For automating the
test coverage, for example.
- [Joe] Automating test coverage?
If you want a test coverage report
to see what code does
not have a unit test,
there's a service called
Coveralls that I've used.
I'm sure there's others.
Does anyone know of a
test coverage report tool?
That's one that I've used before.
I'm sure there's others.
I think CircleCI.
CircleCI does, it's kind of like Travis,
it's like does a build test in the cloud.
I think they have a tool for it.
Any other questions?
Comments, objections?
Okay.
Yeah, yeah.
- [Woman] What type of
issues have you experienced
when like, for example,
you're like trying to
get more unit tests done,
or like how do you deal with, for example,
when you have like a project
that's on a tight deadline,
and you have like a project manager saying
you need to get this stuff going,
but you want to take time
to do all the unit tests,
like spreading this out.
How do you...
- [Joe] (sighs) If I had... (laughs)
So the question is
what problems have you experienced trying,
you have a deadline, but you
need to do the unit tests?
If I had an answer to
that I would share it.
(audience laughs)
I'll give a quick take and
then, Mike, if you have.
- [Mike] I only intuit.
- [Joe] (laughs) What I found
is that there needs to be,
the stakeholders need to weigh in.
You have a choice.
You understand, it's like the the Lorax,
it's like the Once-ler.
If you if you wanna push
this through, you can,
but it's at the cost of this practice
that we believe will help
us control the pollution
in our codebase, or the
things that are incorrect,
it'll help us root out bugs.
Mike would.
- [Mike] Along the same lines,
if you're not using the
Agile, then consider it,
because if you start down the process,
if you start the processes
with an agile methodology
where testing is built into each sprint
and you get your stakeholders
involved with the process
and they attend the
sprint planning meetings
and they are responsible
for the sprint review,
if they see a lack of quality,
if they see a lack of test plans,
in expected results versus actual,
if they see a lack of unit tests,
they'll push back and they'll
help people speak up more.
A lot depends on the team.
You have to get your team,
everyone has to onboard with quality.
- [Joe] So Mike was saying,
if I summarize this well,
that agile methodology will help here.
At each sprint review,
they can see what has
test coverage or not,
and they can weigh in and say,
weigh in sprint by sprint
and say whether more time
should be invested in testing.
- [Mike] Just not to leave
the testing till the end.
- [Joe] Yeah.
All right, we're at time, but maybe one--
- [Man] I'll follow up with you.
- [Joe] Yeah, yeah.
Come talk to me.
I'm on Twitter, reach out to me.
Thank you all for coming.
(audience applauds)
Captions made possible by ClarityPartners.com. Chicago area Drupal consultants