Testing Ruby

Testing Ruby

Slides for my presentation at HackBulgaria Ruby course - coreruby.github.io

7a0e72a6f55811246bb5d9a946fd2e49?s=128

Radoslav Stankov

December 04, 2014
Tweet

Transcript

  1. 3.
  2. 4.

    • 2002 - първия ми “професионален проект” (flash) • 2008

    - първи PHP unit test • 2009 - първи Ruby on Rails проект • 2009 - първи Ruby unit test • 2010 - официално Ruby developer • 2010 - първи selenium acceptance test • 2014 - все още пиша главно на Ruby … :P
  3. 5.
  4. 7.

    • За какво са ни нужни тестове? • Какво е

    автоматичен тест? • Какви видове тестове има? • Какво е добър тест? • Как да пишем добри тестове? • Какво трябва да тестваме? • Какво са “Mocks”? • Какво е “RSpec”? • Какво е “Test Driven Development”? • Какво е “Fragile tests”?
  5. 9.
  6. 10.
  7. 11.
  8. 12.
  9. 13.
  10. 14.
  11. 15.
  12. 16.

    puts "1 + 1 = #{Calculation.new("1 + 1").value}" puts "2

    - 1 = #{Calculation.new("2 - 1").value}" puts "3 * 2 = #{Calculation.new("3 * 2").value}"
  13. 19.

    raise "INVALID" unless Calculation.new("1 + 1").value == 2 raise "INVALID"

    unless Calculation.new("2 - 1").value == 1 raise "INVALID" unless Calculation.new("3 * 2").value == 6 puts 'Everything okay :)'
  14. 21.

    assert_calculation '1 + 1', 2 assert_calculation '2 - 1', 1

    assert_calculation '3 * 2', 6 puts 'Everything okay :)'
  15. 22.

    • code task 1 • check task 1
 • code

    task 2 • check task 2 • check task 1
 • code task 3 • check task 3 • check task 2 • check task 1
 • code task 4 • check task 4 • check task 3 • check task 2 • check task 1 • code task 1 • code task 1 test • run tests
 • code task 2 • code task 2 test • run tests
 • code task 3 • code task 3 test • run tests
 • code task 4 • code task 4 test • run tests Manual Automatic
  16. 23.

    • code task 1 • check task 1
 • code

    task 2 • check task 2 • check task 1
 • code task 3 • check task 3 • check task 2 • check task 1
 • code task 4 • check task 4 • check task 3 • check task 2 • check task 1 • code task 5 • check task 4 • check task 4 • check task 3 • check task 2 • code task 1 • code task 1 test • run tests
 • code task 2 • code task 2 test • run tests
 • code task 3 • code task 3 test • run tests
 • code task 4 • code task 4 test • run tests • code task 5 • code task 5 test • run tests Manual Automatic
  17. 24.

    • Safety net • Reduces bugs in new or existing

    features • Forces you to understand the features and communication between components • Reduces the cost for new features • Making code testable decreases complexity and modularity
  18. 26.

    class CalculationTest < Minitest::Test def test_add assert_equal 2, Calculation.new('1 +

    1').value end def test_subtract assert_equal 1, Calculation.new('2 - 1').value end def test_multiply assert_equal 6, Calculation.new('3 * 2').value end end
  19. 27.
  20. 28.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end
  21. 29.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end Test Suite
  22. 30.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end
  23. 31.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end Test Setup
  24. 32.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end
  25. 33.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end Test Case
  26. 34.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end
  27. 35.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end Assertion
  28. 36.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end
  29. 40.

    Unit Test Integration Test Acceptance Test Tests one object Tests

    one “component” Tests the whole stack Isolated with mocks Some times uses stubs Uses the UI
 Expresses domain terms Fast Slow Very slow Helps for good design Helps for verification Helps for verification
  30. 47.

    “Когато даден тест се счупи, трябва да знам точно кой

    тест се е счупил и защо” Локализирани
  31. 49.
  32. 56.

    class OrderTest < Minitest::Test def test_proccess order = Order.new assert_equal

    Date.today, order.initiated_at.to_date assert_nil order.user assert order.empty? product = Product.new order << product assert_equal 1, order.size assert !order.valid? user = User.new order.user = user assert_equal user, order.user assert !order.valid?
  33. 57.

    assert !order.valid? user = User.new order.user = user assert_equal user,

    order.user assert !order.valid? def Order.can_be_processed_today? true end assert order.valid? order.cancel assert_equal :canceled, order.state end end
  34. 58.

    class OrderTest < Minitest::Test def test_proccess order = Order.new assert_equal

    Date.today, order.initiated_at.to_date assert_nil order.user assert order.empty? product = Product.new order << product assert_equal 1, order.size assert !order.valid? user = User.new order.user = user assert_equal user, order.user assert !order.valid? def Order.can_be_processed_today? true end assert order.valid? order.cancel assert_equal :canceled, order.state end end
  35. 59.
  36. 60.
  37. 61.
  38. 62.

    class OrderTest < Minitest::Test def setup @order = Order.new end

    def around(&block) Order.stub :can_be_processed_today?, true, &block end def test_initiated_at assert_equal Date.today, @order.initiated_at.to_date end def test_add_product product = Product.new @order << product assert_equal [product], @order.products assert_equal 1, @order.size assert !@order.empty? end
  39. 63.

    def test_add_product product = Product.new @order << product assert_equal [product],

    @order.products assert_equal 1, @order.size assert !@order.empty? end def test_valid @order.user = User.new @order << Product.new assert @order.valid? end def test_valid_when_no_user @order << Product.new assert !@order.valid? end
  40. 64.

    def test_valid_when_no_user @order << Product.new assert !@order.valid? end def test_valid_when_no_products

    @order.user = User.new assert !@order.valid? end def test_cancel @order.cancel assert_equal :canceled, @order.state end end
  41. 65.

    class OrderTest < Minitest::Test def setup @order = Order.new end

    def around(&block) Order.stub :can_be_processed_today?, true, &block end def test_initiated_at assert_equal Date.today, @order.initiated_at.to_date end def test_add_product product = Product.new @order << product assert_equal [product], @order.products assert_equal 1, @order.size assert !@order.empty? end def test_valid @order.user = User.new @order << Product.new assert @order.valid? end def test_valid_when_no_user @order << Product.new assert !@order.valid? end def test_valid_when_no_products @order.user = User.new assert !@order.valid? end def test_cancel @order.cancel assert_equal :canceled, @order.state end end
  42. 66.
  43. 72.

    Query Command ▶︎ Incomming Check result directly Check side effects

    via public api Send to self (private) ✕ No tests ✕ No tests ◀ Outgoing ✕ 
 No tests Check with expect to send (mock)
  44. 75.

    ◀ Outgoing command class MyObjectTest < Minitest::Test def test_my_method array

    = Minitest::Mock.new array.expect :clear, :returned custom = MyObject.new custom.clear array array.verify end end
  45. 76.
  46. 78.

    class ArrayTest < Minitest::Test def setup @array = [] end

    def test_empty assert @array.empty? end def test_push @array << 1 assert_equal [1], @array end end
  47. 79.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end
  48. 80.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end Example Group
  49. 81.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end
  50. 82.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end Setup
  51. 83.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end
  52. 84.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end Example
  53. 85.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end
  54. 86.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end Expectation
  55. 87.

    describe Array do before do @array = [] end it

    "can tell when it is empty" do expect(@array).to be_empty end it "can filled with items" do @array << 1 expect(@array).to eq [1] end end
  56. 88.

    describe Array do let(:array) { [] } it "can tell

    when it is empty" do expect(array).to be_empty end it "can filled with items" do array << 1 expect(array).to eq [1] end end
  57. 89.

    describe Array do let(:array) { [] } it "can tell

    when it is empty" do expect(array).to be_empty end it "can filled with items" do array << 1 expect(array).to eq [1] end end
  58. 90.

    describe Array do describe "#empty?" do it "returns true when

    empty" do expect(array).to be_empty end it "returns false when not empty" do array = [1] expect(array).to be_empty end end end
  59. 91.
  60. 92.
  61. 93.
  62. 94.

    Fake Used as a simpler implementation, e.g. using an in-memory

    database in the tests instead of doing real database access.
  63. 95.

    Dummy Used when a parameter is needed for the tested

    method but without actually needing to use the parameter.
  64. 97.

    Spy Records arguments, return value, the value of this and

    exception thrown (if any) for all its calls.
  65. 98.

    Mock Mocks are fake methods (like spies) with pre-programmed behaviour

    (like stubs) as well as pre-programmed expectations. A mock will fail your test if it is not used as expected.
  66. 100.

    describe PostsController do stub_rendering stub_current_user let(:post) { double 'Post' }

    describe "GET 'index'" do it "responds with posts of a given category" do allow(Post).to receive(:for_category).with(‘1’).and_return [post] get :index, category_id: '1' expect(controller).to respond_with [post] end end describe "POST 'create'" do it "creates new post" do allow(Post).to receive(:create).and_return post post :create, post: {title: 'test'} expect(Post).to have_received(:create).with(user: current_user, title: 'tes end end end
  67. 105.

    Test Driven Development 1 Добавя се тест ... за несъществуващ

    код 2 Пише се код ... колкото само тестът да мине
  68. 106.

    Test Driven Development 1 Добавя се тест ... за несъществуващ

    код 3 Правят се подобрения ... подобрява се качеството на кода 2 Пише се код ... колкото само тестът да мине
  69. 107.

    Test Driven Development 1 Добавя се тест ... за несъществуващ

    код 3 Правят се подобрения ... подобрява се качеството на кода 2 Пише се код ... колкото само тестът да мине
  70. 108.

    describe Calculation do it "can sum numbers" do calculation =

    Calculation.new('1 + 1') expect(calculation.value).to eq 2 end end
  71. 111.
  72. 112.
  73. 113.
  74. 114.

    describe Calculation do # . . . it "can subtract

    numbers" do calculation = Calculation.new('2 - 1') expect(calculation.value).to eq 1 end end
  75. 115.
  76. 116.

    class Calculation def initialize(expression) @expression = expression end def value

    a, operation, b = @expression.split ' ' if operation == '+' a.to_i + b.to_i elsif operation == '-' a.to_i - b.to_i end end end
  77. 117.
  78. 118.

    describe Calculation do # . . . it "can multiply

    numbers" do calculation = Calculation.new('3 * 2') expect(calculation.value).to eq 6 end end
  79. 119.
  80. 120.

    class Calculation def initialize(expression) @expression = expression end def value

    a, operation, b = @expression.split ' ' if operation == '+' a.to_i + b.to_i elsif operation == '-' a.to_i - b.to_i elsif operation == '*' a.to_i * b.to_i end end end
  81. 121.
  82. 122.

    describe Calculation do # . . . it "raises an

    error on unknown operation" do calculation = Calculation.new('3 ~ 2') expect { calculation.value }.to raise_error end end
  83. 123.

    class Calculation def initialize(expression) @expression = expression end def value

    a, operation, b = @expression.split ' ' if operation == '+' a.to_i + b.to_i elsif operation == '-' a.to_i - b.to_i elsif operation == '*' a.to_i * b.to_i else raise "Can't parse expression - #{@expression}" end end end
  84. 124.

    class Calculation def initialize(expression) @expression = expression end OPERATIONS =

    { '+' => ->(a, b) { a + b }, '-' => ->(a, b) { a - b }, '*' => ->(a, b) { a * b } } def value a, operand, b = @expression.split ' ' operation = OPERATIONS[operand] raise "Can't parse expression - #{@expression}" unless operation operation.call a.to_i, b.to_i end end
  85. 125.

    describe Calculation do # . . . it "can divide

    numbers" do calculation = Calculation.new('6 / 3') expect(calculation.value).to eq 2 end end
  86. 126.
  87. 127.

    class Calculation def initialize(expression) @expression = expression end OPERATIONS =

    { '+' => ->(a, b) { a + b }, '-' => ->(a, b) { a - b }, '*' => ->(a, b) { a * b }, '/' => ->(a, b) { a / b } } def value a, operand, b = @expression.split ' ' operation = OPERATIONS[operand] raise "Can't parse expression - #{@expression}" unless operation operation.call a.to_f, b.to_f end end
  88. 128.
  89. 129.

    class Calculation OPERATIONS = { '+' => ->(a, b) {

    a + b }, '-' => ->(a, b) { a - b }, '*' => ->(a, b) { a * b }, '/' => ->(a, b) { a / b } } attr_reader :expression def initialize(expression) @expression = expression end def value @value ||= calculate_value end def to_s “#{expression} = #{value}" end private def calculate_value left, operand, right = expression.split ' ' operation = OPERATIONS[operand] raise "Can't parse expression - #{@expression}" unless operation operation.call left.to_f, right.to_f end end
  90. 132.

    class OrderTest < Minitest::Test def test_expired order = Order.new( user:

    User.new, product: [Product.new] town: 'Sofia' initiated_at: 5.days.ago ) assert order.expired? end end ✗
  91. 135.

    class OrderTest < Minitest::Test def setup @order = Order.new end

    def test_valid @order.user = User.new @order << Product.new assert @order.valid? end def test_valid_when_no_user @order << Product.new assert !@order.valid? end def test_valid_when_no_products @order.user = User.new assert !@order.valid? end end ✗
  92. 136.

    class OrderTest < Minitest::Test def setup @order = Order.new( user:

    User.new, products: [Product.new] ) end def test_valid assert @order.valid? end def test_valid_when_no_user @order.user = nil assert !@order.valid? end def test_valid_when_no_products @order.products = [] assert !@order.valid? end end ✔
  93. 138.

    describe "Task" do describe ".complete_all" do it "completes a task

    by text mask" do task = create_task text: 'Create slides' other = create_task text: 'Create examples' Task.complete_all query: 'slides' expect(Task.ongoing).to eq [task] end end end ✗
  94. 139.

    describe "Task" do describe ".complete_all" do it "completes tasks who

    match query" do task = create_task text: 'Create slides' Task.complete_all query: 'slides' expect { task.reload }.to change { task.completed? }.to true end it "doesn't complete tasks who don't match query" do task = create_task text: 'Create examples' Task.complete_all query: 'slides' expect { task.reload }.not_to change { task.completed? } end end end ✔
  95. 140.

    describe "Task" do # . . . describe ".ongoing" do

    it "selects ongoing tasks" do ongoing = create_task state: :ongoing completed = create_task state: :completed expect(Task.ongoing).to eq ongoing end end end ✔
  96. 142.

    describe Wallet do describe "#buy" do amount = true gateway

    = double 'Gateway' processor = double 'Processor' allow(gateway).to receive(:active?).and_return true allow(gateway).to receive(:processor).and_return processor allow(processor).to receive(:enough_money?).and_return true allow(processor).to receive(:process) Wallet.new(gateway).buy amount: 5 expect(processor).to have_received(:process).with(5) end end ✗
  97. 143.

    class Wallet def initialize(gateway) @gateway = gateway end def buy(amount:

    amount) return false if @gateway.active? processor = @gateway.processor return false if processor.enough_money?(amount) processor.process amount end end
  98. 144.

    describe Wallet do describe "#buy" do amount = true processor

    = double 'Processor' allow(gateway).to receive(:process) Wallet.new(gateway).buy amount: 5 expect(processor).to have_received(:process).with(5) end end ✔
  99. 146.
  100. 148.
  101. 149.
  102. 150.
  103. 152.

    • За какво са ни нужни тестове? • Какво е

    автоматичен тест? • Какви видове тестове има? • Какво е добър тест? • Как да пишем добри тестове? • Какво трябва да тестваме? • Какво са “Mocks”? • Какво е “RSpec”? • Какво е “Test Driven Development”? • Какво е “Fragile tests”?
  104. 155.
  105. 156.