Exercise Monday

Goal: In this classwork, we will focus on TDD. As we covered in intro, use the TDD process to create tests before implementing your code. If you complete Ping Pong with a strong understanding of TDD, you have already mastered the goal for this classwork. The additional exercises offer more practice and challenge with these processes. Success is measured in understanding, not in completion of every item on the page. By design, we offer more exercises than most can accomplish in a single class session. This way, should you ever need to go back and review, you can complete new exercises. Always make understanding your measurement of success.

Warm Up


  • What does it mean that web development can be test-driven?
  • What is represented in a specification?
  • What is the purpose of refactoring? Why is refactoring ideally completed before moving on to the next specification?

Code


Ping-Pong

To start off our first course section of Ruby, we'll begin with ping pong, a classic programming exercise that utilizes branching and looping. Here's how it works:

  • A user enters a number and the application returns all numbers from 1 to the user input with the following changes:
    • All numbers divisible by 3 are replaced by "ping".
    • All numbers divisible by 5 are replaced by "pong"
    • All numbers divisible by both 3 and 5 are replaced by "ping-pong".

If the user enters 15, the program will return:

1
2
"ping"
4
"pong"
"ping"
7
8
"ping"
"pong"
11
"ping"
13
14
"ping-pong"

Make sure you use a TDD approach to break down the problem into the smallest possible behaviors. Here is a spec to get you started:

describe('#ping_pong') do
   it("returns an array of ascending numbers up to the number entered") do
    expect(ping_pong(2)).to(eq([1,2]))
  end

Title Case with TDD

Using the previous lessons as reference, create a Ruby method that takes a string of word(s) from the user and returns it in title case.

Write plain English versions of your specs first.

Don't be tempted to cut and paste the Title Case code you've seen. Talk through what behaviors need to be built and write tests for each one at a time. Create code to make each pass before moving to the next behavior/spec.

Final Check: Before moving onto the next exercise, have a teacher review your final code and specs.

Leetspeak

Leetspeak uses an alternative alphabet of numbers and symbols to replace various letters in words. For example, "leet" becomes "1337" and "Epicodus" might become "3p1c0duz".

Write a Ruby method that converts a string using some of the rules of Leetspeak:

  • The letter "e" should be replaced with "3".
  • The letter "o" should be replaced with "0".
  • The capital letter "I" (but not the lower case, "i") should be replaced with "1".
  • All instances of "s" should be replaced with "z" UNLESS it is the first letter of the word.

Here is a sample of input and output

In: "Don't you love these 'String' exercises? I do!"
Out: "D0n't y0u l0ve theze 'String' exercizez? 1 d0!"

describe('String#leetspeak') do
  it('returns a string as is when no Leetspeak rules apply') do
    expect("happy".leetspeak).to(eq("happy"))
  end
end

Here are some additional specs to complete with code:

describe('String#leetspeak') do
  it('replaces every "e" in a string with a "3"') do
    expect("elephant".leetspeak).to(eq("3l3phant"))
  end

 it('replaces every "o" in a string with a "0"') do
    expect("boo boo".leetspeak).to(eq("b00 b00"))
  end

 it('replaces every "I" in a string with a "1"') do
    expect("I like Ike".leetspeak).to(eq("1 lik3 1k3"))
  end

 it('replaces every "s" in a string with a "z"') do
    expect("roses".leetspeak).to(eq("r0z3z"))
  end

  it('does NOT replace the first letter when it is an s') do
    expect("sassafrass".leetspeak).to(eq("sazzafrazz"))
  end

  it('replaces letters correctly in a string of words') do
    expect("I scream you scream we all scream for raspberry ice cream.".leetspeak).to(eq("1 scr3am y0u scr3am w3 all scr3am f0r razpb3rry ic3 cr3am"))
  end
end

Wh3n y0u hav3 compl3t3d writing c0d3 so that all specz are pazzing, azk a t3ach3r for a c0de r3vi3w b3f0r3 y0u pr0c33d!

Queen Attack

If you finish the Leetspeak project and have it checked by an instructor, try this one. In chess, a queen can move horizontally, vertically, and diagonally, making it the most powerful piece on the board. If another piece is within its line of sight, the queen can attack it. Make a method that is called on the position on the board of the queen and takes as an argument the position of the other piece. The method should tell whether the queen can attack the other piece.

Here's your first test:

describe('Array#queen_attack?') do
  it('is false if the coordinates are not horizontally, vertically, or diagonally in line with each other') do
    expect([1,1].queen_attack?([2, 3])).to(eq(false))
  end
end

By creating a test that uses positions where the queen CANNOT attack, the code to analyze horizontal, vertical and diagonal is not yet necessary. For this code to pass, we simply need to run the method and return false. This is the MOST simple behavior which is why it is first. What ultimately becomes your else condition is often the simplest spec to write first.

After you make this pass, write a spec for a queen attacking horizontally, and get it to pass; then one for vertically, and get it to pass; and finally one for diagonally. Remember to refactor or simplify your code, if appropriate. Don't be tempted to write a single test for the true case (e.g., it 'is true if it can attack horizontally or vertically or diagonally') — there are three separate behaviors here for horizontal, vertical, and diagonal. Make a commit after each successful passing test!

Have your instructor over for a code review before proceeding to Clock angle.

Clock Angle

Time for something a little more complicated. Let's write a method that tells us, given a certain time, the distance between the minute and hour hands on an analog clock. For example, 12 o'clock would return 0º and 6 o'clock would return 180º.

Always return the smaller distance and be as precise as possible. Make sure to think about the object class of the input — all form inputs from params are strings. In order to convert a String to a Float, you need to use the String#to_f() method. Try it out in IRB to get the hang of this new method.

Peer Code Review


  • Do specs have complete coverage for behaviors that need to be tested?
  • Are specs all passing?
  • Is the Ruby logic easy to understand?
  • Is code indented and spaced properly?
  • Are variable names descriptive?

Lesson 4 of 22
Last updated August 7, 2022