We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

A Guided Read of Minitest

00:00

Formal Metadata

Title
A Guided Read of Minitest
Title of Series
Number of Parts
66
Author
Contributors
License
CC Attribution 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Identifiers
Publisher
Release Date
Language
Producer
Production PlaceSan Antonio

Content Metadata

Subject Area
Genre
Abstract
Minitest is a testing library of just 1,500 lines of Ruby code. By comparison, Rspec clocks in at nearly 15,000! Why is Minitest so small? I'd like to investigate by doing a guided read of Minitest's source code.
26
Thumbnail
44:21
Reading (process)Software testingReading (process)CodeSoftware testingComputer programmingXML
Stack (abstract data type)EvoluteElectronic mailing listCodeSoftware testingMereologyQuicksortComa BerenicesComputer animation
Software testingDirection (geometry)Process (computing)Line (geometry)Computer scienceObject (grammar)WordInstance (computer science)Programming languageQuicksortSoftware maintenanceMathematicsFlash memoryProgrammer (hardware)Multiplication signTask (computing)Real numberComputer programmingComputer animation
Software testingTrailData compressionGroup actionQuicksortSingle-precision floating-point formatCodeMereologySource codeComputer animation
Line (geometry)CodeComputer configurationPairwise comparisonTrailSoftware testingTask (computing)InternetworkingComputer animation
InternetworkingMachine codeInternetworkingSoftware testingComputer animation
Software testingSoftware frameworkLine (geometry)Reading (process)Revision controlCodeBitComputer animation
Library (computing)CodeReading (process)Row (database)Revision controlExecution unitSoftware testing
Machine codeSingle-precision floating-point formatFormal languageLine (geometry)CodeData structureLibrary (computing)Projective planeFault-tolerant systemProcess (computing)Scripting languageReading (process)Computer fileSoftware testingFunction (mathematics)MultiplicationForm (programming)
Machine codeFunction (mathematics)Numbering schemeCodeLine (geometry)Form (programming)Software testingConnectivity (graph theory)
Component-based software engineeringCore dumpLine (geometry)Connectivity (graph theory)Plug-in (computing)Core dumpHydraulic jumpTotal S.A.Expected valueSoftware testingNumbering schemeLine (geometry)Computer fileLibrary (computing)Entire functionTheory of relativityPairwise comparisonComputer animation
Total S.A.Total S.A.Theory of relativityQuicksortPairwise comparisonSoftware testingNumbering schemeCodeLine (geometry)Point (geometry)1 (number)Computer animation
Line (geometry)1 (number)Library (computing)Software testingLine (geometry)ResultantGoodness of fitComputer fileDifferent (Kate Ryan album)Multiplication signStatistical hypothesis testingDefault (computer science)Computer animation
Software testingObject (grammar)DichotomyStatisticsSoftware testingInteractive televisionLatent heatObject (grammar)Set (mathematics)Power (physics)Multiplication signChannel capacity
Channel capacityCodeMultiplication signSoftware testingPower (physics)
Configuration spaceSoftware testingComputer fileLine (geometry)Configuration spaceInstance (computer science)Core dumpTotal S.A.Computer animation
Software testingWordCodeConfiguration spaceQuicksortPoint (geometry)Connectivity (graph theory)2 (number)Reading (process)Contrast (vision)Set (mathematics)
Metropolitan area networkError messageVoltmeterContext awarenessCodeLine (geometry)Software testingSingle-precision floating-point formatFerry CorstenTask (computing)Variable (mathematics)HookingExpressionRight angleInstallation artLibrary (computing)Social classOvalFunction (mathematics)Different (Kate Ryan album)Set (mathematics)Pattern languageParameter (computer programming)Endliche ModelltheorieDean numberType theoryInheritance (object-oriented programming)View (database)Arithmetic meanShape (magazine)Computer programmingGreatest elementContrast (vision)Spectrum (functional analysis)Instance (computer science)Computer fileQuicksortBlock (periodic table)Execution unitProper mapProcess (computing)CausalitySystem callComputer animation
QuicksortTerm (mathematics)Interface (computing)Software testing
Computer configurationDigital filterTerm (mathematics)Hash functionHookingCodeTask (computing)Ferry CorstenLeakSoftware testingShape (magazine)Line (geometry)BitDifferent (Kate Ryan album)Data compressionFreewareOrder (biology)DichotomyRight angleTraffic reportingMultiplication signResultantSymbol tableQuicksortMachine visionPRINCE2Sound effectSet (mathematics)Computer configuration2 (number)Level (video gaming)Axiom of choiceHash functionGoodness of fitComputer animation
CuboidLocal ringSoftware testingLibrary (computing)CodeVariable (mathematics)Computer animation
Software testingThread (computing)Software testingSocial classInheritance (object-oriented programming)Variable (mathematics)TrailTask (computing)CodeMultiplication signObject (grammar)Computer configurationLibrary (computing)Different (Kate Ryan album)Internet service providerPhysical systemParameter (computer programming)Revision controlStandard deviationDifferenz <Mathematik>Data compressionEndliche ModelltheorieProjective planeArray data structureLine (geometry)Thread (computing)ParsingString (computer science)Computer animation
Local ringThread (computing)Variable (mathematics)Social classCore dumpCurvatureRevision controlAlgorithmThread (computing)Social classSoftware testingLocal ringTotal S.A.CurvatureVariable (mathematics)Level (video gaming)ImplementationCodeStandard deviationReading (process)Core dumpLibrary (computing)Suite (music)Computer animation
Process (computing)Physical systemCodeAuthenticationSoftware developerLine (geometry)Library (computing)Social classSoftware testingPoint (geometry)QuicksortDirected graphTask (computing)Computer fileMessage passingLecture/Conference
Software testingNeuroinformatikSoftware testingHookingSocial classExpressionMetadataArithmetic meanRegulärer Ausdruck <Textverarbeitung>Computer filePatch (Unix)Different (Kate Ryan album)String (computer science)Multiplication signFerry CorstenSlide ruleSemantics (computer science)QuicksortImplementationLattice (order)WordComputer scientistTask (computing)System callComputer animation
SurfaceReduction of orderSystem callService (economics)AliasingSoftware testingConfiguration spaceMessage passingLine (geometry)Task (computing)String (computer science)Social classMultiplication signDistortion (mathematics)Error messageException handlingData structureWritingProduct (business)System call
Pairwise comparisonLine (geometry)Expected valueMereologyMatching (graph theory)QuicksortParameter (computer programming)Block (periodic table)Computer animation
System callAbstractionAbstractionSoftware testingGodChannel capacityProgrammer (hardware)Computer animation
Multiplication signGodSoftware testingBitHydraulic jumpCodeComputer fileComputer programmingVideo gameXMLUML
Slide ruleSlide ruleSoftware testing
Transcript: English(auto-generated)
Um, oh, boy.
All right, so my name is, uh, Nate. I'm gonna talk into the mic while I do this here. Uh, and today we're gonna talk about Minitest. I know on the program it said, uh, guided read, and we are gonna read code today. This is basically a code-reading talk. Um, but, uh, as I was writing this talk, I realized it's really about pain,
um, and the pain that testing makes us feel. Um, so we're gonna talk about pain. Uh, you probably could have titled this talk, um, something else. Uh, Minitest, it hurt me, and I liked it. Um, my name is Nate Birkapek. Uh, I write about full stack Ruby at performance
at natebirkapek.com. Performance is my usual wheelhouse. Um, I'm not, like, a testing guy. I don't usually write about testing. Uh, testing is sort of the last thing on, on my, uh, code skill improvement list sometimes. Um, but, uh, Minitest played a huge part
in my sort of evolution as a tester, and as I'm gonna show you as a, as a Rubyist. So that's why I wanted to come and, and talk about it here to you today. Um, uh, wrong way. Um, so I'm gonna start off by telling you a story. Uh, I started learning to program in, um, 2010.
I was a college senior. Uh, Ruby was my first programming language. Uh, I wanted to get a job at a big, flashy startup, and, uh, I thought the best way to do that would be to become a programmer. Um, turns out that was true. Um, but, uh, when I started learning Ruby, I, I used Michael Hartle's Rails tutorial.
I had no computer science background. Um, and if you've ever looked at that, uh, you know Michael Hartle does a great job of, of teaching you TDD from the start. Um, but he uses RSpec. Uh, and for a long time, when I started with Ruby, um, testing was really hard for me. Uh, I was just getting started with Ruby.
I barely understood what I was doing, uh, with Ruby to begin with, and then along comes RSpec, and there's sort of like, well, you gotta learn describe and it and all these other kind of words that RSpec concepts that RSpec brings to you. Um, and says you have to learn those too. And for a while that was really frustrating to me. And then I found Minitest.
And, uh, it was kind of like a brand new day for me. It's like, oh, it's just Ruby. I can just write whatever I want. You might have heard this sort of line before if you've ever done anything with Minitest. Um, I knew the API was very simple, and it was a lot easier for me as a beginning Rubyist to write, but I came upon this, this, this test, and I, I wanted to stub, um, an object.
I wanted to stub, uh, an object's, uh, instance method. Um, and, uh, I was really frustrated because it seemed like there was no way in Minitest to do that. And, uh, I went into the Minitest IRC channel, and I said, guys, this is dumb.
RSpec makes it really easy to do this, and why won't you let me stub an instance method? And, uh, Mike Moore, uh, the maintainer of the Minitest Rails gem, among other things, um, I keep going the wrong direction here, said, well, that's because we already have it in Ruby. Um, it's called a singleton method.
I was like, what? You do that? Uh, and that sort of started this long love affair with Minitest where Minitest kept showing me parts of Ruby that I'd never looked at or I'd never understood. Um, and, uh, so by reading Minitest source code,
um, we can sort of re-engage, um, with Ruby. In this way, pain, feeling this pain of, oh, it's really hard to do X, Y, Z, uh, led me to learn something new about, uh, Ruby. This is a talk about Minitest. It's in the less code track,
so I do have to address the elephant in the room, and that is RSpec. Um, I think the original, uh, uh, CFP or something for this conference, this track specifically, said that less code was, like, 500 lines or less. Minitest is not that small. Um, but it did, I did get into the less code
track anyway, and I think that's because we all understand, um, that Minitest is small in comparison to the other options that we usually reach for when testing in, uh, Ruby. Um, but I'm not here to play Internet Fight. I'm not here to talk about RSpec. I'm here to talk about, um, Minitest, but, uh,
and, and to get it totally out there, I mean, it's pretty obvious I've came to give a talk about Minitest. I am a Minitest guy, and, uh, so I want you to take everything I'm saying here with that grain of salt in mind. I don't want you to, uh, swallow everything I'm, I'm saying here, um, just wholesale. Um, but this talk is about the philosophy of Minitest
revealed through its code, and, uh, we can't really talk about that without talking about, um, RSpec, because a lot of what makes Minitest's philosophy so interesting is what it doesn't do, and we can't talk about what something doesn't do without talking about something that does it. Um, and in that way, I think Minitest is, is best
thought of as a reactionary testing framework. Um, it's a, uh, reaction to other kinds of, or other styles of testing. Uh, bit of a history lesson here. The original Minitest, uh, was 90 lines of code. Uh, Ryan Davis, sitting
right up here in front, was nice enough to resurrect that version for me, uh, and it's available there at tinyurl.com slash original Minitest um, for an interesting read. Um, and, uh, if you've ever wanted to know what being terrified feels like, it's talking about someone's library while they're sitting in the front row.
Um, uh, and Ryan has said in other conference talks that it was originally a replacement for test unit. He sat down, was, uh, saddled with the maintainership of test unit, thought it was too complicated to even, uh, understand and wanted to see if he could write something that did what test unit does, but in less code.
Uh, and it's from that beginning that we have Minitest that we, uh, have today. Before we actually read a single line of code though, um, I think you can learn a lot from a project's file structure and its general, um, line, where its lines of codes live and how many there are. So, uh, before I dig
into a library, I think the, one of the greatest things you can do is open up Clock. Um, it's this little library for counting the lines of code in a project. It's smart enough to break things out by file, to break things out by language, so if you have multiple languages in a project, it'll say, oh, well this, it's got 10,000 lines of Ruby and 5,000 lines of JavaScript and so on and so on.
Um, and if we apply Clock to Minitest, you get this kind of, like, crazy output. Um, but the gist of it is, uh, Minitest has 1,699 lines of documentation, um, in the form of its readme and all the non-blank
comment lines. Um, and just 1,586 lines of Ruby code. Um, that's also blanks removed. Um, that maybe sounds like a lot, I guess. 1,000 is like a big number. Um, but uh, let's take a look at
something else. So RSpec has four major components. Minitest lives in one gem. Um, there are, there's a huge plugin infrastructure, but the, a few gem Minitest, there's no Minitest dash a million other things that get required. Uh, RSpec has four major components that get required when you, uh, add RSpec to your gem file. That is
RSpec core, expectations, mocks, and all three of those depend on something called RSpec support. In total, that is 1,500 800 and 43 lines. I just messed up saying that, but it's a lot. It's a big number. It's so big I can't even say it. Um, it's,
Minitest's, uh, entire library is about as big as RSpec's support library. Um, so Minitest is about one tenth the size of RSpec in total. Um, if Minitest was the mouse in the motorcycle by Beverly Cleary, RSpec is Ulysses by James Joyce. That is a literal relative size comparison. Um, I'm not, uh,
sure how far that metaphor goes, but uh, Ulysses involves some guy getting plastered, wandering around, and not much happens, uh, which sort of describes my, uh, experience reading RSpec in preparation for this talk. So, that's Minitest's philosophy point number one. Um, do less
with less. Um, you don't go from 15,000 lines of code to 1,500 just by cutting out, like, waste and being a better Rubyist than everyone else. Um, you get it by cutting scope. And, so, when we go in and start reading Minitest, we're gonna be looking for the things that Minitest doesn't do.
Uh, as another good example, RSpec includes 1,639 lines of formatters, um, just for printing your test results. Um, there's an HTML formatter, JSON formatter, a whole bunch of other ones that I don't know what they do, um, but Minitest spends its entire library doing what
RSpec does just to show you what it did. Um, another, uh, so, moving on from that, I guess, um, RSpec mocks, and this is a, this is a particular example, I think, of of a difference in priority between RSpec and Minitest is 28 times larger than Minitest's included mocking library.
You can use any mocking library you want with Minitest, um, but the one that's included is this Minitest mock.rb file, and that is 28 times smaller than what RSpec includes by default. Um, so, without even reading mock.rb or reading RSpec mocks, um, I think it's fair to say that Minitest
is a statist, uh, testing library. That Minitest is not in, like, the Stalin way, but, uh, in the, uh, statist mock.st, um, uh, testing philosophy dichotomy. If you don't know, um, what I mean by that, um, I think the late James Golic said it best. A statist tester asserts
that a method returns a particular value. A mock.st tester asserts that a method triggers a specific set of interactions with the object's dependencies. Um, and, uh, this is not, oh, wrong button. Um, so, we know
that if Minitest is gonna provide us 28 times less mocking power and capacity than RSpec is, mocking in Minitest will be hard. Um, we will experience pain with Minitest like I did, um, when using test doubles, mocks, stubs. Um, and I think Minitest is trying to tell us something with that.
It's trying to say, uh, don't do that. Um, if this is hard, maybe you should try something else. Maybe you should try code that doesn't need this, that doesn't need mocking. Um, so, that was kind of the first instance
of, I think, Minitest using pain to put us down a particular testing and coding path. Um, and, uh, here's another. The largest file in RSpec core is configuration.rb that is 758 lines. Again, roughly half the size of Minitest in total.
The largest file, uh, in Minitest is minitest.rb 446 lines. Um, and what I think Minitest is kind of saying here is that, uh, configuration, maybe this is like sort of the old Rails thing that we've always heard, like, uh, convention over configuration.
I've been trying to run with this pain theme here, so I think Minitest knows what's best for you. You better do it, dammit. Um, configuration is not a huge component in Minitest when compared to reading RSpec. Uh, Minitest is extensible. Um, and we're gonna see that in some of the code that we, uh,
read here in a second. Um, but we should not expect, uh, a huge amount of, of, of configuration or, um, uh, that sort of like, uh, you know, settings and that sort of business. Um, Ryan has called this turning testing up to 11. Um, I would say it means
that Minitest has a testing philosophy and it would like you to adopt that when using it. Um, it's designed to teach you a certain style, and that style is not necessarily up for debate when using Minitest. Uh, it's extendable but it's not really configurable and I would contrast that, um, with RSpec's approach which seems to
suggest that anything is possible with RSpec. Um, there's all these hooks, there's all these configuration points, ways to make RSpec do whatever you want. Um, and you can do that with Minitest, um, but maybe it'll be a little more painful than RSpec. Um, and maybe you should think twice before doing that. So, um, you might be thinking, Nate, uh, you've talked
for about 15 minutes now and we haven't looked at a single line of code and I agree. Um, so let's talk about Minitest. Uh, how does it work? Um, here's a typical, uh, extremely simple Minitest test. Um,
I want to call your attention to the I guess sort of concepts or like big, uh, things that Minitest is going to require you to understand, to use Minitest. Um, we require something, okay, cool. Like I'm a Rubyist, I know what requiring does. Or maybe I have some idea. Uh, there's a class, alright.
Uh, I've used those before, I've seen that once or twice. Uh, it inherits from something called Minitest test. So that's interesting. We need to understand what Minitest test is and what it's going to give us. Because as soon as we inherit from a class, right, we're going to get all of that class's methods, so, you know, Minitest test could have a million methods and
now we're working in that world, so who knows what's happening there. Uh, and then we have def, uh, we have a method, which is test the truth. I could make a bigger example here, but if you had a larger Minitest test, you would notice that every method, uh, or most of the methods started with test underscore something.
So that's interesting, probably has a meaning that we should look for in the code. And finally we've got this method, which probably, because we can guess there's an inheritance here, assert equal probably comes from Minitest test, uh, which takes two parameters. So those are the
concepts that we're going to be looking for digging into the code. If we run that file, just with Ruby, the command line tool, uh, we get all this output. We don't know how that output shows up. So let's start from the beginning. Requiring Minitest slash autorun. My guess would be that, uh, I should go looking in Minitest library
for a Minitest, or for an autorun.rb file in lib Minitest autorun, which is exactly what I find. Slightly simplified, I cut some stuff out at the beginning here, uh, but this is what that file looks like. It just requires some other files, uh, which is great, we know how that works, and uh, calls Minitest dot autorun.
Well, where does Minitest dot autorun live? Uh, it lives in Minitest dot rb, which was what we might expect if we were looking for a class method on Minitest. Um, and it looks like this. Now you might be thinking, Nate, I thought you were trying to sell me on Minitest, it was the simple testing
library, and look at how complicated that method is. And uh, I think that's interesting. And I, I had that same initial reaction looking at this code, and uh, I want you to pay, so does mine! Um, but I want you to pay attention to uh, the shape of this method. Like, if you
stood here and squinted and just paid attention to, alright, it's about ten lines, it's uh, fairly sequential, there's not like, uh, there's one sort of long line here, but otherwise it's pretty short. Um, and there's just one method here, right? Um, I think
yeah, like there's only one other Minitest method that's called here. Minitest dot run. We've got this some Ruby stuff here, like add exit uh, we're setting some variables and stuff, but there's only Minitest dot run. That pattern is gonna happen a lot more as we keep reading this library. Um, and I think that, that
pattern is in great contrast to RSpec, which I'm gonna get to in a second. Um, all Minitest is doing here is installing an add exit hook. Um, if you were wondering from the previous example, how does Minitest know to run our tests? Uh, it's required before we define any of our tests, so how
does Minitest know where our tests are and how to run them? Um, that's because when your program runs, there's nothing to do uh, cause there's no like code execute, there's no Minitest dot run at the bottom of your test file. Uh, so your program just exits and when it exits
uh, basically we do some other stuff and then call Minitest dot run. Um, nine tenths of this method is uh, defining some um, stuff to give you the proper unit exit, Unix uh, exit code, but really the meat of it here is Minitest dot run. Uh, if we just sort of uh,
collapsed uh, the add exit uh, block here, it would just say add exit, do some stuff unless class variable, install that exit, then set it to true. Clearly just meant to prevent us from running that method twice. Fine, not complicated.
This is how RSpec does that. Um, not that much more code, maybe like twice as much code, um, literally just does the same thing, installs an add exit hook, um, but I want, again, I want you to pay attention to the shape. Very different. Very interesting. Uh, one, two,
three, four, five and I eliminated like five other methods um, that RSpec defines to uh, carry out this whole add exit process. Uh, this is interesting, you probably can't read this down here, but uh, the disable autorun class method on RSpec just
sets the autorun disabled uh, instance variable to true. Um, that's an interesting pattern that you don't see anywhere in Minitest. I think it's a very interesting uh, expression of this is what RSpec considers readable versus what Minitest
considers readable. Minitest says you're a Rubyist, you know what this means, it means that we've installed something at exit. Um, and RSpec says well, we need to ask, we need to put that behind uh, this accessor that we've written here.
Very interesting, you're going to see that a lot. Uh, and it's also kind of funny uh, RSpec does all this to install this add exit hook and then tells you not to use it uh, because they want you to use the uh, RSpec test runner. When you type RSpec whatever file.rb uh, there is no test runner included uh, in Minitest.
Uh, you just use the Ruby command line interface. Although there are plenty of Minitest compatible test runners like the one you use in Rails. Uh, that's the better view of that example I just gave you there. Um, again, very interesting differences uh, in opinion on what is readable um,
and what is not. So I think that this means that Minitest is trying to uh, tell you that Minitest thinks that you should do the simplest thing that could possibly work. Um, and I want you to note the differing philosophies here on simple. Um, RSpec in terms of its testing interface as well
tends to think that simple is uh, English readable or looks vaguely like English. Um, and that's sort of the whole spec syntax um, mindset. Um, but I think in a lot of ways Minitest says well you're a Rubyist. Uh, Ruby is simple. Um, just read Ruby.
Uh, at the end of that at exit hook that we installed earlier, you'll remember we called Minitest.run. And that's what determines the exit code. Um, let's look at Minitest.run.
Alright, so right off the bat, before you even get to, again, look at the method shape here. Very different, um, than a lot of what you find in RSpec. Um, and I'm picking, I'm kind of picking out some of the most complicated stuff in Minitest. Like, this whole stack of, Minitest goes to this whole stack of methods before it actually runs your test. And it's kind of the more complicated stuff in Minitest. Um, there's a lot that's more
that's a lot simpler than this. Um, but right off the bat, before we even get to the second line here, check this out. Self.run reporter options. Where the hell are the parentheses here, Ryan? Where'd those go? Um, so let's talk about Seattle style for a little bit. Uh, for the uninitiated,
uh, Seattle style, I think has a lot of different definitions for the purposes of this talk. I'm just gonna say um, it's, it's sometimes interpreted as don't use parentheses and method definitions. Sometimes you go further and say don't use parentheses except where absolutely necessary. Um, which kind of leads to some interesting
uh, style choices. It's not super, um, popular. Uh, David Brady gave this great example of, of, um, parentheses usage. Uh, I'm gonna preface this by saying this sort of, um, code that you're looking at right now does not exist in Minitest.
This is not, um, something that you'll, similar that you would see in Minitest. I'm just making this up. Um, we've got hash dot fetch delete and then puts, right? I agree that parentheses makes this more readable,
right? Parentheses are sort of like this like tunnel vision. You can say, alright, what's the innermost parentheses here? Okay. The foo symbol, alright, let's go out one level, okay. Hash dot fetch, okay, so we're gonna fetch the foo symbol from this hash, alright, outside of the next set of parentheses, alright. Array dot delete, okay, we're gonna delete that thing from
an array, got it. And then we're gonna put it to the screen, okay, fine. But maybe that's not the dichotomy here, maybe that's not the uh, reason that we're not using parentheses. What if that code looked like this? Uh, this is a contrived example, so I don't think it
plays as well, but I couldn't think of a, of a good actual code example for this. Um, what if we blew the original code out into three lines instead, did something like actually describe what the thing was that we were getting out of this hash, um, and then putting the side effect onto a different line. Uh,
just an idea, I think this is a lot more readable. Um, I think it might be interesting, I've honestly never considered this until I read the Minitest source code all the way through. It might be interesting to, um, take parentheses out of your code when you're writing it, and then see what that makes you do. What does your Ruby code have to look like when not using
parentheses in order to be readable? Is that result more readable than, uh, using parentheses at all? An interesting exercise. I think uh, I'm gonna, I know I'm gonna be doing that, um, in the future just to see if I like it. So what I'm trying to say with all this is that I think Minitest has a philosophy that
pain is good. Um, and we can navigate our coding with pain. Um, we can use it as a kind of signal, as a kind of, well I feel pain here, so what does that mean? What should I do? Um, is there a way I can make this go away without just hiding it? Um,
for, like I already talked about the mocks example, I think what Minitest is saying there is mocks are painful here, try writing code that doesn't need the mocks. Um, rather than coming up with a better mocking library. Um, or a mocking library that makes this code easier. Um, Minitest also makes adding assertions
very easy, uh, which I'm gonna talk about in a second. Um, when it's doing this, I think it's saying, it's trying to give you the opposite. It's saying, well, assert, making new assertions are easy in Minitest, so I am encouraging you to write your own assertions. Um, RSpec makes it easy, but it makes it difficult to understand. Um,
Seattle style can, uh, encourage the use of local variables, intention revealing method names, um, and perhaps, I think, uh, the Seattle style tries to suggest to us parentheses are sometimes a band-aid um, on a problem that we should fix without them.
So, I've kind of outlined how Minitest, um, starts running your tests. How does it actually know where your tests are? How does it know how to run them? Uh, Minitest has this class runnable, um, which Minitest test inherits from this runnable, uh,
and basically, I've omitted a ton of code from runnable here, but, uh, all it does is keeps track of this class variable, runnables, simple enough, and then you see this method, class method, inherited, which just adds something to the runnables array, and then calls super.
I'd never seen this before, so I had to go look it up. I, like, first searched through the project and said, well, where is inherited defined? It's not in the project. Okay. Well, it must be something in Ruby. Um, inherited is a callback that you get for free. I think it's on class. It might be on module. Uh, and it's just called
anytime that, uh, you uh, get a new subclass defined. Uh, this is just straight from the Ruby documentation as an example. Uh, if we subclass foo, uh, this method will get called with the new, uh, subclass, uh, as an argument. So, literally,
all that's gonna happen here is my test will get passed as an argument to minitest runnable inherited, which will add it to the runnables array. Uh, eventually, minitest looks at the minitest runnables array for all of the test classes that it knows about and just runs through them one after another.
Um, I'm not gonna talk about, uh, test discovery in RSpec. I ran out of time. Uh, standard lib is used very heavily in the minitest source code. Um, we see things like option parser, thread, mutex, stringio, tempfile. Uh, and,
uh, in something I can't say I've never really seen before, uh, minitest will use your system diff tool, um, when printing, uh, the different, uh, the difference between two large objects. Like, if you assert equal some huge array, some other huge array, and, like, one thing in that huge array is different, um,
minitest will use diff, um, to figure out which, uh, thing was different. Uh, RSpec implements their own version. You know, RSpec implements that in Ruby, and minitest instead says, well, I'm just gonna use what's provided. Um, interestingly, a lot of RSpec, um,
re-implements a lot of core and standard lib classes. Uh, they have their own version of set, they have their own version of flatMap, they use their own version of thread local variables, and they even have their own ran implementation. Um, some of these I understand. Uh, these two, I think, have Ruby version issues. Um, not sure about
thread local variables, but threading's a total, you know, put your hands up thing in Ruby, so maybe there's a good reason for that. Rand, I don't know. Um, I was looking at, I don't think I put the example in here, but, um, RSpec uses its own algorithm for setting the random seed, uh, in your test suite.
Uh, there was some code comment that this implementation that they use is, like, just somehow better. That's it, it's better somehow. I don't know. so I think Minitest has a philosophy to use what is given to you. To use what is given to you by the system, to use what is given to you by
Ruby itself. that's an interesting philosophy that I think is something that we're really not used to as Ruby developers, and I think it's to our detriment. Um, a lot of us have a kitchen sink philosophy when it comes to our gem files. Uh, if there's some library that will save me ten minutes
of effort, um, that I can put in my gem file, then I will do it. Um, Devise, I think, is kind of the worst offender here, not because Devise is a bad library, but because we use Devise to do simple user pass name authentication that we could just write ourselves or use Rails facilities for that and be done in, uh,
in ten lines of code, um, anyway, uh, so I think that's an interesting, um, philosophy point from, that we can take out of reading Minitest. So how do we actually define, um, a, a, uh, am I saying test here? Yeah, so how do we define a test? How do we define
the, uh, we, if we say a class file is sort of a collection of many, many different things we want to test in Minitest, how do we define one of those? Uh, this is in, uh, where is this? Minitest test, test.rb Um, and it gets called in Minitest.run, which is on a previous slide.
We ask, um, a Minitest test subclass uh, for its runnable methods. We say, dear subclass, please tell me which methods you would like me to run. Uh, on Minitest test, that, uh, is really, this is all just ordering them. It's just this
methods matching some regex which is just matching test underscore. Uh, the methods matching method is literally just a one-liner that basically just calls grep on the collection of methods and brings them back as an array of strings. Uh, so really it's just, okay,
well, a test in a test in Minitest is just a method that starts with test underscore. Also interesting, I think that this is not configurable. Um, there's no, you could monkey patch it, of course. Um, but it's not like some, there's not some hook provided that lets you change out that regular um, expression. Very different in RSpec. Um,
RSpec has, from what I can tell, 13 different ways to define a test or an example. Um, granted, they do a little different things in RSpec, uh, but there are three that do exactly the same thing. Example and specify. There's ways to define a test while also focusing and skipping at the same time and there's also
a pending example. Pending apparently has some other semantic meaning in RSpec. Um, and this method here is also provided for you in RSpec so you can do this yourself. You can say, well, uh, I want uh, the smiley face emoji to uh, define a test
method in my RSpec test so I can do that. Uh, Minitest says that doesn't matter, just do it this way, it's fine. Um, I, there's maybe some reasons for this, uh, but if you don't think that, uh, exit being a way to skip a test because of, I don't know, I think JUnit or XUnit used to do that, uh,
if that doesn't matter to you, then maybe RSpec is providing other things that don't matter to you. Uh, this is, the RSpec implementation of define example method does this thing called idempotently define singleton method. I'm not a computer scientist, so, like, I don't really know what this word means. I know what a singleton method is, but I don't know why they didn't just do that
instead of calling this other method that I guess does it for me. Um, there's some metadata. I don't know what metadata is. And, uh, well, this I understand. Examples, yup, there's some examples here, we're adding an example there, that's cool. Very different approach.
Um, so what I think Minitest is espousing here is to reduce our API services, let the user define their own aliases if they want and make it simple to do so. Um, and I think that kind of goes along with limiting configurability. Um, I'm kind of running out of time here, I'm okay. Uh,
yeah, just talk faster, Nate. Uh, let's talk about assertions really quick. This is an example assertion in Minitest. Um, the structure, as you can see, is really simple. If you look at, um, Minitest assertions.rb, they literally all look like this, they're all like three lines. Uh, it defines a message that we print if
the assertion fails, and then makes an assertion using the assert method. Uh, so, uh, Minitest doesn't really provide any facility for writing your own, um, assertions. It just says do this on your own tests. Just open up Minitest tests or
open up your test class and write something that looks like this. There's no specific, um, facility like what RSpec provides to, um, define a new uh, assertion or matcher as RSpec calls them. This is what assert looks like, not any more complicated. We increment our assertions count by one
and then run the test, the thing that we want to test, and if that returns true we just keep moving, and if it doesn't return true, we're going to uh, raise this special kind of exception which Minitest handles in its own way I don't have time to get into that, unfortunately. Um, and you'll, you might have noticed here that message is a proc, which is sort of
interesting, maybe a little more complicated. Originally it was just a string uh, but if you think about it, if we're calculating the error message every time we run the test, eventually that's going to get very expensive. Uh, so by putting it into a proc, we can only calculate an error message when we actually need it. You don't have to do that uh, when uh,
writing your own assertions, uh because if you, if it's just a string or whatever um, Minitest will just print it. Um, if you don't care about that or if it's not a big problem for you. Um, and the way that these get included uh, into your test, these assertions is we just include the assertions module in Minitest test.
We've all done that a hundred times, no big deal. Um, RSpec I honestly have no idea how they do this. Uh, the expectations gem itself is like three thousand lines and then there's shoulda matchers, which is another six thousand lines. Um, I just, I really honestly, I've sat down and read RSpec to try to give it an honest comparison
in this talk. This part of RSpec I could not understand. Uh, this is the facility that RSpec provides for defining your own matchers. Um, it, it's interesting because I think this is, this is very characteristic of RSpec. Um, it reads quite well if you like read it out loud in English. But um, these are all
sort of new concepts, which you have to go learn about and understand how they work. Uh, this define method, where this match method comes from I have no idea. Uh, and what these block arguments are actually doing I think are all questions that you have to go ask RSpec about. Whereas
this use a module and call this method that raises a minitest assertion. That's pretty simple to me, I guess. Um, what I think minitest is saying is that abstraction is the enemy. Uh, the pain here is that you will have to write your own abstraction sometimes. I think that's
OK. I think that makes us better Rubyists and I think, uh, learning to write abstractions well is one of the greatest skills that we can learn as programmers. Um, and God forbid that you learn some Ruby along the way rather than learning how to better use RSpec. Um, to extend and
use minitest in a greater capacity, you usually just have to write more Ruby. Um, with RSpec you have to learn to use RSpec better. Because it's a DSL that you have to interact with. So I think I hope this has shown through a little bit of code, um, though not as much as I would like, um, that
minitest in a lot of ways is a philosophy about pain. At least it is to me. Um, and maybe you've had pain with minitest in the past. Maybe if you think back, uh, and on a time that you've had to struggle with minitest or any time that you've struggled with Ruby really, um, did you just give up and go throw
devise in your gem file? Or go throw, uh, whatever, uh, gem you thought you needed to get this done without you having, God forbid, having to write any more code. Um, or did you listen to that pain? And did you think about what that pain was trying to tell you? Um, I think in programming,
like life, what doesn't kill us makes us just that little bit stronger. Thank you. Um, that has been my talk. Please go read minitest if you haven't. It's really easy to read, um, and, uh, go test
more. Um, and my slides are available at that URL. Uh, I guess I have five minutes, so if anyone has any questions, no? Cool. Thank you for listening to me yap 45 minutes. Appreciate it.