Minitest Cheat Sheet
As an Elixir novice, I think one of the hardest things about getting started inElixir is figuring out what kind of data structures to use. My teamhas recently started going all in on Elixir, so I've been trying to brush upin earnest. Often, however, I'll be reading over some of my team's code and have a hard time deciphering what I'meven looking at. The syntax looks very similar to Ruby (which I know prettywell), but the patterns, conventions, and data structures are justslightly different. In my mind, that makes sense since it's a functionalrather than object-oriented language: where you would use objects inRuby, you're probably spawning processes instead in Elixir.
- Commit Message Guidelines Short (72 chars or less) summary More detailed explanatory text. Wrap it to 72 characters. The blank line separating the summary from the body is critical (unless you omit the body entirely).
- Minitest do not come with any feature for testing requests, unlike request spec in RSpec. Fortunately, controller tests are able to test through everything in the backend like request tests if.
- MiniTest Assertions Cheat Sheet by CITguy - Cheatography.com Created Date: 0830Z.
- MiniTest 的正确使用姿势. 在 MiniTest 之前,用 Ruby 做测试的有两种人,一种人喜欢 Test::Unit 的 test. 风格,另一种人喜欢 Rspec 的 describe 风格。.
For what it's worth, learning Elixir reminds me a lot of when I learned Spanish for thefirst time and thought to myself, this alphabet looks familiar, but thereare all these extra letters! Thanks to cognates, I can get by in Spanish. Learning Elixir feels a lot like that to me.
Minitest Cheat Sheet Recently I came across a post that called out a perceived lack of available documentation and other learning material for Minitest in contrast with RSpec. And while I’m not entirely sure I agree with the premise, the main point of the article had the ring of truth to it.
But anyway, since I'm learning Elixir now, I thought it'd be useful toprovide a cheat sheet or overview of the differences in data structures I've noticed as aRubyist exploring Elixir.
Data Types
If you're coming from Ruby (or most other programming languages), integers,floating-point numbers, ranges, and regular expressions are all probablyfamiliar to you. Fortunately, those all exist in Elixir too. There are a fewdifferences, but I haven't dealt too much with them yet.
Atoms are like symbols in Ruby. They begin with colons and their names aretheir values. For example, :hello
is a valid atom in Elixir. They're oftenused to tag values.
There are also strings in Elixir. Strings always have double quotation marks, while charlists are in single quotations marks. Strings are binaries, and charlists are actually just lists of code points. I have rarely used charlists so far.
Here's a quick glance at what those types look like
Elixir has the additional data types, Port
and PID
, whichare used in process communication. They are entities that are made availablethrough the Erlang VM.
Port
A Port
is used to communicate (read/write) to resources outside yourapplication. They are great for starting operating system processes andcommunicating with them. For example, you might want to open a port to run anOS command, like echo
.
You could open a port and send it a message, like this:
Then, you can use the flush()
IEx helper to print the messages from theport.
You can send a port any name of a binary you want to execute. For example,from the directory of my jekyll blog, I opened up an iex
session, opened aport, and then sent the bundle install
command, which installed all theRuby gem dependencies. Here's a snippet of the output.
PID
A PID
is a reference to a process. Whenever you spawn a new process, you'llget a new PID. Expect to talk a lot about PIDs. You'll probably need to holdonto PIDs so you can send different processes messages.
Minitest Cheat Sheet Template
Here's an example of spawning a process and getting the PID back.
The process dies after it has done its job. PIDs and Ports warrant their own standalone post, but for now, I think it's sufficient to just be aware that they exist.
So, now that we've added our new types, this is our basic cheat sheet.
The real challenge with Elixir in my opinion, though, is figuring out how toorganize these basic data types into structures you can use. So let's take alook at the various collection types and why you would use each.
Collection Types
Here are the collection types you'll likely encounter:
- Tuples
- Lists
- Keyword Lists
- Maps
- Structs
You've probably heard those words before, at least in passing, but if you're accustomed to Ruby, you're probably wondering why you need all those extra types of collections. Let's investigate.
Tuples
Tuples are ordered collections of values. They look like this:
I think tuples are a little wild. I mean, they look like they should be hashes, but they sort of behave like Ruby arrays. And then they're called tuples! It'll pay off to get familiar with them though, which is what I keep telling myself when I get confused for the hundredth time.
Tuples crop up all over the place in Elixir. Return values of functions are often tuples that you can pattern match on, so it makes sense to start seeing the world through tuples. Tuples usually have two to four elements, and at this point, they're my go-to data structure. When you're dealing with data structures that have more than four elements, that's probably a good case for using a map or struct instead.
Lists
Lists are linked data structures. They look like this:
In Ruby, you'd think that was an array, but in Elixir, it's a list! Because lists are implemented as linked data structures, they're good for recursion, but bad for randomly retrieving an element or even figuring out the length because you'd need to traverse the whole list to figure out the size. To date, I've mostly been using tuples instead of lists. If you had to choose between them, I suppose you'd need to consider the expected size of the collection and what kind of operations you'll be performing on it.
Keyword Lists
To further complicate matters, there are also such things as keyword lists in Elixir. In essence, this is a list of two-value tuples.
This continues to baffle me, even though I am aware of its general existence.The cool thing about keyword lists is that you can have two of the same keys in a keyword list.
Keyword lists are good for command-line parameters and options.
Maps
Next up are maps. If you wanted a real key-value store, not a list of key-values, this is what you're looking for. They look a bit like hashes in Ruby.
Maps are good for passing associative data around, and pretty much everything else that is bigger than tuple size.
Structs
Structs are like enhanced maps. They permit only certain keys and those keys must be atoms. Structs need to be defined in modules with reasonable default values. They're maps with rules.
You'll see that the struct is defined with the same %
percent symbol as a map, but it's followed by the name of the module. That's how I remind myself that they're just stricter maps.
Older versions of Elixir used to also include the HashDict
to handle maps with more than a couple hundred values, but that module has been deprecated in favor of the good ol'fashioned Map
.
That brings us to the end of the common data types and collection types you'll see in Elixir. Although there are a number of differences between the two languages, there are some similarities. Of course, there's plenty more to learn about Elixir, the conventions, and the cool things you can do, but this (I think) is a good start to getting familiar with the language. Hopefully this'll serve as a decent guide to deciphering any Elixir you might encounter soon!
The Elixir Collection Cheat Sheet
Resources
- Basic Types onElixir Lang
- Programming Elixir by Dave Thomas
When it comes to exercising a piece of application logic with automated unit tests, there’s a well-understood process that most frameworks and testing tools follow:
- Setup: Establishes instances of data objects and preconditions essential for running the test.
- Exercise: Executes the method or logic to be tested.
- Verify: Verifies that the tested method has produced the expected result by making one or more assertions.
- Teardown: Cleans up or resets application state that should not be allowed to persist between tests.
Perform a Google search for “unit test anatomy”, and you’ll see this same pattern described in books and articles for many programming languages and methodologies - sometimes with slightly different terminology, but still following the same basic sequence. But the way that a given tool or testing library realizes each phase can vary a lot - a fact which has launched hundreds of testing frameworks and thousands of flame wars.
The original Ruby standard for testing was established by the Test::Unit library (itself based on the xUnit model) which was part of the Ruby standard library going back many years and many more releases. Minitest follows the same model by providing a setup
method which can be overridden and will be run by the framework before each individual test.
RSpec came along quite a bit later and introduced a more granular scheme of hooks for setting up test state that mapped more naturally to its block-based syntax.
Minitest Cheat Sheet Excel
- before(:each) - logic to run before each individual test method
- before(:all) / before(:context) - logic to run at the start of a context/describe block
- before(:suite) - logic to run before the test suite runs
- let - memoizes the result of a block and provides an accessor method for it
Minitest has built-in support for some but not all of these. In this post, I’m going to show you how to achieve the same effects in your own tests using the features that Minitest gives you along with a sprinkling of plain old Ruby. Because in the end, it’s all just Ruby.
Setup Before Running Each Test
You probably already know that Minitest::Test provides a setup
method that you can override to define logic that runs before each test.
Minitest::Spec provides an equivalent in the form of its before
block:
What Exactly Does :let Do Again?
Using let
provides an alternate and some would say more elegant way of setting up testing state with a more declarative syntax. The following would be comparable to the example in the previous section.
Comparable, but not equivalent. Each let
invocation defines a new method with the specified name that executes the block argument upon the first invocation and caches the result for later access - in other words, a lazy initializer. The main advantage of this technique over the use of instance variables defined in a setup
method or before
block is that the setup logic can be divided into smaller units and executed only in tests where they’re needed.
What’s more, let
gives you the ability to define and redefine the block assigned to each name so that tests can be run against a set of values and preconditions defined within the most immediate block, then the enclosing block, and so on. Take the following sample spec as an example:
Both of these tests will pass since the contents of the list in each case will be determined by whatever is most immediately assigned to thing
in the enclosing block. This can be a really powerful tool, and I’ve found it’s really effective in situations where I need to test the same method with different inputs, but bear in mind that nesting describe
blocks too deeply will make your tests harder to understand and leave you and other developers confused about what’s actually being tested.
(Special thanks to @jemmyw for calling out the fact that this was not well covered in the original version of the post.)
I had always assumed that the memoized result was cached and available for use across all tests in a test case after the first invocation, but because Minitest runs each test using a fresh instance of the test class, the value is associated with a single test instance, not shared across instances.
Setup Before Running the Test Case
RSpec gives developers the ability to define setup code that would only run before the start of each test case using a before(:all)
block – now also aliased as before(:context)
. Minitest doesn’t support the same syntax, but it’s easy enough to implement by executing class-level code and using class variables to store references to any shared resources as in the following example.
In this case, we’re assuming that the call to the Facebook API will be slow, so in order to perform that initialization just once rather than before every single test, we assign the class variable @@fb_client
one time at the start of the test case. All instances of the test case will then have access to the shared client resource without creating a new connection.
While this is a nice tool to have at our disposal, it has the potential of being taken too far by, for example, using it for setting up anything involving database access. Overusing class variables in this way reduces test isolation and introduces the potential that tests will begin to fail (or worse, not fail) randomly, and so I’d be somewhat cautious about where and how often you apply this model.
Extra credit homework: Read the GitHub issue that requests the inclusion of support for before(:all)
and the discussion afterward. It specifically describes the technique explained above, and the comments provide a lot of insight about how to take a conservative approach to library design.
Setting Up Before Running the Suite
Setup code intended to run once before all tests in the suite use a similar technique as shown in the previous section, but in this case, we’ll need to modify Minitest::Test in our test_helper.rb
file instead of the individual test cases. The code will look like this:
The result is a Facebook API client that’s shared between all test cases in the suite and which is set up once before any tests are executed.
Minitest Cheat Sheet Printable
The fact that this can be done doesn’t mean that it should be done though. Before using a technique such as this though, you need to ask yourself what effect it will have on your suite. Tests should be written as much as possible in a single file with as much verbosity and repetition as is needed to convey their meaning, and I’d personally be really reluctant to distribute code that’s essential to a clear understanding of my test case into other files.