Self-testing Code in Ruby¶ ↑
% : subtitle % in Ruby : author
Giovanni Sakti
: institution
Starqle
% : content-source % id_ruby % : date % 2017-10-16 : theme
starqle
Self-testing code¶ ↑
(('tag:center'))What is Self-testing code?
properties¶ ↑
: hide-title true
Self-testing code¶ ↑
* Code that have built-in tests * The tests serve as a binding contract * The tests can be run arbitrarily
TDD¶ ↑
(('tag:center'))What is TDD? How it differs from self-testing code?
properties¶ ↑
: hide-title true
TDD¶ ↑
* Practices of writing ((*tests*)) before the code * Ensure that the code is self-tested * It is, however, ((*optional*)) to do TDD to write self-testing code
TDD¶ ↑
(('tag:center'))But some companies enforce TDD because TDD enforces ((YAGNI)) principle
TDD¶ ↑
(('tag:center'))We'll see why…
TDD Steps¶ ↑
* Write a test * Run the test, it should fail * Write code just enough to pass the test * Run the test * Repeat
TDD & YAGNI¶ ↑
(('tag:center'))Because we only write just enough code to pass the test, there will be no unnecessary codes
Test in Ruby¶ ↑
(('tag:center'))There are several tools for doing testing in ruby
Test in Ruby¶ ↑
* RSpec * Minitest * test-unit
Test in Ruby¶ ↑
(('tag:center'))Let's try using RSpec
RSpec Install¶ ↑
# rouge console % gem install rspec
RSpec Help¶ ↑
# rouge console % rspec --help
TDD with RSpec¶ ↑
(('tag:center'))Now let's do TDD practice using RSpec
properties¶ ↑
: hide-title true
TDD with RSpec (1)¶ ↑
Create a simple test of program that we want to create
# rouge ruby # game_spec.rb RSpec.describe Game do describe "#score" do it "returns 0 for new game" do game = Game.new expect(game.score).to eq(0) end end end
TDD with RSpec (2)¶ ↑
Run the example and watch it fail
# rouge console % rspec game_spec.rb uninitialized constant Object::Game (NameError)
TDD with RSpec (3)¶ ↑
Now write just enough code to make it pass
# rouge ruby # game.rb class Game attr_reader :score def initialize @score = 0 end end
TDD with RSpec (3) cont'd¶ ↑
Now write just enough code to make it pass
# rouge ruby # game_spec.rb require './game' ...
TDD with RSpec (4)¶ ↑
Run the example and the test shall pass
# rouge console % rspec game_spec.rb --color --format doc Game #score returns 0 for all gutter game Finished in 0.00057 seconds 1 example, 0 failures
TDD with RSpec (5)¶ ↑
(('tag:center'))Repeat with new features
RSpec API¶ ↑
(('tag:center'))Some important RSpec APIs
properties¶ ↑
: hide-title true
Basic Matchers¶ ↑
# rouge ruby # equality expect('x'+'y').to eq('xy') # a == b expect('x'+'y').to eql('xy') # a.eql?(b) expect('x'+'y').not_to be('xy') # a.equal?(b) # strings expect('abcd').to include('bc') expect('abcd').to start_with 'ab' expect('abcd').to end_with 'cd' expect('abcd').to match /[a-z]+/ # collections expect([1, 2, 3]).to include(1, 3) expect([1, 2, 3]).to contain_exactly(3, 2, 1) # order not important expect({ a: 1, b: 2 }).to include(b: 2)
Basic Matchers cont'd¶ ↑
# rouge ruby # booleans and nil expect(true).to be true expect(false).to be false expect('abc').to be_truthy expect(nil).to be_falsey expect(nil).to be_nil # numeric expect(5).to be > 4 expect(5).to be >= 4 expect(5).to be < 6 expect(5).to be <= 6 expect(5).to be_between(4, 6).exclusive expect(5).to be_between(5, 6).inclusive expect(4.99).to be_within(0.02).of(5) # errors (exceptions) expect{ 5 / 0 }.to raise_error(ZeroDivisionError) expect{ 5 / 0 }.to raise_error("divided by 0") expect{ 5 / 0 }.to raise_error(ZeroDivisionError, "divided by 0")
Predicate Matchers¶ ↑
Predicate matchers are a little DSL for calling predicate methods. Predicate methods are methods that:
* return a boolean value; and * have a name that ends with ?
Predicate Matchers cont'd¶ ↑
# rouge ruby # array expect([]).to be_empty # [].empty? # hash expect({a: 1}).to have_key(:a) # {a: 1}.has_key?(:a) expect({a: 1}).to have_value(1) # {a: 1}.has_value?(1) # object expect(5).not_to be_nil # 'hi'.nil? expect(5).to be_instance_of Fixnum # 5.instance_of?(Fixnum) expect(5).to be_kind_of Numeric # 5.kind_of?(Numeric)
Predicate Matchers cont'd¶ ↑
Predicate matchers work on all objects, including custom classes
Exercises¶ ↑
(('tag:center'))Now let's do some exercises…
properties¶ ↑
: hide-title true
TDD Exercises (1)¶ ↑
Create a Sentence from Wordsn
Without using “Array#each” iterator, create a method that will return a sentence when given an array of words.
# rouge ruby create_sentence(["hello", "world"]) # will return: "hello world"
TDD Exercises (2)¶ ↑
Find Palindromes
Write a method that receives two positive integers “m” and “n” and returns an array of “n” palindrome numbers after “m” (including “m” itself).
# rouge ruby find_palindrome(100, 2) # will return [101, 111] find_palindrome(22, 3) # will return [22, 33, 44]
TDD Exercises (3)¶ ↑
Descending Order
Create a method that receives an integer as an argument and rearrange it to generate biggest possible value.
# rouge ruby descending(21445) # will return 54421 descending(145263) # will return 654321 descending(1254859723) # will return 9875543221
TDD Exercises (4)¶ ↑
Deep Count
Create a method called deep_count that will return the number of elements in an array, including the number of elements of its sub arrays.
TDD Exercises (4) cont'd¶ ↑
# rouge ruby deep_count([]) # will return 0 deep_count([1, 2, 3]) # will return 3 deep_count(["x", "y", ["z"]]) # will return 3 elements ("x", "y", ["z"]) in main array # plus 1 element ("z") in sub array # total = 4 elements deep_count([1, 2, [3, 4, [5]]]) # total = 7 elements deep_count([[[[[[[[[]]]]]]]]]) # total = 8 elements
TDD Exercises (5)¶ ↑
Letter Count
Create a method that receives a string as its argument and returns a hash that shows the number of occurrence of each letter in that string.
TDD Exercises (5) cont'd¶ ↑
# rouge ruby letter_count("gojek") # will return {:g=>1, :o=>1, :j=>1, :e=>1, :k=>1} letter_count("kolla") # will return {:k=>1, :o=>1, :l=>2, :a=>1} letter_count("scholarship") # will return {:s=>2, :c=>1, :h=>2, :o=>1, :l=>1, :a=>1, :r=>1, :i=>1, :p=>1}
Thanks¶ ↑
(('tag:center'))Thanks
properties¶ ↑
: hide-title true