00:00:24.660
All right, let's talk about MiniTest. I want to start off with a poll: if you guys were to start a brand new project today, one which you had one hundred percent control over, which test framework would you choose?
00:00:32.349
Raise your hand if you would use Test Unit. Not that many! How about RSpec? A lot more. How about something else?
00:00:40.300
What would you use? MiniTest? Oh geez! Is there anyone here using anything other than MiniTest, Test Unit, or RSpec?
00:00:48.489
Okay, so today I want to talk about MiniTest. I've been using Test Unit for several years and about a year and a half ago I started using RSpec. I really liked it, but then a few months ago I started using MiniTest. Today, I want to share with you my experience and how its simplicity won me over.
00:01:05.429
My name is Jared Ning. You can find me at redninja.com. Thanks, mom and dad!
00:01:47.920
While preparing this talk, I Googled for simplicity quotes, and this one really jumped out at me. Frank Lloyd Wright said, 'The architect should strive continually to simplify; the ensemble of the rooms should then be carefully considered so that comfort and utility may go hand-in-hand with beauty.'
00:01:54.349
The reason this quote resonated with me is that it sounded almost like he was talking about programming. So, I did a little substitution: 'The programmer should strive continually to simplify; code should then be carefully considered so that intuition and functionality may go hand-in-hand with beauty.'
00:02:21.859
So, functionality—does your code do what you thought it was supposed to do? And intuition—when you look at your code, does it make sense to you right away? This is the big one: the ability to simplify means to eliminate the unnecessary so that the necessary may speak.
00:02:51.319
Committing to simplicity makes me happy, like this commit from John Layton. He referred to a refactor Aaron Patterson did, where John took 72 lines of code and replaced them with just 6 lines, achieving exactly the same functionality. I think most people can agree that simpler code is better—it's faster, more readable, and easier to understand.
00:03:32.410
We've been told many ways to achieve simplicity: shorter methods, don't be too clever, and avoid putting too much magic in your code. One of the principles that stands out is Occam's Razor. Wikipedia defines it as the principle urging one to select among competing hypotheses that which makes the fewest assumptions, thereby offering the simplest explanation of the effect.
00:04:06.579
I personally prefer Jodie Foster's definition from the movie 'Contact': 'All things being equal, the simplest answer is usually the right one.' So how can we apply Occam's razor to code? I think it applies to the red-green-refactor cycle.
00:04:29.480
In this cycle, you start by writing a test that fails (red), then implement the code to make the test pass (green), and finally refactor the code. You repeat this cycle until you end up with a codebase that you’re happy with. Anyone who's gone through this cycle usually finds that when you refactor, your code ends up being shorter, less clever, and easier to understand.
00:05:03.470
But does simplicity apply to all code? What about code that you don’t write, like tools and gems? Does this principle apply to your test framework? I definitely think it does.
00:05:23.760
In Ruby, we basically have two very popular choices: Test Unit and RSpec. It’s clear that Test Unit has been around forever, and we all know it well, while RSpec offers different features. But today I want to introduce you to another testing framework, which is MiniTest.
00:05:53.920
So what makes MiniTest awesome? Well, it’s small, fast, and although there’s a little bit of magic, one of the things I want to do today is show you what’s going on behind the scenes to demystify it. The greatest asset MiniTest has is its simplicity, and you likely already know how to use it.
00:06:15.610
MiniTest is essentially a drop-in replacement for Test Unit. This should look familiar to anyone who has used Test Unit. You still have a class, which subclasses something, and you define your tests by starting them with the prefix 'test_'.
00:06:43.229
You’re also still using assertions. While they removed some lesser-known assertions, I haven’t noticed their absence in practice. MiniTest also serves as an RSpec alternative, and for me to switch from RSpec to MiniTest, readability has to be maintained—that's what we love about RSpec.
00:07:17.949
But if we already have Test Unit, which is solid, could we possibly use it in a way that gives us an RSpec-like syntax without reinventing the wheel? I'd like the readability of RSpec just like I appreciate the readability of natural expressions, like '2 + 2'.
00:07:40.089
So here's the challenge: can we adapt the spec style to use assertions typical of Test Unit? Can we transform the code on the left into code that represents the RSpec style? For you RSpec users, this may not look familiar, but there was a time when RSpec was written like this.
00:08:11.270
The key takeaway here is the readability of the syntax. Now let’s do a bit of pseudo-TDD: our goal is to get the code on the top to call the code on the bottom. The first thing we need to do is define a method called 'should_equal' on any object.
00:08:47.750
That’s really easy; you just open the object class and define your method. Next, we need to be able to give it an argument. Now we actually have everything we need to call this assertion.
00:09:06.310
Because we are already inside the object, we can refer to the value as 'self.' And if we make one small modification—changing 'should' to 'must'—we are effectively performing MiniTest specs.
00:09:38.350
I've just shown you how easy it is to achieve spec-styled readability using plain Ruby without reinventing a whole new test framework. Here's what a spec-styled MiniTest suite would look like. This is a complete runnable suite and should look familiar to RSpec users.
00:09:58.870
We start with our 'describe' block or 'it' block. Inside it, we use what MiniTest calls expectations. Let me show you what MiniTest is doing behind the scenes with the 'describe' block: it creates a class out of it and subclasses it from MiniTest Spec.
00:10:31.650
'MiniTest Spec' is simply a subclass of MiniTest Unit TestCase, which is what we used before with Test Unit style. The 'it' block, in the background, defines a method that begins with 'test_'. While it does a bit more, the essential part is that it defines a simple method which begins with 'test_'.
00:11:06.760
When you obtain the MiniTest library, there's a file called 'history.txt,' which acts as a change log. As I reviewed it, a few lines caught my eye, including this line: 'removed as much code as I could while still maintaining full functionality.' That's an excellent practice if you are aiming for simplicity in your code.
00:11:27.010
Let's conduct another poll! This time, let's start with everybody's hands up. If you have already used MiniTest, put your hand down. If you are still using Ruby 1.8 and have not yet upgraded to Ruby 1.9, put your hand down. There are way too many hands still up!
00:11:57.810
If your hand is still up, I’m going to make an assumption about you. Put your hands down now. Either you're using Ruby 1.9 and have at least used Test Unit at some time or you're not testing at all.
00:12:16.560
If you’re not testing at all, excuse me while I go on a little tangent. Let me tell you the story of my experience before and after I started using testing. It truly took me quite a while to buy into testing.
00:12:51.640
Initially, I didn’t see the point of it; if you have a bug, just fix it and move on, right? But as you probably know, some bugs are harder to squash than others, and I often found myself trying to squash these bugs.
00:13:13.350
Unfortunately, these bugs kept coming back and causing me more grief. If you’ve ever battled these kinds of bugs in your code, you know that dealing with them can be miserable not just for you, but also for your coworkers.
00:13:49.170
When you’re not testing, you are breaking their code, and they’re breaking your code. You're cleaning up their messes and they're cleaning up yours. It usually isn’t fun. So, I began practicing testing and improved my skills, especially with Test-Driven Development (TDD).
00:14:16.750
One day, I woke up and realized testing was genuinely helpful. How did I not discover this sooner? I can't imagine working without testing anymore. This realization marked one of my most significant growth moments as a Ruby developer.
00:14:41.680
I still encounter bugs—just like everyone else—but these bugs are minor and more manageable. Occasionally, my users experience minor inconvenience due to bugs, but now I actually know how to squash them.
00:15:03.750
If you're using Ruby 1.9 and Test Unit, guess what? You're already using MiniTest! This is because in Ruby 1.9, Test Unit is built on top of MiniTest.
00:15:42.890
This means you get MiniTest for free with Ruby 1.9. The package comes in standalone gems, so even if you don't want to install a bunch of different Ruby patch levels, you can just install the gem whenever you want.
00:16:06.050
How hard is it to switch to MiniTest? If you’re coming from Test Unit, you won’t experience problems since it is really a drop-in replacement. If you’re switching from RSpec, it may feel mostly familiar, but there might be features that feel missing.
00:16:24.430
However, I assure you it doesn’t take long to adapt, and you will find that some of those features you miss can be accomplished with simple Ruby.
00:16:50.010
It includes many RSpec-like features such as before blocks, after blocks, let, subject, and specify. Based on comments in the source code, they included these elements to keep RSpec users happy.
00:17:31.340
When I switched from RSpec to MiniTest, the first thing I missed was the ability to define custom matchers. For example, if I wanted to check that 42 should be a multiple of 3, this is how I would do it in RSpec.
00:18:03.920
You’d define a matcher, give it a name, define your match block, and set your failure message. However, MiniTest does not have custom matchers. Since it uses Test Unit style, you first have to define the assertion.
00:18:35.160
So, we need to define an assertion, which checks if a number is a multiple of another number. If you go into the MiniTest assertions module, anything you declare there automatically gets included in your tests.
00:19:10.520
So we define the assertion, provide the arguments, build the failure message, and then call it. For anyone who has created custom assertions, this should look familiar.
00:19:31.000
To connect the assertion to the spec-style expectation, I referenced what I showed you earlier about how easy it is to get spec style to call the assertion. The only difference is the names of the expectation and the assertion.
00:19:54.850
To keep things DRY, MiniTest introduced a method called 'infect_assertion' which performs all of this for you. All you have to do is give it the names of the assertion you've defined and the expectation you want.
00:20:32.470
One little wrinkle is instead of using the Object class, we switch to the Numeric class, so now only numeric objects are able to call 'must_be_multiple_of'.
00:20:57.320
MiniTest employs this technique for all its expectations. If you check the source code in the expectations module, you’ll notice there are no defined method definitions; it's just a collection of 'infect_assertions'.
00:21:32.200
The last argument serves as an optional parameter: when you define an assertion, you give the expected value first and the actual value last, which is the reverse of the spec style.
00:21:50.860
MiniTest is aware of this and assumes that when you define a custom expectation, you want to flip them. When you invoke 'infect_assertion,' this is what occurs behind the scenes—it's flipping the arguments for you.
00:22:45.580
But what about other expectations, like 'must_be_empty,' which doesn’t have an argument to flip? If we were to define an expectation like this, calling 'infect_assertion' in the usual manner would lead to errors.
00:23:04.480
That’s why we include a final argument telling MiniTest ‘don’t worry about flipping, I’ll take care of it.’ Let me illustrate how I use 'infect_assertion' to write a gem using Capybara.
00:23:27.000
This is what a typical Capybara test might look like in RSpec: 'page.should have_content.' I want to convert this to MiniTest spec style, changing it to 'page.must have_content.'
00:23:49.790
All I had to do was define the assertion 'assert_page_has_content,' apply 'infect_assertion,' and repeat this process for every built-in matcher in Capybara.
00:24:09.370
To test this, I literally took the spec files inside Capybara, checked for every instance that noted 'should' and replaced it with 'must.' There were minimal adjustments to make, and that was all!
00:24:30.760
Another thing I missed while switching from RSpec to MiniTest was the ability to define shared behaviors or examples. Let’s take a brief look at a module called 'MikeMyers' that I created.
00:25:11.660
In this module, I have two classes: Dr. Evil and Austin Powers, each of which includes the 'MikeMyers' abilities. This is what my RSpec approach would have looked like—defining a shared example and then testing to ensure that both classes include the module.
00:25:38.590
When it comes to testing in MiniTest, I would have to implement shared behaviors manually, whereas with RSpec I could just include examples effortlessly. This might not sound too complicated, but I wanted to implement it as simply including a module.
00:25:53.480
My implementation in MiniTest ended up being a bit more complex. I define the module, use 'self.included' within it, and employ some class eval to accomplish testing. If you’re familiar with how modules work, this should be straightforward.
00:26:25.550
However, before you start considering how to define shared behaviors or examples, first think about whether you should extract it into a gem. Gems are usually much easier to test due to their isolated structure.
00:26:43.680
Extracting tests into gems not only makes them cleaner but also tidies up your app tests. So consider extracting into a gem before getting into independent testing.
00:27:09.200
If you switch to MiniTest and feel disoriented, there are numerous extensions others have written. If you feel something is missing, check out these extensions to see if someone else has already filled those gaps.
00:27:50.680
Now that you understand the basics, let's explore another MiniTest library—the mocking library. This is a simple class called Mockingbird I created. When I instantiate a new Mockingbird, I’ll give it a Twitter client.
00:28:12.900
When asking the Mockingbird to 'sing,' I want it to tweet something and return the status—all this without the requirement of internet access. So, I mock the Twitter client using MiniTest mock.
00:28:44.950
With this mock of a Twitter client, I specify that it expects a 'tweet' method call and, when called, it should return a specific status. Notice how I’m not providing a specific string; I’m using the string class instead.
00:29:02.890
Now, with my Mockingbird instance created, I can call 'sing,' and it performs the tweeting for me. This way, I can verify the expectation that the tweet method is invoked.
00:29:31.190
In the event that 'tweet' isn't called upon the mocked object, the test will fail. This is a brilliant library—it's quite compact, comprising just over 100 lines of code.
00:30:06.370
However, when talking about mocking, stubbing also comes into play. MiniTest lacks a built-in stubbing library, which surprised me at first because I thought mocking and stubbing naturally went together.
00:30:30.870
This confusion lingered until I attended a talk by Ryan Davis. He’s one of the authors of MiniTest, and in that talk, he explained the absence of a stub library. He quoted Phil Hackleberg, who famously said, 'Death is my stub framework.'
00:31:04.420
To illustrate, I created a simple class called StubbingBird. It checks for a comparison and responds with a message based on whether the condition is met. If the condition is met, it simply returns 'true,' and if not, it plays an annoying song.
00:31:38.330
In the test, I create a new StubbingBird. The trick involves defining a singleton method on that specific instance of the bird. With that defined, when the 'have you heard' method is called, I’m forcing it to return 'true.' That’s how Death became my stubbing framework.
00:32:09.260
Now when I compare the created bird to a word, I should receive a 'true.' However, it’s important to note that both mocking and stubbing should be used sparingly.
00:32:27.210
Over-mocking and over-stubbing can lead to misleading assumptions. For instance, which tests would you trust more? Tests that operate close to the real world, or those riddled with mock data and assumptions?
00:32:49.550
It’s prudent to stub and mock only external clients, such as Twitter, with whom you have no control. If you can test without stubbing and mocking, that’s the ideal approach. Ryan emphasizes making your tests pass first before implementing stubs and mocks.
00:33:15.780
Let’s move on to a feature I like in MiniTest: customized error outputs. When comparing two objects, you get the default output. However, when comparing articles, if they differ, the default output may not reveal the discrepancies clearly.
00:33:45.510
I prefer using a gem called AwesomePrint, which enhances output clarity, providing color coding and multi-line displays. When we diff two AwesomePrint outputs, it's much easier to identify discrepancies. Fortunately, implementing this is straightforward.
00:34:06.270
To utilize AwesomePrint in MiniTest, all we need to do is override MiniTest’s assertion method called 'mu_pp,' which stands for 'MiniTest Unit Pretty Print.' While I’m not entirely sure about the exact definition, it certainly achieves our desired outcome.
00:34:41.110
Another neat output feature is MiniTest pride. By default, when you run your tests, the output reflects drab dots or errors. However, you can include 'require mini_test/pride' to receive a colorful output format.
00:35:06.030
In my experience, reading the source code of MiniTest was significantly easier than that of many other libraries I’ve encountered. One quick tip I would like to share with you all is to read your source code.
00:35:18.890
Google and Stack Overflow contain a wealth of information, but oftentimes the solution is right under your nose. As a practice, try working without internet access for a while.
00:35:42.770
Whenever you run into a problem, look for the answer in your code. If you're using Bundler from the command line, you can call 'bundle open' followed by the name of a gem to open the exact version currently being used in your application.
00:36:09.110
This approach facilitates easier navigation to the source code, helping you find functions you never knew existed. During this exploration, I discovered a quirky feature: the tests in MiniTest run in random order each time.
00:36:32.140
This randomness is designed to catch potential order dependencies within your tests, which could lead to unpredictable results. However, if you decide you want to disable this feature, you can do so easily.
00:36:57.720
It's humorous that you have to call the line: 'I suck and my tests are order dependent' to revert back to sequential testing. Lastly, MiniTest encompasses just about six files, with a small number of lines of code.
00:37:15.540
Most of the files are small, and a significant portion of the code is documentation, making it easy to read. The easier the code is to read, the better the chances you have at understanding it. Understanding leads to greater control over your code.
00:37:39.440
How fast is MiniTest? To be honest, I don’t personally care that much about speed. However, if you're curious, I recommend watching Ryan Davis's talk, where he presents some compelling graphs comparing the lines of code among MiniTest, RSpec, Cucumber, and others.
00:38:07.220
In conclusion, I would emphasize that 99% of your test speed relies on your code, not the test framework. Yes, MiniTest is agile, but that’s merely a side effect of its simplistic design. The simplicity of MiniTest remains its principal benefit, serving as its guiding philosophy from inception to execution.
00:38:51.619
Thank you for your time! My name is Jared Ning, and you can find me at redninja.com. This was my first presentation, and I genuinely enjoyed creating it. If you could check out speakerrate.com for any feedback, I would greatly appreciate it. Thanks a lot!