Code Monkey home page Code Monkey logo

project_walkthrough's Introduction

Test-Driven Development Exploring Minitest

Our Objective:

Today we will learn to

  • Setup a Ruby project environment
  • Build a specification using Minitest.
  • Use Rake to execute the tests on the specification or run the project.

Setting up a Ruby Project Environment.

Until now all our programs have involved one enormous file with all of our classes inside it. This quickly becomes unwieldy for a couple of reasons.

  1. If the project is broken into separate files team-members can work on the individual files independently.
  2. Git can track changes if each class is broken into individual files.
  3. Later we could potentially reuse classes without importing others if they are broken out into separate files.

When we are finished your project folder will look something like this:

Folder Organization

Making Sure Rake & Minitest are Installed

Rake is a tool for building applications. With it you can create different ways to run your application, such as for testing. You can check to see if Rake is installed with the command: rake.

If rake is not found you can install it with: gem install rake You should see:

Fetching: rake-11.2.2.gem (100%)
Successfully installed rake-11.2.2
Parsing documentation for rake-11.2.2
Installing ri documentation for rake-11.2.2
Done installing documentation for rake after 0 seconds
1 gem installed

You can ensure that minitest reporters is installed with gem install minitest-reporters

Successfully installed minitest-reporters-1.1.11
Parsing documentation for minitest-reporters-1.1.11
Done installing documentation for minitest-reporters after 0 seconds
1 gem installed

Creating a Project

Step 1: Creating a Github Repository

Now that we have Rake installed we can set up our project. First go to Github and create a new repository named FizzBuzz and then clone the repo to your terminal.

Create a new repo

git clone [email protected]:<USERNAME>/FizzBuzz.git

Then change directory into your local repository (cd) and create some configuration files by typing:

cd PROJECT-NAME
rvm --create --ruby-version ruby-2.3@FIZZ-BUZZ

This will create a pair of files which specify the version of Ruby to use in your project and the project name (Fizz Buzz). Later we will use Gemfiles to serve this purpose.

We will also create a .gitignore file which is a file which tells git to ignore certain files & directories. Copy this file here and save it as .gitignore in your project folder.

Add a line to have it ignore the .DS_Store file.

Editing .gitignore

Next lets create a few directories we will use:

mkdir lib
mkdir specs

This creates two folders:

  • lib - The lib folder will hold your classes in your project.
  • specs - The Specs folder will hold your test cases

Creating a Rakefile

In the last part of our setup we will create a file called a Rakefile. This file lets you specify tasks you run in your program using Ruby code. We are just going to create one task in which we will run our testcases.

So copy the following code into a file named Rakefile in the root of your project.

require 'rake/testtask'

Rake::TestTask.new do |t|
  t.libs = ["lib", "specs"]
  t.warning = false
  t.verbose = false
  t.test_files = FileList['specs/*_spec.rb']
  puts "Running TestTask"
end

task default: :test do
	puts "Running my Rakefile"
end

The code above creates a TestTask and runs all the files in the specs folder which have filenames that end with _spec.rb and runs that task by default. The line task default: :test do marks test as a prerequisite for the default task, meaning it will run before the default task.

You can run the task with rake.

Creating a Module

We will create a Module for our project named FizzBuzz, so create a file named FizzBuzz.rb in the root director with the following code.

module FizzBuzz
end

We now have a project environment set up and ready to go!

Building A Specification

Problem Description

In this example we will build a FizzBuzz Project which is a method which solves the following problem.

"Write a method that takes a number as a parameter and for most numbers it returns the same number as a String. But for multiples of three return “Fizz” instead of the number and for the multiples of five return “Buzz”. For numbers which are multiples of both three and five return “FizzBuzz”. Everything is returned as a String"

Building Test Cases

Because we are follow Test-Driven Development we will need to determine test cases for this problem. We will do this using Minitest, specifically Minitest's Specification format.

We have built tests like the following:

class YearTest < Minitest::Test
  def test_leap_year
    assert leap_year?(1996), 'Yes, 1996 is a leap year'
  end

  def test_non_leap_year
    skip
    refute leap_year?(1997), 'No, 1997 is not a leap year'
  end
end

So for fizzbuzz it would look like this:

class FizzBuzz_test < Minitest::Test
  def testfizzbuzz-3
    assert fizzbuzz(3), 'Fizz'
  end

  def fizzbuzz-5
    assert fizzbuzz(5), 'Buzz'
  end
end

This works fairly well, but Minitest has another format which does the same thing, is more readable for non-Ruby programmers and it's in a similar format to other testing frameworks like RSpec.

The new format is called spec-style testing.

An example is below

require 'minitest'
require 'minitest/spec'
require 'minitest/autorun'
require 'minitest/reporters'


require_relative 'leap_year'

describe "Testing Leap Year" do
  it "Testing Leap Year on regular years divisible by 4" do
      expect(leap_year(1996)).must_equal(true)
  end
end

The describe line simply describes the types of tests you are running. You can either give it a String or a class name. We use it to group together tests over as a test case.

The line starting with it simply defines a test with a descriptive String passed as a parameter, think of it like a method definition.

Minitest Specs add a bunch of methods like in the chart below letting you write more readable tests. They are analogous to the assert methods we used in our previous automated testing.

Minitest Spec Expectation Methods

| Expectation | Opposite | Example | |----------- |----------- |----------- |----------- |--- | | must_be | wont_be | expect(@balance).must_be :balance? | must_be_empty | wont_be_empty | expect(@owners.must_be_empty) | must_be_instance_of | wont_be_instance_of | expect(@owner).must_be_instance_of Owner | must_be_nil | wont_be_nil | expect(@owner).must_be_nil | must_equal | wont_equal | expect(@balance).must_equal 1000 | must_include | wont_include | expect(@names).must_include 'Ada Lovelace' | must_match | wont_match | expect(@names.first).must_match 'Ada Lovelace' | must_raise | | expect( proc { Account.new() }).must_raise ArgumentException

Creating Specs for FizzBuzz

So for Fizzbuz we will need to test for:

  1. Cases where the number is not divisible by 3 or 5.
  2. Cases where the number is divisible by 3 and not 5
  3. Cases where the number is divisible by 5 and not 3
  4. Cases where the number is divisible by both 3 & 5.

We can build the first two test cases like this:

# fizzbuzz_spec.rb
require 'minitest'
require 'minitest/spec'
require 'minitest/autorun'
require 'minitest/reporters'


require_relative '../lib/fizzbuzz'

describe "Testing FizzBuzz" do
  it "Testing for cases not divisible by 3 or 5" do
      expect(FizzBuzz::Fizzbuzz.fizzbuzz(1)).must_equal("1")
      expect(FizzBuzz::Fizzbuzz.fizzbuzz(2)).must_equal("2")
      expect(FizzBuzz::Fizzbuzz.fizzbuzz(4)).must_equal("4")
  end
  it "Testing for cases divisible by 3 and not 5" do
      expect(FizzBuzz::Fizzbuzz.fizzbuzz(3)).must_equal("Fizz")
      expect(FizzBuzz::Fizzbuzz.fizzbuzz(6)).must_equal("Fizz")
      expect(FizzBuzz::Fizzbuzz.fizzbuzz(9)).must_equal("Fizz")
  end
end

Now lets setup our fizzbuzz method. Lets create a lib/fizzbuzz.rb file

# Fizzbuzz.rb

class FizzBuzz::Fizzbuzz
  def self.fizzbuzz(number)
    return number.to_s
  end
end

Now when we run the test cases with rake we will get:

(in ~/FizzBuzz)
Running TestTask
Run options: --seed 39072

# Running:

.F

Finished in 0.001504s, 1329.4027 runs/s, 2658.8055 assertions/s.

  1) Failure:
Testing FizzBuzz#test_0002_Testing for cases divisible by 3 and not 5 [~/FizzBuzz/specs/fizzbuzz_spec.rb:16]:
Expected: "Fizz"
  Actual: "3"

2 runs, 4 assertions, 1 failures, 0 errors, 0 skips
rake aborted!
Command failed with status (1): [ruby -I"lib:specs" -I"~/.rvm/gems/ruby-2.3.0@global/gems/rake-10.4.2/lib" "~/.rvm/gems/ruby-2.3.0@global/gems/rake-10.4.2/lib/rake/rake_test_loader.rb" "specs/fizzbuzz_spec.rb" ]

Tasks: TOP => default => test
(See full trace by running task with --trace)

So we need to fix the fizzbuzz method to that it satisfies the test cases.

# Fizzbuzz.rb
require_relative '../Fizzbuzz.rb'

class FizzBuzz::Fizzbuzz
  def self.fizzbuzz(number)
    if number % 3 != 0 && number % 5 != 0
      return number.to_s
    elsif number % 3 == 0 && number % 5 != 0
      return "Fizz"
    else
      return "Buzz"
    end
  end
end

Now it passes the tests, but we still have more tests to write.

Exercise 1: Finish The Test Cases

Now write the further two test cases and edit the fizzbuzz method to solve the proposed problem.

Exercise 2: Refactoring

Notice that our solution is not terrifically efficient (technical term). It potentially compares the number to 3 & 5 multiple times, so there is a more efficient method. Attempt to improve on the solution, and then re-run the tests to make sure it still satisfies the specs.

Finished Solution

You can see a finished solution below:

Resources

project_walkthrough's People

Contributors

cheezitman avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.