TDD in Crystal with minitest.cr

Please note that this tutorial was written for Crystal 0.24.1 and minitest 0.3.6. I can’t be certain that these instructions will work for other versions of either.

Welcome back to my blog. Today we’re going to explore Test Driven Development(TDD) with the Crystal language. The Crystal compiler does include a fully featured spec library that was inspired by Rspec. However, I always preferred Minitest over Rspec in Ruby, so if you’re like me, you’ll be very pleased to know that there’s a minitest inspired Crystal library, appropriately called minitest.cr. So I’m going to show you how to use minitest.cr to create and run unit tests in your Crystal projects.

Creating The Project

First thing we’re going to do is create our project. Since we’ll be including a third party library in Crystal (also known as a Shard), we’ll be making use of the standard Crystal project structure. Luckily, the Crystal compiler has a built-in feature to create the project folder and necessary files. From the command line of the directory you want to create your project in, type the following command:

$ crystal init lib learning_minitest

Allow me to explain this for those of you who haven’t used this feature before. If you’re familiar with this command, feel free to skip to the next section.

The crystal command is the command for accessing the Crystal compiler, and its suite of built-in tools. In this case, we’re using the init tool to create our project. The lib command means that we’re creating a library. The other option is app, which is used for creating an application. After that, comes the directory(if not the current directory) and project name.

When run, this command will create a folder called learning_minitest and the project skeleton, complete with a git repository and .gitignore file.

Note: Make sure to use an underscore and not a dash in the project name. The Crystal compiler takes a dash to mean that you’re extending a library. In this case, that means you’ll get a module called Learning, and a module called Minitest within the Learning module. That’s not what we want. We just want one module called LearningMinitest.

Shard.yml

Third party libraries in Crystal are referred to as Shards. They’re very analogous to Ruby’s Gems. Following this analogy, Crystal uses a YAML file called shard.yml, which simultaneously fills the functions of Ruby’s gemfile and gemspec files. For those of you who haven’t worked with a shard file yet, I’ll briefly cover the features that are related to this tutorial. If you are familiar with the shard file, feel free to add the minitest shard (version 0.3.6), and move on to the next section.

The crystal init command we used earlier created a basic shard.yml file, which will look something like this:

name: learning_minitest
version: 0.1.0

authors:
 - Chris Larsen <clarsen@example.org>

crystal: 0.24.1

license: MIT

Now there’s a lot of information you can put into a shard file, but for our purposes, all we need to do is add a dependency. For other features, I recommend looking at the shard.yml specification page.

Right below where it says says crystal: 0.24.1, place the following lines:

development_dependencies:
   minitest:
     github: ysbaddaden/minitest.cr
     version: "0.3.6"

 

Shards accepts two different types of dependencies, regular and development, which are indicated by the line stating dependencies: or development_dependencies:, respectively. In this case, you’re not going to need your tests in production, so we’re declaring minitest as a development dependency.

Now that our dependency is declared, we need to actually download the files for it. The following command does that for us:

$ shards install

This will install the necessary files from the git repositories of all the dependencies listed in your shard.yml file. If you need to update the version of the shard you’re using, you can use the shards update command after updating the version number in your shard file.

Creating Tests

Since the creation of a Crystal project doesn’t give us a test folder, we’ll have to do that manually. Your test folder should reside in the top level directory of your project structure, just as it would in Ruby, and be named test. Within it, we’re going to need a file for our tests. In the interests of convention, I suggest ending the name of the file with _test.cr. In this case, I’m going with learning_test.cr. To start with, add the following lines to your file:

require "minitest/autorun"

require "/../src/learning_minitest.cr"

class LearningTest < Minitest::Test

end

The first require statement is part of the minitest library, which allows us to run the test methods that we’ll write. The second is the file in our own project that will house the class that we’re going to test. Lastly, the class, which is a subclass of Minitest::Test is where we put all our tests.

The minitest.cr library contains all the same life cycle hooks that the original minitest contained; before_setup, setup, after_setup, before_teardown, teardown, and after_teardown.

However, I must provide a quick word of warning. In Ruby, many developers use the setup methods to initialize an instance of the class. In Crystal, due to the compiler, it won’t work that way. Since the compiler doesn’t know that the setup methods will be called before every test, it treats any item initialized in those methods as a union type with nil.

This can cause all kinds of problems, so I don’t recommend doing it. The alternatives are to use memoization, as in:

def cat
  @cat ||= LearningMinitest::Cat.new
end

Or, you can define any instances to test right below the class declaration, and give them a default value, like so:

class LearningTest < Minitest::Test
  @cat = LearningMinitest::Cat.new

end

I prefer memoization, so let’s add those lines inside the class. In case you’re wondering, yes, we’re creating a Cat class.

Now, let’s create our first test. Simple, yet effective:

def test_cat_likes_petting
  cat.pet
  assert cat.happy
end

Since minitest.cr is a third party library, there aren’t any default tasks that run all your test files, so you run them manually, one at a time, like so:

$ crystal run test/learning_test.cr

All test methods must be prefaced with test_ in order to be recognized as test methods and run during this process. Running them now should give you an error:

Can't infer the type of instance variable '@cat' of LearningTest

@cat ||= LearningMinitest::Cat.new

This is because we haven’t yet created the class that we’re testing. So let’s do that now.

Creating Library

The crystal init command created a learning_minitest.cr file in our src directory. Right now, it looks like this:

require "./learning_minitest/*"

# TODO: Write documentation for `LearningMinitest`
module LearningMinitest
   # TODO: Put your code here
end

We’re going to create our Cat class within this module. We also need to add the pet method and happy read-only property. Remove the two # TODO statements and add the following lines after the module definition:

class Cat
  getter happy : Bool

  def initialize
    @happy = false
  end

  def pet
    @happy = true
  end
end

If you’re not familiar with the getter line there, that defines a boolean instance variable called happy, and also creates a getter method for it, thus making it read-only. You can use the same format to define write-only properties with setter, or read-write properties with property. The Bool keyword can also be replaced with any class name to define an instance variable of that type.

Now that our class is defined, along with the pet method and happy instance varible, we can run our test and expect a successful output.

.

Finished in 00:00:00.001435720, 696.5146407377483 runs/s

1 tests, 0 failures, 0 errors, 0 skips

This output is quite possibly the biggest difference between the Ruby minitest, and the Crystal version. Whereas Ruby gives you the number of runs and assertions, this one gives you the number of tests. So a test method with 5 assertions, will still output as 1 test.

Lifecycle Hooks

As stated earlier, the same lifecycle hooks exist as in the Ruby version, so let’s use one of those now. Add the following setup method to learning_test.cr:

def setup
  cat.name = "Pandora"
end

Now to test this:

def test_cat_has_adorable_name
  assert_equal "Pandora", cat.name
end

Which should result in an error. This we fix by adding a property to our Cat class, and assign a default value to it.

property name = ""

Yes, another feature of our instance variable declarations is the ability to assign a default value that the variable holds if it’s not assigned anything during the initialization process.

Now, both of our tests should pass.

..

Finished in 00:00:00.001404765, 711.8628382683224 runs/s

2 tests, 0 failures, 0 errors, 0 skips

There’s a lot more to minitest.cr, but most of it is exactly like the original Ruby minitest.

If your tests aren’t passing, or there’s anything you don’t understand about what we’ve done, you can either refer to the github repository for this project, or leave a comment below.

Many thanks to ysbaddaden for creating this shard, without which, this blog post would not have been necessary. The source code for the shard can be found here.

Advertisements

Crystal Grep

Today, we’re going to have some more command line fun with Crystal. Along the way, we’re also going to cover Regex, IO and other subjects.

This tutorial was influenced by my friend 8bitmiker and his recent YouTube video, where he shows you how to create a similar program in Ruby. The final version in Crystal can be found here.

To start with, we’re going to make sure that the program is called with an argument. Without any argument, there’s nothing for this program to do. You may remember from my previous blog post that Crystal uses the same ARGV array constant for command line arguments as Ruby. This, and other similarities allows us to use the same line as used in the Ruby version of this program.

abort "Need a regex pattern" unless ARGV.size > 0

The abort command does what you’d expect it to do, it exits the program immediately.

While Ruby has Array methods called length, size and count, Crystal has a non-alias philosophy, and in this case, has chosen size to return the number of elements in an array.

Combine these two with the in-line unless (another Ruby beauty), and the program will exit on the first line if the user didn’t give it any command line arguments.

Next, we’re going to take that Regex from the ARGV array, and store it. That way, we don’t have to keep accessing ARGV, and also make our code more readable. Using another Ruby inspired method, shift, will do the trick:

regex = ARGV.shift

Just like in Ruby, shift removes the first element in an array, and returns it. In this case, passing it into the aptly named regex variable. Now, it’s important to note, ARGV is an array of strings. The regex variable therefore, contains a string of the regex pattern that the user entered. Later, we’ll convert that string to a Regex object.

Now that we have our second line, we can show just how important that first line is. If the user doesn’t enter any command line arguments, ARGV will be an empty array, and trying to run shift on an empty array will result in an Index out of bounds error. Hence, the need to abort the program if there’s no arguments provided. Remember, Crystal may look like Ruby, but it’s not Ruby.

Let’s run a quick test of the program at this point. For a program like this, it’s best to compile and run the compiled version. I saved the file as cgrep.cr, so we can compile it with the command:

$ crystal build cgrep.cr

Now we run it with:

$ ./cgrep

This will of course return the message Need a regex pattern since we didn’t give it a regex pattern. To run it properly, you need to not only give it the regex pattern, but also give it some input to process.

$ ls -l | ./cgrep .*

If you’re not incredibly familiar with Command line interfaces, the pipe(|) operator takes the output from the left side, and inputs it to the standard input of the command on the right side. So this line takes the output from ls -l, and sends it to the standard input of our Crystal program.

Unlike the original Ruby program from 8bitmiker, Crystal doesn’t possess the $_ operator, so we’ll access our standard input using the built-in STDIN variable. STDIN is an IO::FileDescriptor object, so you can use any methods available to this class on your input. Since ls -l returns multiple lines, we’re going to use the popular each_line method, which iterates over each line in the input.

STDIN.each_line do |i|
end

And there’s our main loop. It will iterate over each line of the input, and pass that line into the i variable for access within the loop. Now, what we do we want to do with that line? Well, we want to compare it to the regex. To do that, we need to turn the string representation of our regex into a proper regex using interpolation:

%r(#{regex})

We also have the familiar match operator (=~) to see if the regex matches the line from standard input, and output if we have a match, like so:

puts i if i =~ %r(#{regex})

Placing that into the loop, and we have our finished program:

abort "Need a regex pattern" unless ARGV.size > 0
regex = ARGV.shift

STDIN.each_line do |i|
  puts i if i =~ %r(#{regex})
end

Running it with the same command as before:

$ ls -l | ./cgrep .*

Will produce an output similar to this:

total 2048
-rwxr-xr-x 1 chrislarsen staff 1037464 27 Dec 14:36 cgrep
-rw-r--r-- 1 chrislarsen staff 128 4 Jan 14:20 cgrep.cr

This is definitely not a production ready program. Ideally we’d have a check to make sure that the regex string is a regex pattern, and we’d interpolate that into a Regex object before the loop instead of wasting resources doing that conversion in the loop, but it does work as desired. It’s what 8bitmiker would call “sloppy and dirty.”

Constant Learning is Fun

Yes, you read that right. Learning is fun. No, I don’t work as a teacher, and I’m not doing this as a plug for academia. There’s no hidden agenda behind this blog post.

One of the many things that I’m doing in the tech world is learning the new Crystal programming language. My first project in Crystal is an interface for the Open Weather Map API. Recently, I learned that there was a far better(and simpler) way of coding the majority of this library. So here in late December, I spent half a day rewriting my only Crystal project from scratch.

As I headed out to a holiday party afterwards, I reflected on the fact that I was in the process of rewriting the entirety of my Crystal code. Naturally I did have a fleeting thought that this amounted to a lot of wasted time. However, this thought was very quickly replaced with a feeling of accomplishment.

You see, the time wasn’t wasted, because the first and second versions of my code were stages in my learning process. While the code of my first draft was very bad, I did learn a lot about this new project while writing it. While I am completely rewriting that first draft, the fact that I am, so soon after finishing it shows just speed at which I’m learning.

I’m not going to be sharing the technical aspects of what I learned in this blog post, but to keep your eyes open in the future for a post on the JSON.mapping macro, and how to use it to create elegant and efficient wrappers for your favourite web APIs.

In the meantime, allow me to pass on some helpful advice for beginner and expert programmers alike who find themselves in this same situation. Be proud that you wrote bad code, because you’re now good enough to recognize that it’s bad code. Replacing that bad code is proof of how good you are of a developer.

Working with command line arguments in Crystal

Being a compiled language, Crystal is great for creating command line tools. But what’s a command line tool without options? Well, that’s where command line arguments come in.

In line with the goals of the Crystal language, working with command line arguments in Crystal is very similar to doing so in Ruby. The constant even has the same name.

In this tutorial, we’ll be building a simple command line tool that shows the current time, complete with a usage guide.

Let’s start by creating a file that we’ll name cl-tutorial.cr. Since command line tools always have a help page or usage guide, we’ll create one of those first.

A simple way to create a multi-line string in Crystal is with a heredoc. From Crystal’s documentation:

A “heredoc” starts with <<-IDENT, where IDENT is an identifier: a sequence of letters and numbers that must start with a letter. The “heredoc” finishes with a line that starts with IDENT, ignoring leading whitespace, and is either followed by a newline or by a non-alphanumeric character.

In this case, we’ll use STRING as our identifier, and assign it to a variable named usage. Enter this at the beginning of your cl-tutorial.cr file:

usage = <<-STRING
Usage: cl-tutorial [option]

Command:
    time, --time, -t show the current time
    help, --help, -h show this help
    version, --version, -v show version
STRING

 

As I said earlier, Crystal handles arguments pretty much the same as Ruby, with an array with the name ARGV. In this example, we only have one argument, so we’ll be accessing it as ARGV[0].

Since there are 3 different options, we’ll use a case-switch structure to check the argument:

case ARGV[0]
when "time", "--time", "-t"
  puts "The current time is #{Time.now}"
when "help", "--help", "-h"
  puts usage
when "version", "--version", "-v"
  puts "CLI Tutorial v0.1.0"
else
  puts "Unrecognized option"
  puts usage
end

 

Now, running a program with arguments using the Crystal compiler requires a little extra work. You have to add to hyphens (–) between the file name and the arguments themselves. Keep in mind that you only need those hyphens if you’re compiling and running in one step, not when you’re running an executable. So you can either run this with the help option this way:

$ crystal cl-tutorial.cr -- -h

or this way:

$ crystal build cl-tutorial.cr
$ ./cl-tutorial -h

and you should see the help menu output.

The app mostly functions now, but it has one critical error. If you were to run this program without any arguments, you’d get an error, due to ARGV being an empty array. In Ruby, this would trigger the else clause of the case-switch statement, so it’d be ok. However, because Crystal is statically typed, ARGV[0] on an empty array triggers an out of bounds error.

This is easy enough to fix however. We’ll create an if-else statement, and place our current case-switch inside the else of said if-else statement. Add these three lines prior to the case:

if ARGV.size == 0
   puts usage
else

and then add another end after the case. Now, your case statement will only be evaluated if the ARGV array is confirmed to be non-empty. If you’d like, you can confirm this by going back to your command line, and entering:

$ crystal cl-tutorial.cr

without any arguments. The output will be identical to what you saw earlier when you added — -h to the end of the line.

Now our simple program is complete. You can even test out the time function by running:

$ crystal cl-tutorial.cr -- -t

If you’re having any issues, you can compare your program to the original on my github page. Otherwise, feel free to let me know in the comments section.