 So, I'm here to talk about what is certainly the most beloved and non-controversial gem in the entire Rails ecosystem. Of course, RSpec, yeah, exactly. Everybody loves RSpec, right? Right after this, Ryan's going to come out and give you a talk just about how much he particularly loves RSpec. In the meantime, I'm going to fulfill my role here and set up all the things that he's going to say in the next talk. So, as I said, everybody loves RSpec. RSpec offends me aesthetically with no discernible benefit. RSpec is a cargo cult. These also, by the way, unless you think this is a new argument in any way, shape or form, this is from March 2011. So this is a debate, obviously, that's been going on for a long time about RSpec's complexity and its utility. And I'll just, like, I'll own it right here. Like, RSpec is complicated. I'm not a member of the RSpec core team. I didn't write any of it. I use it all the time, but I have no ego wound up in saying that it's anything in particular, it's complicated internally. The internals of it are complicated. And while I do think that it has benefits that are discernible and I would be happy to go over that some other time, here, we're mostly going to talk about the complicated part. So when we talk about RSpec being complicated, and the things that I'm going to talk about here today are not so much the complexity of the domain-specific language that it presents to developers who are writing tests using it, but the internal complexity of it. RSpec, there are a couple of reasons why RSpec is complicated. I suspect the RSpec core team would talk about how expressive RSpec is and how much trouble there is a lot of stuff in the RSpec code base that is there specifically to allow you to express your tests in a way that is closer to natural language than it is to straight up Ruby. And a lot of the complexity of RSpec internals goes towards supporting that. RSpec's also very flexible. It supports two completely different syntaxes because the deprecated version two syntax is still there. It runs in a lot of different, under a lot of different Ruby, a lot of different versions of Ruby in a lot of different contexts on a lot of different machines. And obviously that's true of all popular frameworks in this ecosystem. And that has a certain cost in terms of the internal complexity of the code base. And it's big. It has features that other test libraries don't for good or for ill. It has them. It has a full-featured, a very full-featured mock framework attached to it. Has a very full-featured matching framework. If you're in JavaScript, you easily could be doing that with three separate libraries and RSpec bundles them all together. Just to top off here with one quick example of how flexible RSpec is, I did want to say that with about ten lines of configuration, most of which are things that are exposed by the RSpec API, I was able to turn this, this is executable RSpec, with my configuration. I mapped thumbs-up emoji to describe, eyeball emoji to it, pointy emoji to expect, and heart emoji to equals. Three of those four I did using stuff that is exposed by RSpec that we'll talk about in a little bit. But yeah, RSpec is like, it's not specifically there. The flexibility is not specifically there to support emoji RSpec, but if you want to do it, it's not that hard for good or bad. So this is RSpec. It's not actually magic. My name is Noel Rappin. I work at a consulting shop called TableXI in Chicago. I have stickers and we are hiring. And if you want to talk about either of those things, you can find me. That's why I wear the green hoodie. Hi, everybody. It's so hard. There's such a glare here that I can't really tell whether there are people out there. There are people out there, right? Okay. Hi, people. So we're going to be looking at some RSpec source code from the actual RSpec code base that current GitHub master has a couple of days ago. So this is your last chance to bail before we start looking at RSpec code close up. If you have sensitive stomachs or something like that, fasten your seat belts, make sure your tables are in their upright and locked position, don't avert your eyes. But I do want to say why is it important or why do I want to talk about the internal of RSpec? It's possible that some of you will come out of this learning a little bit more about RSpec as a user. You will probably pick up a trick or two that you could use in your own testing. That's not really why I'm doing this. RSpec's a really interesting example of a domain-specific language written in Ruby and it has some techniques that you might use if you want to try to write your own DSL. That's not really why I wanted to do this either. Really I've used RSpec almost every day of my professional life for about the past eight years and it occurred to me that I really didn't have a serious idea of what it was actually doing. And I thought that was worth rectifying just because I was really curious and wanted to know. So that's what I'm going for here. Hopefully you'll come out of this with a little bit more understanding of RSpec's internals. So I always tell people that technical talks, even technical talks, should tell a story. There should be a beginning, middle, and an end. My story, I guess, is once upon a time there was a test and we'll go from there. This is a minimal RSpec test. This is about as small as I could make it and still have it like recognizably do something. And we're going to walk through the stages that RSpec goes through to convert this first to internal representation and then to execute that representation and turn it into a pass or a fail. Now this kind of looks like idiomatic Ruby and it kind of doesn't. One of the things that I find often helpful when you are in Ruby exposed to domain specific language or something like that that is trying to look a little bit different than normal Ruby is to start to parenthesize it and fully qualify it. And if you do that, you come up with this. This is what happens. So there are a couple things that we can see right off the bat that are very clear once we parenthesize and qualify this. We see that describe is a method that takes one argument and a block. That argument is a constant name. We see that it is explicitly calling out the self receiver there. It's a method that takes one argument here and a block. And on the last line, we see that expect is an argument, is a method also of whatever self is at that point. It takes whatever comes out of expect is an object that expects to receive the method two. The method two takes as its argument something there that gets returned when I call eq. But it's worth noting that it, expect, and eq are all defined in terms of whatever self is when those methods get evaluated. And those are some of the key words of Rspec's internals or Rspec's DSL. We have describe it, expect, and then eq being a representative example of Rspec's matchers. And so here we have describe it, expect to something. Each of those matches, each of those maps to an external, to an internal object or class inside Rspec. So describe maps to an Rspec concept called an example group. Expect maps to an example. Expect maps to something that Rspec calls an expectation target. And then the two at the end is something that Rspec calls a matcher. If you're familiar with Rspec in terms of a user of it, you've probably heard example and matcher. And you may not have heard example group or expectation target because those are a little bit less used by the public face of the library. So just to sort of sketch that in terms of this spec, the describe defines the example group and the example group sort of encompasses the entire describe, the block that is the argument to describe. They all are sort of under the auspices of that example group. It, the example object is invoked by it and covers the block that is passed to it. Each individual called it gets its own example. The expect and its argument are the expectation target and then the matcher is that self.eq. So we're gonna walk through this step by step in terms of Rspec's internals. So it starts off when Rspec executes that code, when Rspec loads it, the first thing it hits is that describe call, which creates an example group. And describe is a method, it's defined, it's actually defined somewhat indirectly as a class method of example group to create new ones. One of the things about the RSpec code base and the RSpec code base examples that I'm gonna show you is that most code in RSpec has at least one more level of indirection than you would expect. So in some cases I'm gonna show you something that is along the way of the RSpec call stack and not exactly the method that you think you're calling because it's actually defined in terms of something else. Describe is actually, there's no place in the RSpec code base where you'll see def describe, RSpec has a method called like define example group method. And then describe is defined as an instance of that or a example of that, an alias to that. The examples to describe are the description, which is typically a string or a constant and then RSpec's metadata, which is a set of keys or key value pairs, which we're not gonna be talking a whole lot about except that RSpec just sort of holds on to them and can use them in its execution. And what describe, what happens when you call describe is that RSpec creates an anonymous subclass of example group and then executes the block argument in the context of that new anonymous subclass. So here's a piece of that code. This is from the part of example group. This is sort of called along the way as that example group gets created. The first thing that happens here is you see that we're using Ruby's class.new to create a brand new class. The parent of a top level describe is example group itself. And as you nest, they become subclasses of each successive nested example group in turn. Once you have that subclass, it calls out to a method called set it up, which you'll be surprised to learn does some setup. Specifically, it mixes in the matcher and the mock package. So if you're not using RSpec's mocks, that's where it gets mixed in. And then RSpec uses the module exec Ruby function to execute the block, that block argument, if it's there. And hold on to that thought for a second. And then it defines some extra helpers, and then it returns that new class. Whoops, then it returns that new class. So module exec, module exec is core Ruby. It's one of a sort of trio of module exec, class exec, and instance exec. And those are very frequently used by domain specific language implementations to control how a block gets executed and make a block not get executed in the context where it looks like it's being defined. And specifically, module exec has the receiver. This example is actually from the Ruby core documentation. Module exec, you can see in the middle, it has a receiver thing and then a block. And when I call module exec, that block is executed in the context of the receiver as a class. And what that means is that for the purposes of module exec, inside that block, self resolves to that outer object. And it treats the contents of the block as though it was inside effectively inside a class definition. So in this case, I'm defining a method and I'm effectively reopening the thing class and defining the method inside that so that once that module exec call is done, I can create a new thing and call that hello method on it and that will work. So if you look at what that means in terms of what the RSpec code that we are looking at. So RSpec describe turns this example group subclass and then we module exec that. And that block is where we have befores and afters and it's. And that block gets executed by RSpec in the context of that subclass. So when I say self.it that self inside the block refers to that example group subclass. And it is a method defined on that subclass as is before and let and all of those things are all method on example group that get executed here when RSpec does this module exec. So we call the method it and it in RSpec terms creates an example object. There are a number of different things that are defined to be able to create example objects. Again, RSpec defines a generic method and then defines it and specify an example as aliases to that or as versions of that. It takes as a description some metadata again and a block. And what happens when you call it is that RSpec holds on to that example object in that block and adds it to an array that is a class attribute of that anonymous example group class that is its parent example group. So here's a piece of this code you can tell that it's kind of several layers of indirection deep because I haven't even given you the method name here. But this is part of the code that gets executed as the example gets created from the RSpec code base. We have a bunch of stuff here that handles some bookkeeping around the metadata. This is also where if you've specified that this example gets skipped. This is where that gets handled. But basically what happens here is that RSpec creates a new instance of this example class, the arguments to that are self, which is the example group, the description, the metadata options and the block. It attaches that to an example's property of the group class and then returns the newly created example. So at that point, RSpec has now loaded all of the things that it needs to load. It's executed the block inside the describe. It hasn't executed the block inside the it, but it's holding on to it. So at run time, RSpec actually creates an instance of that anonymous example group class, and then the new instance runs all of its examples. And that happens in three stages. The example group has a method that sort of goes through all of the examples. That method spins off to a method that runs each individual example, and that method spins off to the example itself to actually handle its own run. So let's look at some of that code. This is a little small, I'm going to zoom in in a second. But this is what gets called on the example group when it gets executed. And there's about three or four stages to it. The first stage here just sets up stuff. If RSpec has already decided it wants to quit out, it just bounces right at the top, it tells the reporter or the formatter that an example group has started because the formatter might do something in response to that hook. And then it looks for context hooks for any of its descendants. Mostly this is mostly just bookkeeping. Inside begin block, it then looks for, it actually runs the before method if any exist. And then that second line there run examples will zoom in on because that's where it actually starts to run all of the examples. Once all the examples have run, that method returns a true or a false. It looks for nested children and runs all of them and determines a true or false for the entire group based on its own pass or fail status and the pass fail status of all of its children to results for descendants. And then it handles some exceptions. If it skips, if there's a skip declared, it bounces out. The bottom exception here is, I believe, called if there's actually an exception in a begin block or something, it goes there and tells RSpec it wants to quit if RSpec isn't in fail fast mode. That returns false because that's a failed spec. And then at the end, no matter what has happened in the insure block, it runs all of the after hooks. And then it tells the formatter that it's done with the example group, because the formatter, again, might do something in response to that. So it's only this one line, this one line run examples is the thing to zoom in on to see what it actually does for each individual example. And so this is the code that actually handles all those examples. The first line there, that order strategy order, is the examples that it plans to run, and then it maps them. So we're going to get a true false for every individual one of these examples by using map. And so, first it bounces again if RSpec has decided it wants to quit. It creates a new instance of the example group for each test, sets any instance for it to set, and then defers off to the actual example to run it. And if that example has failed and we're in fail fast mode, then RSpec tells itself that it wants to quit. And then it returns true false if it's exceeded, if it has exceeded or failed, and uses the all method, the map will return a list of truths and falses, and the all method will return true if all of the individual examples are true. So that's what the example group does. When the individual example gets control, it first checks to see that it's not pending and actually should run. It runs, it's before blocks, I'll put actually put the code up. Checks to see, so first of all it runs any before hooks, and then it, this is the part that actually runs the test, like after, you know, we're like 45 slides into this. This is the part that actually runs the thing that we think of as the test. It creates it, it takes that instance of the example group and it instance execs that block inside that group. If it's pending, we stop, we break out. If an exception has been raised because the test failed, we hold on to that exception because we're gonna need it for reporting. And then at the end we run clean up if there's clean up and then we finish which passes, whether this has passed or failed to the format, or again, because it'll wanna put a dot or an F for do whatever it does. Skeleton of what this is doing for blocks, but inside that it, it actually has to do something to determine whether the spec has passed or failed, which our spec does with expectations and with matchers. So that line of code in this test, that line of code in this test is the expect, we're expecting something to equal something else. And that expect method call, that expect method takes in an argument which can be an arbitrary Ruby object and it returns an RSpec object called an expectation target. The expectation target, basically all it does is it responds to two and it responds to not two to determine how it handles the matcher that it's been called with. Two and not two both take RSpec matchers as arguments and evaluate whether those matchers are actually match or don't match. And a matcher at its most simple is just an object that responds to the API is match's question mark. So RSpec's, what we had there was two EQ, the EQ method, which is defined, EQ is one of several built in methods that RSpec defines in a module called RSpec matchers. There are all of these shortcut methods that basically are just really thin wrappers around these matcher objects. In this case, the matcher object is called built in EQ. All of the things that you use as default RSpec matchers are defined in this one RSpec matchers file and most of them simply defer out to a more complicated object that handles the actual matcher. So if we resolve both of those pieces, we wind up with an expectation target object that's using its two method and it's being, the argument there is the matcher, the EQ matcher, which also has its own argument. The EQ matcher determines whether it matches or not by comparing whether the two values that's been called with are equal. So what happens here is that that expectation target two defers to a method that eventually calls match for the actual matcher. In this case, it returns true. The two strings are identical. The matcher boils that up as a success because it's been done, because it's been called as a, because we've used two. So we're looking for a positive expectation. It boils that up as a match and it succeeds. If we had been using not to, it would have, the matcher would still have matched, but the expectation target would have considered that to be a, not the result that we were looking for and it would have raised an exception that would have gone to the reporter that the example group would have held on to for final reporting. So that's the flow, that's the basic, that's, it's not really the basic anything, but that's the workflow. Our spec creates these example groups that create examples that use these expectations and matchers to determine whether what you've stated as your expectation is actually carries. There's one other trick that I wanna talk about here for our spec's matchers, which is the implicit matcher. So our spec has an implicit matcher where if you do B underscore anything as, so in this case, B underscore value, it defers to a predicate method of the object. So in this case it would look for valid question mark on my name object. And the way that that works is it's a method missing, here's the code inside the our spec matchers module. There's a method missing, which is invoked because B valid is not defined anywhere. And then the method missing compares against a regx. So we have a top up, it actually looks for two different kinds of things. The B predicate regx looks for B or BA or BAN. And then the has regx looks for have and just switches it around. The have regx lets you say something like expect something to have key rather than, and then it defers to the has key method so that your test sounds grammatical and not like a lolcat meme. The B one is what we're looking at here though. So B valid matches that B predicate and it creates, it actually goes to a matcher, this object built in B predicate, which takes in that method name and the arguments and the block and in the fullness of time comes to a some code inside that matcher where it checks to see whether that predicate is accessible and whether it matches. So it has some code that I didn't show that converts B valid to the valid question mark method. It checks to see whether that method actually exists and whether it will, sorry, it looks for it in either, it looks to see whether that method exists and then if the method exists, it uses a double underscore send to dynamically invoke it and return true or false, whether determine whether the matcher passes based on whether the predicate is true or false. It uses double underscore send here because that is much, much less likely to have been overridden by the actual object whereas it's at least theoretically possible the object might have overwritten a regular send. So the emoji trick, a brief interlude for the emoji trick. The emoji trick takes advantage of these define example group methods and define example methods that I showed before. This is also from our spec, this is from the internals of example group and it's just all the different defined example group method is the method that our spec uses to say what an example group should do when it's created and these are all the ways that you can by default create an example group in our spec. You're probably familiar with describing context, you've probably never used example group, you may have used X describe and X context which automatically pass along metadata to say that that example group gets skipped or F describe and F context which automatically pass along metadata to say that that example group has focus. You could do something similar with methods which you can use specify it, I don't know for some reason it fell off the slide but specify example, F it, X it all of which pass along metadata. So what I did then is I just reopened example group, formatting got a little strange here. I reopened example group and defined an example group method and an example method in terms of the emoji I wanted and inside matches I used the similar alias match method to use the heart emoji and then I just sort of brute force overwrote, there's no easy way to get at an alias for expect so I had to kind of brute force it. This is essentially a copy of what expect does and I suppose I could have used a straight alias, a regular Ruby alias and then with that then this code works. So if you want to define your own emoji and really earn the love and respect of your coworkers by doing so it's that simple. There's one other thing I want to talk about before I left which is I wanted to talk a little bit about how RSpec's mock package works. Mox again, not what Ryan is going to do to RSpec in the next talk but mox as test doubles. So here's a minimal mock example. We have a user, we say that we expect the user to receive a method called credit rating and return a thousand and then we call something else that has its own expectation but we've also set up by saying we expect the user to receive. We're setting up an expectation that that user will receive that method and this test will fail if that method does not get invoked. So I wanted to think for a second just abstractly like what has to happen in that line of code that to receive all the bookkeeping that has to happen for RSpec for the mock package to work. A couple of different things have to happen. RSpec needs to ensure that the underlying method is not called and then instead the value that you've specified is returned. RSpec also has to track how often that method gets called and with what arguments because that's used to determine whether the mock passes or fails and then at the end of the test it has to determine that all those expectations are met. In order to see how that's done we need to talk about two things. First is the way that Ruby method lookup works. You may or may not be familiar with this. Ruby method lookup first looks in a thing called the singleton class of the instance then it goes through instance methods of that class included modules, parent methods and parents included modules and so on. The part that I'm most interested here is the singleton class because that's the first place that Ruby looks and it's the place that's usually not used. The singleton class you may have seen one or two of these syntaxes. Every object in Ruby has this weird little thing called a singleton class that basically exists to define methods on. You can use that class shovel operator syntax to get at it. You can use, you can define something with an actual instance dot method name and in both cases what happens is you create a method that only exists for that instance and no other object in your object space which is perfect for what a mock wants to do. A mock wants to be a method that exists for that one particular object and no other objects in your object space. So what our spec needs to do effectively these are the concepts here. This is a kind of a bad diagram but we have the original object and it's singleton class and our spec sets up this structure where our spec has this concept of a proxy and a method double and space. And the method double actually gets pushed into the singleton class of the original object to be the first place that Ruby will look for the method to get called and to return the value that you specified rather than the actual method. The proxy holds on to information like how often that method has been called, what arguments and that kind of thing and then the space is the list of all of the proxies that are defined. So if you define two separate mocks, mocked methods on the same object, the space, our spec will use the space concept to ensure that they both of those stubs get attached to the same proxy. So let's look at a little bit of code because what happens here as our spec's getting called calling this as an argument to to receive actually is a built in that creates a matcher just like EQ did and that matcher is defined, that matcher just like EQ is a very thin wrapper around an actual larger object. In this case it's called matcher's receive and that takes in the method name being stubbed and an optional block because most things in our spec can take blocks but in this case we don't need to worry about that block. Inside the receive matcher, there's some bookkeeping that goes on but basically what happens is our spec sets it up so that it creates a proxy and causes this add message, expectation method to be called on it. Our spec looks at its existing proxy space to determine if there's a proxy, to get the proxy for the object being stubbed and then it creates this method substitute which eventually again, there's a couple of layers in direction here but eventually that add message expectation gets called. It creates this method double object and adds an expectation to it and then that method double object holds on to the original method, holds on to reference to it and then it uses class exec, in this case the definition target is the object being stubbed, class exec gives you the singleton class of that object and it's basically defining a method to go into that singleton class. That method has the name of the method being stubbed and the body of it is the method double basically doing whatever you specified whether you specified a return value or a block or a couple of various arcane ways that our spec works. That method gets pushed into the singleton class so that for that object and only for that object the stub method captures that method call and returns the behavior that you specified and also tells the proxy that the method has been received with the arguments because then the proxy can keep track of that and then at the end of the test our spec has an after hook which is specifically for the mock package to clean up after itself and what it does is it walks through the space, it goes to that space object and says are there any proxies for each proxy? It says do you have any methods doubles? For each method double it says do you have any expectations and for each one of those it says was that fulfilled or not given how many times this was called and then that happens after the test runs after all the tear downs our spec goes through and validates all of those and determines whether the test passes or fails. So that's a lot of stuff. I know this was a lot of code flying at you very quickly. It kind of scratches the surface. There are a lot of cool things that our spec does that I couldn't cover because I'm already pushing time and this was already like a freight train of code coming at you. I hope that you found this valuable. If you want more information on our spec, the RSpec documentation and RSpec info which is not the relish documentation that comes off the cucumbers, this is their straight documentation, is actually pretty decent. The code base is actually not all that hard to read once you get used to how it kind of indirect stuff or you can find your local RSpec core member. I happen to work with one and pester them to tell you how things work. If you don't happen to work with an RSpec core member, find one online and pester them. In the meantime, I'm Noel Rappan. Again, I work at TableXI. You can find me on Twitter at Noel Rapp. I occasionally write books. I have a couple that you might be interested in. I have a book on testing Rails stuff, Rails test prescriptions. It's really not so much about Rails as it is about testing in general through Rails. That's available at pragpocket.com. For a limited time only and I have no idea how limited because they didn't tell me. The code test2rapin should give you 25% off both the ebook and the physical book if you buy it through Pragmatic. Also, I do some self-publishing. I have a JavaScript book and what you can get at NoelRappin.com. I have a book called Trust Driven Development which is about projects and estimation and how to make your clients like you or how to behave worthy of your clients like you. Both of those are available at NoelRappin.com. Both of those will be having updates and price raises in the relatively near future. So if you want them, they will never be available for cheaper than they are available right now. Thank you for the time. I hope you found this valuable. Thanks for spending a little bit of your time with me. I hope you enjoy the rest of the conference and I hope you will recover from my fast talk freight train of code for the last 40 minutes. Thank you.