Quality Assurance - Based on TDD example

Programmer helping with quality

Agnieszka Matysek at Fractal Soft

Test-Driven Development

TDD

Create test

```ruby require 'spec_helper' describe 'flush?' do it 'checks if array has one color' do flush_rule = flush?([1, 1, 1, 1]) expect(flush_rule).to eq(true) end end ```

Run test

```bash $ rspec spec/lib/flush_spec.rb Randomized with seed 35317 F Failures: 1) flush? checks if array has one color Failure/Error: flush_rule = flush?([1, 1, 1, 1]) NoMethodError: undefined method `flush?' for # # ./spec/lib/flush_spec.rb:5:in `block (2 levels) in ' Finished in 0.01294 seconds (files took 0.75094 seconds to load) 1 example, 1 failure ```

Create method

```ruby def flush? end ```

Run test

```bash $ rspec spec/lib/flush_spec.rb Randomized with seed 28476 F Failures: 1) flush? checks if array has one color Failure/Error: def flush? end ArgumentError: wrong number of arguments (given 1, expected 0) # ./spec/lib/flush_spec.rb:3:in `flush?' # ./spec/lib/flush_spec.rb:9:in `block (2 levels) in ' Finished in 0.01024 seconds (files took 0.66129 seconds to load) 1 example, 1 failure ```

Add argument

```ruby def flush?(array) end ```

Run test

```bash $ rspec spec/lib/flush_spec.rb Randomized with seed 34173 F Failures: 1) flush? checks if array has one color Failure/Error: expect(flush_rule).to eq(true) expected: true got: nil (compared using ==) # ./spec/lib/flush_spec.rb:10:in `block (2 levels) in ' Finished in 0.05983 seconds (files took 0.83267 seconds to load) 1 example, 1 failure ```

Add first logic

```ruby def flush?(array) true end ```

Run test

```bash $ rspec spec/lib/flush_spec.rb Randomized with seed 40116 . Finished in 0.01189 seconds (files took 0.65796 seconds to load) 1 example, 0 failures ```

Add new test

```ruby describe 'flush?' do it 'checks if array has one color' it 'checks if array has more then one color' do flush_rule = flush?([1, 1, 2, 1]) expect(flush_rule).to eq(false) end end ```

And run this tests

```bash $ rspec spec/lib/flush_spec.rb Randomized with seed 6606 .F Failures: 1) flush? checks if array has more then one color Failure/Error: expect(flush_rule).to eq(false) expected: false got: true (compared using ==) # ./spec/lib/flush_spec.rb:15:in `block (2 levels) in ' Finished in 0.04907 seconds (files took 0.63654 seconds to load) 2 examples, 1 failure ```

Add more logic

```ruby def flush?(array) array.uniq.size == 1 end ```

Run tests

```bash $ rspec spec/lib/flush_spec.rb Randomized with seed 33907 .. Finished in 0.01092 seconds (files took 0.57891 seconds to load) 2 examples, 0 failures ```

Unexpected usage

```ruby def flush?(array) array.uniq.size == 1 end flush?([1, 2, 1]) # => false flush?(['#fff', '#fff', '#fff']) # => true ```

Existing code

```ruby class BaseClass end ```
```ruby class Item < BaseClass attr_accessor :price, :name def initialize(attributes = {}) @price = attributes[:price] @name = attributes[:name] end validates_presence_of :name validates_numericality_of :price end ```

Existing test

```ruby class TestItem < Minitest::Test def test_validation item1 = Item.new(:name => "Guitar") item2 = Item.new(:price => 55) item3 = Item.new() item4 = Item.new(:name => "Guitar", :price => "ABC") item5 = Item.new(:name => "Guitar", :price => 55) assert !item1.valid? assert item1.errors == ["price must be number"] assert !item2.valid? assert item2.errors == ["name can't be blank"] assert !item3.valid? assert item3.errors == ["name can't be blank", "price must be number"] assert !item4.valid? assert item4.errors == ["price must be number"] assert item5.valid? end end ```

Run test

```bash $ ruby item_test.rb Traceback (most recent call last): 3: from item_test.rb:1:in `
' 2: from item_test.rb:1:in `require_relative' 1: from /home/agnieszka/workspace/pair_programming/item.rb:6:in `' /home/agnieszka/workspace/pair_programming/item.rb:14:in `': undefined method `validates_presence_of' for Item:Class (NoMethodError) ```

Write code

```ruby class BaseClass def self.validates_presence_of end end ```

Run test

```bash $ ruby item_test.rb Traceback (most recent call last): 4: from item_test.rb:1:in `
' 3: from item_test.rb:1:in `require_relative' 2: from /home/agnieszka/workspace/pair_programming/item.rb:6:in `' 1: from /home/agnieszka/workspace/pair_programming/item.rb:14:in `' /home/agnieszka/workspace/pair_programming/base_class.rb:7:in `validates_presence_of': wrong number of arguments (given 1, expected 0) (ArgumentError) ```

Write code

```ruby class BaseClass def self.validates_presence_of(name) end end ```

Run test

```bash $ ruby item_test.rb Traceback (most recent call last): 3: from item_test.rb:1:in `
' 2: from item_test.rb:1:in `require_relative' 1: from /home/agnieszka/workspace/pair_programming/item.rb:6:in `' /home/agnieszka/workspace/pair_programming/item.rb:15:in `': undefined method `validates_numericality_of' for Item:Class (NoMethodError) Did you mean? validates_presence_of ```

Write code

```ruby class BaseClass def self.validates_presence_of(name) end def self.validates_numericality_of end end ```

Run test

```bash $ ruby item_test.rb Traceback (most recent call last): 4: from item_test.rb:1:in `
' 3: from item_test.rb:1:in `require_relative' 2: from /home/agnieszka/workspace/pair_programming/item.rb:6:in `' 1: from /home/agnieszka/workspace/pair_programming/item.rb:15:in `' /home/agnieszka/workspace/pair_programming/base_class.rb:10:in `validates_numericality_of': wrong number of arguments (given 1, expected 0) (ArgumentError) ```

Write code

```ruby class BaseClass def self.validates_presence_of(name) end def self.validates_numericality_of(name) end end ```

Run test

```bash $ ruby item_test.rb Run options: --seed 43301 # Running: E Finished in 0.000790s, 1265.3183 runs/s, 0.0000 assertions/s. 1) Error: TestItem#test_validation: NoMethodError: undefined method `valid?' for # item_test.rb:14:in `test_validation' 1 runs, 0 assertions, 0 failures, 1 errors, 0 skips ```

Write code

```ruby class BaseClass def self.validates_presence_of(name) end def self.validates_numericality_of(name) end def valid? end end ```

Run test

```bash $ ruby item_test.rb Run options: --seed 14791 # Running: E Finished in 0.000862s, 1159.7712 runs/s, 1159.7712 assertions/s. 1) Error: TestItem#test_validation: NoMethodError: undefined method `errors' for # item_test.rb:15:in `test_validation' 1 runs, 1 assertions, 0 failures, 1 errors, 0 skips ```

Write code

```ruby class BaseClass def self.validates_presence_of(name) end def self.validates_numericality_of(name) end def valid? end def errors end end ```

Run test

```bash $ ruby item_test.rb Run options: --seed 29139 # Running: F Finished in 0.000691s, 1447.7416 runs/s, 2895.4832 assertions/s. 1) Failure: TestItem#test_validation [item_test.rb:15]: Expected false to be truthy. 1 runs, 2 assertions, 1 failures, 0 errors, 0 skips ```

Check assertions

```ruby class TestItem < Minitest::Test def test_validation item1 = Item.new(:name => "Guitar") item2 = Item.new(:price => 55) item3 = Item.new() item4 = Item.new(:name => "Guitar", :price => "ABC") item5 = Item.new(:name => "Guitar", :price => 55) assert !item1.valid? assert item1.errors == ["price must be number"] assert !item2.valid? assert item2.errors == ["name can't be blank"] assert !item3.valid? assert item3.errors == ["name can't be blank", "price must be number"] assert !item4.valid? assert item4.errors == ["price must be number"] assert item5.valid? end end ```

Run test

```bash $ ruby item_test.rb Run options: --seed 29139 # Running: F Finished in 0.000691s, 1447.7416 runs/s, 2895.4832 assertions/s. 1) Failure: TestItem#test_validation [item_test.rb:15]: Expected false to be truthy. 1 runs, 2 assertions, 1 failures, 0 errors, 0 skips ```

Write code

```ruby class BaseClass def self.validates_presence_of(name) end def self.validates_numericality_of(name) end def valid? end def errors ["price must be number"] end end ```

Run test

```bash $ ruby item_test.rb Run options: --seed 15517 # Running: F Finished in 0.000805s, 1242.0108 runs/s, 4968.0430 assertions/s. 1) Failure: TestItem#test_validation [item_test.rb:18]: Expected false to be truthy. 1 runs, 4 assertions, 1 failures, 0 errors, 0 skips ```

Check assertions

```ruby class TestItem < Minitest::Test def test_validation item1 = Item.new(:name => "Guitar") item2 = Item.new(:price => 55) item3 = Item.new() item4 = Item.new(:name => "Guitar", :price => "ABC") item5 = Item.new(:name => "Guitar", :price => 55) assert !item1.valid? assert item1.errors == ["price must be number"] assert !item2.valid? assert item2.errors == ["name can't be blank"] assert !item3.valid? assert item3.errors == ["name can't be blank", "price must be number"] assert !item4.valid? assert item4.errors == ["price must be number"] assert item5.valid? end end ```

Write code

```ruby class BaseClass def self.validates_presence_of(name) end def self.validates_numericality_of(name) end def valid? @errors_list = [] @errors_list << "name can't be blank" if @name.nil? @errors_list << "price must be number" unless @price.is_a?(Integer) @errors_list.size == 0 end def errors @errors_list end end ```

Run test

```bash $ ruby item_test.rb Run options: --seed 35483 # Running: . Finished in 0.000730s, 1369.7898 runs/s, 12328.1085 assertions/s. 1 runs, 9 assertions, 0 failures, 0 errors, 0 skips ```

TODO list

- use Getters - use Class Methods - Single Resposibility Principle - `valid?` method - Open - Close rule

Use Getters

```ruby class BaseClass def self.validates_presence_of(name) end def self.validates_numericality_of(name) end def valid? @errors_list = [] errors_list << "name can't be blank" if name.nil? errors_list << "price must be number" unless price.is_a?(Integer) errors_list.size == 0 end def errors errors_list end private attr_accessor :name, :price, :errors_list end ```

TODO list

- ~~use Getters~~ - **use Class Methods** - Single Resposibility Principle - `valid?` method - Open - Close rule - more abstraction

Use Class Methods

```ruby class BaseClass def self.validates_presence_of(name) @@presence_array ||= [] @@presence_array << name end def self.validates_numericality_of(name) end def valid? @errors_list = [] @@presence_array.each do |item| errors_list << "#{item} can't be blank" if send(item).nil? end errors_list << "price must be number" unless price.is_a?(Integer) errors_list.size == 0 end def errors errors_list end private attr_accessor :name, :price, :errors_list end ```

Use class methods

```ruby class BaseClass def self.validates_presence_of(name) @@presence_array ||= [] @@presence_array << name end def self.validates_numericality_of(name) @@numericality_array ||=[] @@numericality_array << name end def valid? @errors_list = [] @@presence_array.each do |item| errors_list << "#{item} can't be blank" if send(item).nil? end @@numericality_array.each do |item| errors_list << "#{item} must be number" unless send(item).is_a?(Integer) end errors_list.size == 0 end def errors errors_list end private attr_accessor :name, :price, :errors_list end ```

TODO list

- ~~use Getters~~ - ~~use Class Methods~~ - **Single Resposibility Principle - `valid?` method** - duplication - Open - Close rule - more abstraction

Single Responsibility Principle

```ruby class PresenceValidator attr_reader :name def initialize(name) @name = name end def error_message "#{name} can't be blank" end def valid?(item) !item.nil? end end ```

Single Responsibility Principle

```ruby class BaseClass def self.validates_presence_of(name) @@validators ||= [] @@validators << PresenceValidator.new(name) end def self.validates_numericality_of(name) @@numericality_array ||=[] @@numericality_array << name end def valid? @errors_list = [] @@validators.each do |item| errors_list << item.error_message unless item.valid?(send(item.name)) end @@numericality_array.each do |item| errors_list << "#{item} must be number" unless send(item).is_a?(Integer) end errors_list.size == 0 end def errors errors_list end private attr_accessor :name, :price, :errors_list end ```

Single responsibility principle

```ruby class NumericallityValidator attr_reader :name def initialize(name) @name = name end def error_message "#{name} must be number" end def valid?(item) item.is_a?(Integer) end end ```

Single responsibility principle

```ruby class BaseClass def self.validates_presence_of(name) @@validators ||= [] @@validators << PresenceValidator.new(name) end def self.validates_numericality_of(name) @@validators ||= [] @@validators << NumericallityValidator.new(name) end def valid? @errors_list = [] @@validators.each do |item| errors_list << item.error_message unless item.valid?(send(item.name)) end errors_list.size == 0 end def errors errors_list end private attr_accessor :name, :price, :errors_list end ```

Small fixes

```ruby class BaseClass attr_reader :errors def self.validates_presence_of(name) @validators ||= [] @validators << PresenceValidator.new(name) end def self.validates_numericality_of(name) @validators ||= [] @validators << NumericallityValidator.new(name) end def valid? @errors = [] self.class.instance_variable_get(:@validators).each do |item| errors << item.error_message unless item.valid?(send(item.name)) end errors.empty? end private attr_writer :errors end ```

TODO list

- ~~use Getters~~ - ~~use Class Methods~~ - ~~Single Resposibility Principle - `valid?` method~~ ?? - duplication ??? - ~~Open - Close rule~~ - ~~more abstraction~~ - more ???

From

```ruby class BaseClass def self.validates_presence_of(name) end def self.validates_numericality_of(name) end def valid? @errors_list = [] @errors_list << "name can't be blank" if @name.nil? @errors_list << "price must be number" unless @price.is_a?(Integer) @errors_list.size == 0 end def errors @errors_list end end ```

To

```ruby class BaseClass attr_reader :errors def self.validates_presence_of(name) @validators ||= [] @validators << PresenceValidator.new(name) end def self.validates_numericality_of(name) @validators ||= [] @validators << NumericallityValidator.new(name) end def valid? @errors = [] self.class.instance_variable_get(:@validators).each do |item| errors << item.error_message unless item.valid?(send(item.name)) end errors.empty? end private attr_writer :errors end ```
Woman on Rails logotype

Agnieszka Matysek

agnieszka (at) fractalsoft (dot) org