Showing posts with label Ruby. Show all posts
Showing posts with label Ruby. Show all posts

Tuesday, June 4, 2013

Running slow test (with rake)

Situation: I have a test file that takes a really long time to run. What do I do?

Propose solution: Don't test the file? Of course not! The test file is there to ensure I didn't break anything.  If I don't test it, how do I know my code is working as intended? How do I know the new code didn't break existing code? 

Real solution: Separate out the tests. 

Example: I am working on a TicTacToe game. The game has a computer player that uses minimax to compute the next step. This algorithm takes a long time to calculate when the board has a lot of empty squares. So whenever I add new code and run rspec, it takes a really long time to run, even though the new code has nothing to do with the computer logic. So what I really want is simple command to:
  1. run all test
  2. run slow test
  3. run all test excluding the slow test
If I separated it out like that, I can run most of my test regularly and the slow one before I check in or when I actually change the file. How do I do this? Use Rake. 

Rake is a build tool created by Jim Weirich for Ruby. There are many articles on Rake, including Martin Fowler's detailed explanation. Most didn't really explained what I wanted, so I asked other apprentices for help. Thankful Taka is knowledgable on the subject and offered to help :)

Steps:

  1. Install rake "gem install rake"
  2. Create a file in your project directory call "Rakefile"
  3. Add the tasks to your rake file. This is what my rake file looks like: 
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:default)

RSpec::Core::RakeTask.new(:slow_test) do |t|
  t.rspec_opts = "--tag slow_test"
end

RSpec::Core::RakeTask.new(:test) do |t|
  t.rspec_opts = "--tag ~slow_test"
end
What the file say?
  • Line 3 says, run all tests as the default. 
    • Command: rake
  • Line 5 created a new task called "slow_test" which runs all the test with the tag "slow test" (more on this later). Note that you specify the task name with ":task_name".
    • Command: rake slow_test
  • Line 9 created a new task called "test" which runs all the test excluding the one with the tag "slow_test".
    • Command: rake test
In addition, I can also create a command to run my game. Normally, I would type in "ruby bin/tictactoe", but I can make a shorter command using rake. I called my task play, so I can just type "rake play" and it will start the game for me. Cool eh?
task :play do
  ruby "bin/tic_tac_toe"
end

Tagging test files

I will need to do more research on the topic, but for this rake file to work, I need to add this to my computer test file.
Normal
describe TicTacToe::Player::Computer do
end
Tagged.
Note Taka added a comma after the class under test, then the tag.
describe TicTacToe::Player::Computer, :slow_test => true do
end

Monday, May 20, 2013

Ruby: Loops with value and index

How many way can you loop through an array in Ruby? A lot! I don't want to give a an exact count because I don't think I've learned them all. Here's just a list of the one I came across so far.

Situation:

I have two arrays, secret_code and guess.
secret_code = [2, 4, 6, 8]
guess = [8, 4, 6, 2]
I need to count how many values in the guess array that matches the values and the positions in the secret_code. So for the example above, my method should return 2 because 4 and 6 matches the values and position. 2 and 8 only match the values, but not the positions, so they don't count. This is what the test looks like.
describe MastermindRules do  
  it "counts the exact match when there exact match and value match" do
    secret_code = [2, 4, 6, 8]
    guess = [8, 4, 6, 2]
    MastermindRules.new.exact_match(secret_code, guess).should == 2
  end
end
This is the method api:
class MastermindRules
  
  def exact_match(secret_code, guess)
    
  end
  
end
So how many ways can I write the exact match method? Let's find out!

while loop

  def exact_match(secret_code, guess)
    exact_match = 0
    index = 0

    while index < secret_code.size
        exact_match += 1 if secret_code[index] == guess[index]
        index += 1
    end

    exact_match
  end

until loop

  def exact_match(secret_code, guess)
    exact_match = 0
    index = 0

    until index >= secret_code.size
        exact_match += 1 if secret_code[index] == guess[index]
        index += 1
    end
    
    exact_match
  end
Note that the condition statement is different than the while loop. In the while loop, it says, execute the loop while index is less than secret_code.size. But for the until statement, it says, execute the loop until the index is greater or equal the the code size. Which one do you choose? I prefer the one that is easier to understand. For example, if I have a method call game.over? I would use until game.over? because while sounds a little clunky, while !game.over?. On the other hand, if I have a method call game.in_progress? I would use while game.in_progress? but not until !game.in_progress?. In this case, if I have to choose between while and until, I would choose while.

loop loop

  def exact_match(secret_code, guess)
    exact_match = 0
    index = 0
    
    loop do
     break if index >= secret_code.size 
        exact_match += 1 if secret_code[index] == guess[index]
        index += 1
    end
    
    exact_match
  end
All of these feel a little clunky don't they? I have to initialize the index and manually increment it. The good thing I learned about Ruby is that if it feels clunky, there's probably a better way to write it.

for loop

  def exact_match(secret_code, guess)
    exact_match = 0

    for index in 0...secret_code.size
      exact_match += 1 if secret_code[index] == guess[index]
    end

    exact_match
  end
Note there are three periods for 0...secret_code.size. This gives me a range from 0 to secret_code.size, not including the code size which for me means 0-3. If there are two periods, then the range is 0-4. The two periods will make my test fail because secret_code[4] = nil and guess[4] = nil, which means my exact_match count will be 3. But I don't want that. So be careful with ranges. At the same time, I can also specify an array to loop through. However, this is not flexible for our case because we can't change the secret_code size on the fly.
  def exact_match(secret_code, guess)
    exact_match = 0

    for i in [0, 1, 2, 3]
      exact_match += 1 if secret_code[i] == guess[i]
    end

    exact_match
  end

each_with_index

  def exact_match(secret_code, guess)
    exact_match = 0

    secret_code.each_with_index do |value, index|
      exact_match += 1 if secret_code[index] == guess[index]
    end

    exact_match
  end

range with each

  def exact_match(secret_code, guess)
    exact_match = 0
    
    (0...secret_code.size).each do |index|
      exact_match += 1 if secret_code[index] == guess[index]
    end
    
    exact_match
  end

times

  def exact_match(secret_code, guess)
    exact_match = 0
    
    (secret_code.size).times do |index|
      exact_match += 1 if secret_code[index] == guess[index]
    end
    
    exact_match
  end

upto

  def exact_match(secret_code, guess)
    exact_match = 0
    
    0.upto(secret_code.size-1) do |index|
      exact_match += 1 if secret_code[index] == guess[index]
    end
    
    exact_match
  end

step

  def exact_match(secret_code, guess)
    exact_match = 0
    
    0.step(secret_code.size, 2) do |index|
      exact_match += 1 if secret_code[index] == guess[index]
    end
    
    exact_match
  end
As the name suggest, step is probably better when you want to skip elements in an array, but not when you need to loop through each element.
So with all these looping options, which one do you choose? It's really up to you. I prefer the one that best expresses my intent. For my problem, I chose each_with_index because when I read this, I know that the value and the index is important.
For more details, Alan has a great article on loops in ruby.

Monday, April 29, 2013

Ruby: Accessor

Instance variables in Ruby are private by default.
class Book
  def initialize
    @title = "Ender's Game"
    @author = "Orson Scott Card"
    @location = "Chicago"
  end
end

book = Book.new
book.title = "Programming Ruby"
puts (book.title)
If you run the program, you get a no method error.
accessor.rb:10:in `
': undefined method `title=' for # (NoMethodError)

In order to access to the variables, you need to specify what type of access you want to grant. There are three types of access:
  • attr_reader - for read access only
  • attr_writer - for write access only
  • attr_accessor - for read and write access
Let's say you're writing a book. It's the first draft. You are sending it your friends for review. Let's imagine it's pre-email day, so you're sending it through snail mail. You only have one copy (because you're a poor author), so you want to keep track of where it is. So you have a location that others can read or write to. You haven't decided on a title yet, therefore you don't want anyone else to see it. They can change it, but they can't see it. As for the author, because it's your book, this is something that can't be change so others can only read this field. Let's review.

You have a book with the following attributes:
  • Title - write only access
  • Author - read only access
  • Location - read and write
So your class looks something like this:
class Book
  attr_reader :author
  attr_writer :title
  attr_accessor :location
  
  def initialize
    @title = "Ender's Game"
    @author = "Orson Scott Card"
    @location = "Chicago"
  end
end
So what can you do?
book = Book.new

book.title = "Ender's Shadow"

puts(book.author)

puts(book.location) 
book.location = "Philly"
You can do all that, but if you try to read the title or change the author, you will get an error.
put(book.title)

accessor.rb:22:in `
': undefined method `title' for # (NoMethodError) book.author = "Me" accessor.rb:23:in `
': undefined method `author=' for # (NoMethodError)

The really cool thing about all this is that one day, if you decide to collect all the friends ideas for the book title, you can do it, without changing the caller.
class Book
  attr_reader :author, :suggested_titles
  attr_accessor :location
  
  def initialize
    @title = "Ender's Game"
    @author = "Orson Scott Card"
    @location = "Chicago"
    @suggested_titles = []
    @suggested_titles << @title
  end
  
  def title=(title)
    @title = title
    @suggested_titles << @title
  end
end

book = Book.new
book.title = "Ender's Shadow"
puts(book.suggested_titles)
Even though I added def title, I still set the title by typing book.title = "book name". One thing to note is that you can specify more than variables to the attribute by separating them with commas as I did with :author and :suggested_titles.