Add post 'Testing with Minitest'
This commit is contained in:
parent
ae506afc7b
commit
b98292a774
188
content/posts/2015-04-07-testing-with-minitest.md
Normal file
188
content/posts/2015-04-07-testing-with-minitest.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
+++
|
||||
date = "2015-04-07"
|
||||
title = "Testing with MiniTest"
|
||||
tags = ["testing", "tdd", "bdd", "minitest", "rails", "ruby"]
|
||||
description = "Minitest is a small and fast test framework that works delightfully well with Ruby on Rails."
|
||||
slug = "testing-with-minitest"
|
||||
+++
|
||||
|
||||
Ever since I started doing TDD I've used [RSpec][rspec]. It's a great tool, and for a long
|
||||
time it was part of my standard testing stack. This stack also contains things
|
||||
like [Cucumber][cucumber] and [FactoryGirl][factory-girl].
|
||||
|
||||
Now, this stack works great. But that doesn't mean it's the best, it has its issues:
|
||||
|
||||
## Cucumber
|
||||
|
||||
Cucumber is, in almost every project, added complexity without any benefit. The idea
|
||||
behind cucumber is that it allows you to write your features / user stories in
|
||||
plain English and prove that those features are functional. The process of
|
||||
writing plain English, and using a boat load of _steps_ (regular expressions, really) to
|
||||
convert that to executable code is tedious. Unless those cucumber features actually
|
||||
find their way to a customer, I don't see any added value in writing them.
|
||||
Developers know how to write code, why not omit the entire English-to-code conversion
|
||||
entirely.
|
||||
|
||||
## FactoryGirl
|
||||
|
||||
Factory Girl is a great way of generating object to work with. Unfortunately, more
|
||||
often than not, these generated object are ActiveRecord models. When creating those,
|
||||
you will most likely hit the database. For simple apps that isn't as much of a problem.
|
||||
When an application grows, models become dependent on each other. You can't have an order
|
||||
without a customer, with a valid address and now without order lines and a valid products
|
||||
with sufficient stock. You could mock all these, but especially with integration and feature
|
||||
tests, you want the whole thing.
|
||||
|
||||
## RSpec
|
||||
|
||||
The first thing I fell in love with with RSpec was its clean syntax. The RSpec DSL allows
|
||||
you to write your specs in such away the are very readable. This is how it's suppost
|
||||
to look:
|
||||
|
||||
describe MyClass do
|
||||
context "with a valid email" do
|
||||
let(:email) { "john@example.com" }
|
||||
subject { MyClass.new(email: email) }
|
||||
|
||||
it "reports number of mails sent" do
|
||||
expect(subject.email!).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
But when an application grows, so do the number of specs. And the complexity of the
|
||||
boilerplate needed to set the stage for your test. I've seen spec files over over
|
||||
2k lines where a spec on line 1982 gets setup by `let` statements and `before` blocks
|
||||
spread over all the preceding 1981 lines. Yes, this can be resolved to an extend
|
||||
by refactoring the test, splitting it up and what not. but still, it's very
|
||||
difficult to read a spec and know what it's actually doing.
|
||||
|
||||
## What's the alternative?
|
||||
|
||||
Curiosity drove me to investigate other testing frameworks and soon I discovered
|
||||
Minitest. What I love about minitest is the lack of DSL (unless you use minitest/spec,
|
||||
of course), it also feels much faster when running tests, but I have not run any
|
||||
benchmarks to support that feeling.
|
||||
|
||||
Here's how I'd write that same test in Minitest:
|
||||
|
||||
class MyClassTest < MiniTest::Unit::TestCase
|
||||
def test_reports_number_of_mails_sent
|
||||
assert_equal 1, MyClass.new(email: "john@example.com").email!
|
||||
end
|
||||
end
|
||||
|
||||
Note that everything needed to perform this test is contained in that single
|
||||
test method. You could do the same with RSpec, but it's DSL seems to prefer
|
||||
another convention.
|
||||
|
||||
## Assert, like, whatever
|
||||
|
||||
What I truely _love_ about Minitest are the simple `assert` methods. In essence,
|
||||
that's all you need to know about Minitest: `assert`.
|
||||
|
||||
Did you ever see this in Rspec:
|
||||
|
||||
expect(my_car).to be_drivable
|
||||
|
||||
There's quite a lot going on here. Out of nowhere you have a `my_car` instance,
|
||||
which was probably declared in a `let` somewhere in this file. You could assume
|
||||
it's of the type `Car`, but who knows. You also don't know what else was done
|
||||
to this instance in some `before` block. Furthermore, this line assumes that the
|
||||
`my_car` object responds to a method named `driveable?`. Yes, _with_ a questionmark.
|
||||
There is nothing explicit about what's going on here. If you're a novice Ruby
|
||||
developer, you will probably have some difficulty figuring out what's going on here.
|
||||
If you're an experience Ruby developer, you will as well.
|
||||
|
||||
Consider the following Minitest alternative:
|
||||
|
||||
my_car = Car.new(wheels: 4)
|
||||
my_car.fuel!(type: :diesel, litres: 60)
|
||||
assert my_car.driveable?
|
||||
|
||||
This is plain Ruby. If you know Ruby, you understand what's going on here. After all,
|
||||
your tests are meant to drive your internal design. Well, here it is, in all it's glory.
|
||||
|
||||
The only method you need to know, really, is `assert`.
|
||||
|
||||
assert(test, msg = nil)
|
||||
|
||||
You supply assert with a `test` value. When true, the test passes. When false, it doesn't.
|
||||
|
||||
There are quite a few other `assert*` methods to help you do common assertions. You don't have
|
||||
to use them, as the can all very easily be written with a common `assert`. They are only there
|
||||
for convience. The ones I use most are:
|
||||
|
||||
* `assert_empty` - assert the supplied argument is empty
|
||||
* `assert_nil` - assert the supplied argument is `nil`
|
||||
* `assert_equal` - assert the supplied arguments are equal (using `==`)
|
||||
* `assert_match` - assert the supplied regex mathes with something else
|
||||
* `assert_includes` - assert the supplied collections includes a certain object
|
||||
|
||||
Then, for every `assert` method, there's also a `refute` method that fail when true (instead
|
||||
of passing). It is _that_ simple. You can read more about these in
|
||||
the [Minitest::Assertions][minitest-assertions] documentation.
|
||||
|
||||
## Fixtures or Fixture Replacements
|
||||
|
||||
Fixtures are nothing more than a pre-defined set of data to run your tests against. In
|
||||
the case of Rails fixtures are mostly data that go into the database.
|
||||
They get loaded once and each test is run inside a database transaction on that dataset.
|
||||
|
||||
FactoryGirl and Machinist are _fixture replacements_ in the sense that you define
|
||||
how your data should look and then generate what you need for each test.
|
||||
|
||||
Using fixtures has two major benefits as opposed to FactoryGirl:
|
||||
|
||||
1. All data is loaded only once, before the test suite runs. With FactoryGirl it's not
|
||||
uncommon that, for a specific test file, you need to create 10-20 records in the database,
|
||||
everytime, for 20 specs. This soon adds up an is mostly what makes test suites slow.
|
||||
2. You test against a database full of data. This may sound strange, but with time you'll
|
||||
create an extensive database of data used for testing. It's easy to add (data-wise) edge
|
||||
cases and see how they behave in your application.
|
||||
|
||||
## Capybara
|
||||
|
||||
I already mentioned Cucumber for feature testing. Under the hood, most of the time, cucumber
|
||||
will be using capybara to emulate a client browser. Minitest can handle this as well and it
|
||||
works just as you'd expect:
|
||||
|
||||
class TestCarCRUD
|
||||
def test_branded_car_listing
|
||||
visit "/cars/toyota"
|
||||
|
||||
within("#cars") do
|
||||
# "Toyota Corolla Verso" from fixtures
|
||||
assert page.has_content?(cars[:toyota_corolla].name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
The [minitest-rails-capybara][minitest-rails-capybara] gem might be of help here to make
|
||||
your setup easy.
|
||||
|
||||
## Getting started with Minitest
|
||||
|
||||
I don't want to go into too much detail about using Minitest with Rails. It's already
|
||||
well documented elsewhere and basically all you need is the [minitest-rails][minitest-rails]
|
||||
gem. For feature testing you'd also need [minitest-rails-capybara][minitest-rails-capybara].
|
||||
|
||||
## The Testing Silver Bullet
|
||||
|
||||
There probably isn't a silver bullet when it comes to testing Rails applications. I love
|
||||
Minitest for its ease of use, lightweightedness, explicitness, and the fact that's just
|
||||
Ruby. In combination with fixtures it's possible to write a very fast test suite.
|
||||
|
||||
I think that's RSpec tries to do too much magic and DSL'ing which makes it much harder to
|
||||
write clean tests. As a developer I see little benefit from using a specific DSL for writing
|
||||
tests. Having paired up with a few junior developers, new to Ruby and Rails, they tend to
|
||||
pickup Minitest tests much faster than RSpec and Cucumber.
|
||||
|
||||
Be sure to give Minitest a try and let me know your thoughts!
|
||||
|
||||
[rspec]: http://rspec.info/
|
||||
[cucumber]: https://cukes.info/
|
||||
[factory-girl]: https://github.com/thoughtbot/factory_girl
|
||||
[minitest-assertions]: http://bfts.rubyforge.org/minitest/MiniTest/Assertions.html
|
||||
[minitest-rails]: https://rubygems.org/gems/minitest-rails
|
||||
[minitest-rails-capybara]: https://rubygems.org/gems/minitest-rails-capybara
|
Loading…
Reference in New Issue
Block a user