Aloha RubyConf 2012
Summarized using AI

Yay! Mocks!

by Corey Haines

The video, titled "Yay! Mocks!" presented by Corey Haines at Aloha RubyConf 2012, delves into the use of test doubles - specifically mocks and stubs - in software testing. Haines argues that while test doubles are often blamed for creating fragile test suites, they can actually serve as a valuable tool for improving software design and maintainability.

Key points discussed include:

- Definition of Test Doubles: Haines defines mocks as expectation-based and emphasizes the broader concept of test doubles, which includes stubs and other forms of testing shortcuts.

- Common Misconceptions: The talk confronts the notion that the use of mocks leads to fragile tests by highlighting that issues arising from mocks often indicate underlying design flaws.

- Better Design Principles: Haines advocates for focusing on better design, which is characterized by the ability to change easily. He stresses the importance of separating concerns, reducing duplication, and creating clear abstractions to improve code maintainability.

- Functional and Object-Oriented Programming: He critiques traditional object-oriented programming practices, suggesting they often neglect interactions and services in favor of rigid class structures. He emphasizes the significance of messaging over inheritance in OO design.

- Pain Points in Testing: Haines introduces pain points encountered in testing scenarios, using examples from his experiences in code retreats to illustrate how issues can guide refactoring efforts towards clearer, more maintainable designs.

- Four Rules of Simple Design: The discussion concludes with the four fundamental rules of simple design proposed by Kent Beck: 1) Tests pass, 2) Reveal intent, 3) No duplication, and 4) Keep it small. These rules serve as guiding principles for writing tests and designing systems that respond effectively to changes.

Overall, Haines emphasizes the idea that using test doubles should not be a source of complaint, but rather a cue to examine and enhance overall system design. By treating tests and their accompanying pain points as indicators, developers can utilize these insights to achieve better software architecture and long-term maintainability.

00:00:14.640 All right, everyone! Having a good day? There we go, that's better. I just wanted to let you all know that it's really beautiful outside! Not to do anything, all right?
00:00:21.240 Well, thanks. It looks like we’re going to get started. I don't have a lot of pictures of my cat yawning, but I do have a lot of pictures of her sticking her arms out as far as she can. We call her "long arm of the claw" because how many of you guys have cats? How many of you have a cute little name for your cat that you whisper in their ear? Admit it, yeah, I do!
00:00:40.330 We always call her Long Arm, although we call her Zach. Despite the name, it's a she! This is always fun when you take her to the vet and they say, "Oh, how's he doing?" You’re like, "It's a she!" You always have to go through that mental process of deciding whether to mention it, but you always do because, you know, look at her!
00:01:08.700 Okay, so today I wanted to talk about something that I've been thinking about a lot as I go around, work with people, watch other presentations, and deal with a topic that is near and dear to my heart. It’s really one of the core tenets of my testing strategy: mocks. Mocks are such an easy scapegoat. When you hear people give talks, there’s always this discussion of how "mocks suck" or "mocks make your testing fragile and your code fragile."
00:01:29.170 So, I thought I would talk a little bit about this because I consider myself an effective developer. I’ve been doing Test-Driven Development (TDD) for quite a while in some form or another, and for the most part, I've been what I consider a strict isolationist. I pretty much isolate the code under test from its dependencies. So I thought I would talk a little bit about how I use mocks, what I use them for, and some of the common problems that people have with them.
00:01:50.259 I want to expand it a bit because when you talk about mocks, they have a pretty specific definition. Mocks are expectation-based. You tend to say, "Hey, I'm going to build one of these things, and I’m going to expect this method to be called on it," and then afterwards, you verify whether that method was called with certain parameters.
00:02:07.619 But there's a whole range of uses for these. There's a specific use when you're saying, "Okay, I want to make sure that it interacts with another object that way." But I’d like to expand it to not just be about mocks, but about test doubles in general—about all of the different things we do: stubbing, just general doubles, mocks, things like that. I tend to be very lax in my terms and just call everything a double or a stub or something like that. Throughout the course of this talk, I may drop the word stub when I'm talking about a mock, or I may drop the word mock when I'm talking about a stub.
00:02:27.360 I'll ask for your forgiveness, but please bear with me and assume that I’m talking about some form of a test double. The type of test double is really encoded in the examples in the tests that I write, and I tend not to differentiate that much. It’s one of the things that I really like about the RSpec doubling library or the mocking library is that pretty much everything is an alias for everything else! When you come down to it, when you say "mock something" or "stub something," they’re actually aliases for the same method. They’re really just giving you a test double that you can use for its appropriate purpose.
00:03:07.950 So I’m going to talk more just about test doubles, some of the things that I use them for, and ways to do it. I’m going to go into a bit about design as well.
00:03:13.500 But before I get into that, there’s something I really thought you’d want to see. It’s another picture of Zach. Look at her! She tends to have that look a lot, just like—really honest. I'm always taking pictures of her!
00:05:09.120 So I wanted to start off by talking about good design. How many of you know what good design is? And be honest. If I asked you, and we were sitting around, you’d say, "Oh, I know what good design is!" Anyone? A couple of people would say that. I don’t really like to talk about good design because I find that everybody has their own opinions about it.
00:05:30.180 There are a lot of different views on it and many different truths. So the discussion about what makes a good design is really important, but I tend to only be willing to have that discussion while drinking beer or preferably scotch. It’s a fun conversation and it’s great to hear what other people value!
00:05:50.259 Instead of talking about good design, I like to talk about better design because I think we can come up with a definition for better design. There’s one constant we know about software development: it changes. As we’re building it, as we’re showing it to our clients, they’re going to say, "Oh yeah, that’s great, but I didn’t think about this one thing," or "Now that I see it, I realize I actually wanted something else."
00:06:06.360 Has anybody ever had that experience? How many of you like when that happens? How many of you just sit there and grumble about it with the rest of your teammates? Like "How come they didn't know? They couldn't tell me that?" Man, scope creep! Or, "Why couldn’t they say that first? I already built my system!" In our next breath, we’re like, "Can’t do upfront design! Can’t gather all the requirements upfront!" So we have this cognitive dissonance.
00:07:10.080 It's one of my favorite terms—cognitive dissonance! It’s one of my favorites because I use it all the time, wrongly. But it’s that idea of being able to hold two ideas in your head that conflict. We come up and talk about how we want to build iterative designs, how we want to build small things, show them, build the next thing, and then show it. But when our customer comes and makes a change, we start to grumble! I do. Everybody does.
00:07:27.630 So, knowing this, what I consider better design is entirely given by how easy it is to change. And I'm not talking about building a system that is ultimately extensible or configurable with XML or YAML. It was a Ruby conference, so not like, "Oh man, this thing can do everything if you just change this one parameter." I saw a Rails application once, working with a friend. It was a rescue project that was eventually going to become synonymous with Rails apps.
00:08:09.780 He was like, "Hey, I'm not going to tell you anything about the people who wrote this." I looked at it, and I thought, "Ah, Java developers!" He said, "Yeah, yeah!" (He didn’t ask how I knew, because it was clear.) But one of the fundamental characteristics of Java applications is that you build a framework that’s XML configured.
00:08:44.970 So pretty much every Java application eventually ends up being an XML-configured framework. Well, in Ruby and Rails, we’re better than XML—XML sucks! So instead, if you can see where this is going, it was a Rails application that was basically built on top of Rails but was configured with YAML. They had a couple of hundred YAML files. The guy threw up his hands and said, "I don't even know where to start!" I was like, "Wow, I’m glad I'm only here for a day!"
00:09:18.490 But I’m not talking about that! What I’m talking about is that ability to have a design that is easy to change. It's about being able to find where you want to make a change, knowing how to make it safely, and being able to do it in one place.
00:09:31.200 All of this comes down to that fundamental idea of removing duplication: naming things well and having a good test suite. So this is kind of how I want to lead back to all of this. How many of you write some form of automated testing? You're all Rubyists, so your hands should go up! How many of you consider yourself to be doing test-driven development? How many of you write your tests first? How many of you go, "Oh, I write my tests after sometimes?" That's good!
00:10:09.300 So, I’m going to talk a little bit about this and its implications on design. I also want to talk about Object-Oriented (OO) programming. Over the last few years, I’ve been doing a lot of thinking about OO, dealing with people, and working with people. I have to say, we suck at OO!
00:10:19.830 One of the things that I think is that with the resurgence of functional programming and saying OO doesn’t work anymore, it’s mostly because we don't do OO very well. We don’t adhere to some of the core principles of it. One really important thing that OO is primarily about is messages. It’s about sending messages between things.
00:10:37.430 I'm currently learning Erlang because, as near as I can tell, Erlang is one of the purest OO languages we have out there! It’s this beautiful language that distills everything down to messaging—messaging between processes. It’s about interactions. It’s about how things talk to each other. It's about how one object interacts with another. It’s not about inheritance; it’s not about encapsulation, although those are important.
00:11:01.800 But it’s really about interactions. It’s about how things work with each other and how services are built. It’s about creating good layered systems that are decoupled and do one and only one thing well. Last, it’s about roles. It’s about what you’re using the object for. It’s not about the objects themselves.
00:11:34.060 One of the fundamental problems that emerged when most of us were taught OO was that we were taught to write all of your use cases. And then, what do you do right after you write your use cases? You underline the nouns and then make all of these classes. You underline the verbs and you try to shoehorn them onto the classes. If you think about what we do as developers, we automate processes. Basically, everything we do is about automating a process.
00:12:10.430 Yet, we start our application, our design, not by the process, not by the actions or the verbs, but by the nouns. That leads us into these crazy designs where you're trying to figure out where this behavior lives rather than figuring out the behaviors, the services, and the interactions you have. Then you can think about what names would group them effectively. Test-driven development is a great way to focus on the interactions, the services we provide, and the messages we send.
00:12:43.780 In Ruby, these messages are brought to life via method calls, even though they behave like message passing. I wanted to touch on TDD and its sister, test-first development. I have a very specific definition for these two terms. They’re not the definitions many people use, but I think they’re helpful, especially if you hold by these definitions to differentiate between them to avoid that pit where someone says, "Oh, yeah, I do TDD," and you feel bad because they go, "No you don’t!" It’s like you’re going to hell if you don’t do TDD!
00:13:36.720 So I want to talk about the differences, and as we progress as developers through our careers, we start writing automated tests, write them afterwards, then move on to writing them first and notice the effects that the tests have on us. The main difference between these two, as I see it, comes down to our reaction to pain.
00:14:10.180 The primary difference in my definition is our reaction to pain. What do you do when you find it difficult to test something? When you write your tests first, you tend to change your tests. You might write helper methods or, god forbid, use something like factory girl to hide the complexity of your tests—something that says, "Oh, this is a pain to set up, so I’m going to make something that makes it not painful to set up and I can reuse this in my tests!" That’s okay, it’s a valuable thing because while we’re learning how to code, that’s what we tend to do.
00:14:43.900 But TDD brings forth the idea that when you find something difficult to test, you should change the design. Make design changes your default behavior. Allow that to be your natural reaction to something that’s difficult to test. Test doubles often highlight pain. Many complaints people have about test doubles focus on things like fragility or coupling. Rather than avoiding test doubles or altering the way that we use them, why not take a look at what those complaints are telling you about your design?
00:15:34.720 Let your default response be to change the design so that it isn’t a pain to test. Here’s another picture of Zach just to wake you guys up!
00:15:56.600 Okay, so let’s talk about a couple of examples—common ones that people often mention. I run these workshops called code retreats. It’s not important to know what they are; they’re awesome! But I’m building an application that shows which code retreats are running on the day they’re held. They run on the weekends, so generally, a couple of them are available on Saturdays somewhere in the world.
00:16:38.390 My first pass at it looked like this; it’s a fairly idiomatic Rails way of doing it. Does anybody else have code like this in their app? For the most part, anyone awake? Okay, good! One person! So I’m looking at this; I have an index. I’m passing it over, I do a find on my model, and I pass in the hash: what's it scheduled for? Is it running?
00:17:03.509 When I create a test that retrieves the running code retreats for today, I create a stub, and there’s my hash. This is a pain and a very common one! Has anyone else run into this and thought, "Man, this sucks?" Okay! I’m going to stand here until people raise their hands because this is a valid complaint.
00:17:26.220 When you change the hash, you have to change your tests, and that’s painful! It feels fragile. It feels almost like I’m duplicating the implementation here! What might be suggested is to avoid using a test double, let's actually hit the database to avoid changing the hash.
00:17:54.800 But let's take that pain and think about it for a second. Why is this information relevant to the controller? Why does the controller need to know the fields on my model, how to set them, and what type they are? It knows that "running" is a boolean and this is how you find it out.
00:18:21.040 In my opinion, a controller's job should be to take the parameters from the HTTP request, send it to something, and then figure out what to render based on the result. When you do stuff like this, you bypass layers and mix the abstraction levels.
00:18:41.450 You let the controller reach into the model. This seems strange. Is it fast, because we have Active Record and I can pack out all of this stuff? Yes, I can write my blog in ten minutes. But most of us work on applications that need maintaining.
00:19:17.020 Most of us work where that hash is going to change. One of the things we’d like to abide by is that a controller should know what it wants, not how it wants it. The controller should look at it and say, "Okay, my job is to figure out what I want based on the request." So it’s better to insert a layer between the controller and the underlying database.
00:19:51.240 This layer should be some services that the controller talks to—services that clearly represent the functionality. Has anyone ever read this paper? No? It’s a phenomenal paper by a guy named David Parnas. This is one of those papers where you go, "Did they really know everything back in the 70s?" Or rather, at what point did we lose all of that knowledge?
00:20:20.380 This guy wrote a paper on how to decompose your system into modules. They ran an experiment on a code base, splitting it into modules based on the activities instead of what it was supposed to do. They also split a system into layers based on the services it provided. They found that if you split your system into a more layer-cake design, it became more maintainable. You can swap out sections and change them more easily.
00:21:19.559 We have things like scopes, which provide an additional logical layer between the controller and the database itself. We might write a simpler example by focusing on what the index’s job is: getting today’s code retreats. By listening to that pain point, rather than saying, "The test double sucks because if I change that hash, I have to change the tests." This pain can influence our design.
00:21:48.509 Thus, we focus on what the index is interested in, not how it is provided, and our tests become clearer. An interesting thing about naming the example is that it retrieves retreats running today. If you look at the code, the description pretty much reflects what we are doing.
00:22:14.020 It's always better to delegate how to do something and focus on the usage. Let’s keep in mind, with TDD, it’s about design; test-first is focused on verification. TDD is fundamentally about changing your design, not your tests.
00:22:36.740 Here’s another picture of Zach focusing on the usage of this box. I like presenters in the Rails community because we argue about whether something is a presenter or not. I enjoy debating the semantics of it. Essentially, it’s something that provides a better interface to underlying data, focused on what the view needs.
00:23:14.570 I'm going to call it a presenter and, as I do with a lot of things, I might go a little willy-nilly with vocabulary. If it's incorrect, please bear with me. I'll look at tests for this: I wrap today's code retreats in this presenter, and it’s going to have some things that the view uses. Looking at the test, I set up a stub for today’s code retreat so it should receive today.
00:24:18.060 I have this cool thing here; today’s code retreats create a presenter that's a stub. I stub out the four methods and pass this stub into it, then ensure it gets assigned. Has anybody written tests like this before? There are a few, which is great! Here’s a weirdness: one of the fundamental rules is that test doubles should not return other test doubles. This can build a weird kind of inner-dependency.
00:25:07.160 If you think about how many different ways this test could have to change, there are too many! I know that I’m getting a code retreat presenter back, and I’m passing in something; it feels like I’m bouncing between layers of abstraction. What I’m trying to do is simplify the example and make it clearer.
00:25:37.300 We should start by asking, "What would make this better, clearer, or more maintainable?" This is not very clear, and we want to simplify it so that we just ask the presenter for today’s code retreats. Here’s where we may get into arguments about the role of the presenter.
00:26:03.840 We simplify the example by saying, when I ask this presenter for today’s, I get back the code for JSON. By focusing on making the example simpler, we notice the code becomes simpler! It stops having abstraction-jumping; it focuses back on what the controller needs: today's code retreats.
00:27:03.740 We can really start to think about the APIs: think about the service names, identify the pain points in the tests, and determine where discomfort lies. We need to look at the parts of the system we can get away from. I don’t have to load every component to do the refactoring.
00:27:35.560 Instead, we can focus just on one small part of the design with examples to clarify where pain highlights the issues, and we go through the phases of refactoring. There are four rules of simple design—has anyone heard of these before? A couple of you. The four rules were codified by Kent Beck back in the late 90s. He’s generally considered the father of test-driven development.
00:28:23.750 Kent coined these rules based on his observations of design and the core concepts we should focus on when building our systems. We love to talk about SOLID principles, design patterns, and all of those great, high-level concepts that make our design sound awesome. But if you boil it down to the very core, it's the four rules. The first is: test pass. It basically says, if you can't verify that your system works, it doesn’t matter how good your design is.
00:29:21.080 You could have the greatest design ever, but if you can’t verify how it works, it doesn’t help! In our modern age, we consider most of these automated because if you have a two-week QA cycle or a one-month QA cycle, you put your code out there, wait a month, and in a couple of days, bugs start coming back and you say, "What happened?"
00:30:06.310 The second rule is: reveal intent. This is really about naming! You isolate yourself from the rest of the system and look at the system as layers that you can stub, getting a lot of benefits from that. You work on the names that apply at that level without worrying about the implementation below it.
00:30:46.540 The third is: no duplication. Many people have read The Pragmatic Programmer. If you haven’t, you should! In it, they coined the term DRY—Don’t Repeat Yourself. This is interesting because it doesn’t just apply to code. Many people think if you write something twice, you should extract it! Some people have a three-strike rule: if they write it three times, they extract it.
00:31:27.820 What DRY really states is that every piece of knowledge in your system should have one and only one representation. When you put duplication in context, it changes how you think of your design. Isolate yourself from a lot of the system so you can highlight knowledge centers, those areas that capture information. The fourth rule is: small. After you expand your code, eliminate duplication.
00:31:59.310 You can analyze and see if any parts can be collapsed! So these are the core rules we should focus on when refactoring and writing tests that isolate ourselves from the overall system.
00:32:31.690 I have a take-home challenge for you! It’s an interesting one because it's something that many of us face. Here’s my code retreat: I have logs and validations, which means you must have location, facilitator, and host. There’s my scope! I always test scopes with actual database queries. If I'm testing something generating SQL, it makes sense to hit the database to make sure the SQL is right.
00:33:24.400 You can create various events—today, yesterday, and tomorrow—and when you run the test, it should just have today’s events. But this has a lot of coupling in it: I must put this facilitator and host in my test, which adds unnecessary noise. This gives me false positives. This style of test does not check if yesterday's or tomorrow's events exist.
00:34:15.070 If it fails because of validation, it will not care if tomorrow's event doesn't create! This is a testing pain. I’m trying to verify it checks scheduled events, but I have to include extra information. So the take-home challenge is to think about how we can change the design to fix this. How can we do this without the unnecessary noise?
00:34:50.000 That was it! Thank you! Here’s a picture of me with my cat! It looks like I have seven minutes if anyone has questions.
00:35:05.810 Oh yes! Yes, I’ll put it at the end of the slides; I believe the whole app is up there! Any other questions?
00:35:38.190 No? What’s that? Two questions about holding a presenting question? No? Awesome! We'll have the rest of the day... Aaron? No, it is Comic Sans. That’s the bitly name. It's actually a link to a McSweeney's article, and it’s worth reading. Comic Sans is the ultimate presentation font!
00:36:08.710 Any other questions? Yes.
00:36:42.010 Okay, here we go! Any other questions or random things you’d like me to do? I’m learning how to do the running man, but I'm not ready yet! Probably Australia! I’ll try to get Aaron to dance with me!
00:37:08.490 So the challenge here: this is something I find annoying. These validations tightly couple the rest of your code to this, creating an interesting design challenge. What would you change about how validations work? It’s arguable that this is suboptimal for building something decoupled.
00:37:50.590 It’s a trade-off between speed of development. I don’t even know if that’s a trade-off, but it's an interesting one. It’s fundamental to Rails to have these validations.
00:38:27.820 Mm-hmm.
00:39:00.620 Hmm.
00:39:12.000 So, yes, there's this layer, this presenter layer, and there's this logical layer of scopes. The pain arises when you have to keep all these layers in your head at the same time.
00:39:29.710 My response is: why do you have to keep all these layers at once?
00:40:00.020 Mm-hmm. A lot of it boils down to what value you get from that! What pain are you trying to resolve? Is the integration test ensuring you’ve implemented all of them?
00:40:18.420 So one of the common concerns I hear is that if I change this one thing, then this one referenced object shouldn’t change. The concern also arises about keeping your changes organized.
00:41:01.370 It’s possible that you’re building one layer and then establishing the underlying layer it’s calling. Okay, so let’s try a different approach!
00:41:23.600 Rather than moving from the top down, look for a more vertical slice through your design! Do you ever find you change something lower and it affects what is above it?
00:41:48.540 No, it’s actually a comment, and it seems that it’s a fear many developers have.
00:42:06.030 Many developers carry a concern that change at the lower layer will have a chain reaction that disrupts everything above it. My experience is that it rarely happens. It could be a fear, or it could be just an aversion to change.
00:42:29.570 Once or twice? But the added complexity of not changing often leads to greater effort, and in my experience, it doesn’t pay off.
00:42:48.920 But, check out 'Surrogate' because it's designed to counter that fear. Good! With just fifteen seconds left...
00:43:10.660 That’s it. Thank you very much!
Explore all talks recorded at Aloha RubyConf 2012
+13