Talks

MiniTest: Refactoring Test Unit and RSpec back to version 0.0.1

MiniTest: Refactoring Test Unit and RSpec back to version 0.0.1

by Jared Ning

In the video titled "MiniTest: Refactoring Test Unit and RSpec back to version 0.0.1" presented by Jared Ning at Rails Conf 2012, the speaker discusses the advantages and simplicity of the MiniTest framework for Ruby programming. The session begins with a poll to understand the audience's familiarity with various testing frameworks, before diving into why MiniTest stands out.

Key points outlined during the presentation include:
- Simplicity of MiniTest: MiniTest aims to make testing straightforward and intuitive, emphasizing the importance of simplicity in code.
- Comparison with Other Frameworks: The speaker compares MiniTest with two popular frameworks: Test Unit and RSpec, noting that MiniTest can serve as a drop-in replacement for Test Unit and retains familiarity for RSpec users.
- Red-Green-Refactor Cycle: Jared elaborates on the Red-Green-Refactor testing cycle, explaining how this method promotes simplicity and better code management.
- Custom Assertions and Extensions: He highlights the process of defining custom matchers and how MiniTest allows for extending its functionalities through additional libraries or custom assertions.
- Mocking and Stubbing: The differences between mocking and stubbing within MiniTest are clarified, backed by examples that illustrate practical usage scenarios.
- Performance and Readability: The speed of MiniTest is discussed, but Jared emphasizes that the majority of performance gains come from how developers write their tests rather than the framework itself.
- Integration with Rails: The presentation notes that Rails 4 uses MiniTest, potentially easing the transition for those familiar with Rails.

Jared concludes by encouraging developers to focus on simplicity and to willingly experiment with MiniTest in future projects, rather than overhauling existing codebases. The discussion emphasizes an understanding of testing frameworks as a part of maintaining clean, functional code. The talk reflects Jared's belief in the importance of engaging with different perspectives on testing frameworks to enhance the developer community.

Overall, this session showcases MiniTest as a conducive environment for Ruby developers looking for an efficient, readable, and simple testing framework.

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!