09. Тестване

09. Тестване

09. Тестване

9 ноември 2016

Днес

План за 19-20 ноември

традиционната планина

Четвърта задача

Въпрос 1

Какво ще се случи при изпълнение на следния код (и защо):

try
  def foo; foo; end
  foo
catch Exception => e
  puts e.class
end
  • Синтактична грешка - try и catch имат различна употреба в Ruby
  • При begin и rescue щеше да изведе SystemStackError

Въпрос 2

Какво ще се случи при изпълнение на следния код (и защо):

begin
  def foo; foo; end
  foo
rescue
  puts e.class
rescue 1 / 0
  puts e.class
rescue Exception => e
  puts e.class
end
  • Кодът в begin хвърля SystemStackError
  • Първият rescue по подразбиране хваща StandardError
  • SystemStackError не е подклас на StandardError, търсенето продължава
  • 1 / 0 хвърля ZeroDivisionError
  • Няма rescue, което да обгражда целия израз

Софтуерът и електрониката

Да сравним писането на софтуер с електрониката

Нещата, които правим, обикновено изглеждат така:

Мотивация

Хвърчащият монтаж - плюсове

Хвърчащият монтаж - проблеми

Продукт

Хардуерът в "хвърчащ монтаж" не е завършен продукт.

Legacy код

кошмарът на всеки програмист

Какво е legacy код?

Код без тестове = legacy код

Няма лесен път към просветлението

To test or not to test?

Едно е сигурно - без тестове не може.

Затова затягайте коланите и поемайте по пътя.

In testing we trust!

Терминология

Unit тестове

Интеграционни тестове

Assertion (твърдение, проверка)

An assertion is a function or macro that verifies the behavior (or the state) of the unit under test. Usually an assertion expresses a logical condition that is true for results expected in a correctly running system under test (SUT). Failure of an assertion typically throws an exception, aborting the execution of the current test.

Test Fixtures

A test fixture (also known as a test context) is the set of preconditions or state needed to run a test. The developer should set up a known good state before the tests, and return to the original state after the tests.

Test case

setup/teardown, before/after

Test Suite

A test suite is a set of tests that all share the same fixture. The order of the tests shouldn't matter.

Test Runner

Test Doubles

Общи принципи

Скорост

TDD

test-driven development

BDD

behaviour-driven development

Continuous Integration (CI)

Метрики

Тестване в Ruby

Test::Unit

Test::Unit - assertions

Test::Unit - пример

require 'test/unit'

class TC_MyTest < Test::Unit::TestCase
  # def setup
  # end

  # def teardown
  # end

  def test_fail
    assert(false, 'Assertion was false.')
  end
end

Minitest

Генериране на тестови данни

SimpleCov

Тестване на GUI

Тестване на CLI

Тестване на API-клиенти

Тестване на уеб

Rack::Test

Rack::Test is a small, simple testing API for Rack apps. It can be used on its own or as a reusable starting point for Web frameworks and testing libraries to build on.

Rack::Test - пример

require 'rack/test'

describe 'Homepage' do
  include Rack::Test::Methods

  it 'says hello' do
    authorize 'brian', 'secret'
    get '/'

    expect(last_response).to be_ok
    expect(last_response.body).to eq 'Hello, Brian!'
  end
end

Capybara

https://github.com/jnicklas/capybara

Capybara - пример

describe 'the signin process' do
  before(:each) do
    User.create(email: 'user@example.com', password: 'password')
  end

  it 'signs me in' do
    visit '/sessions/new'

    within '#session' do
      fill_in 'Email', with: 'user@example.com'
      fill_in 'Password', with: 'password'
    end

    click_button 'Sign in'
    expect(page).to have_content 'Success'
  end
end

Въпроси дотук?

Имате ли въпроси по нещата досега?

Следват няколко примера с RSpec

RSpec

Тестване на методи

class User
  # Can be one of `:user`, `:admin`
  attr_accessor :rank

  def initialize(name, rank)
    @name = name
    @rank = rank
  end

  def admin?
    rank == :admin
  end
end

Тестване на методи

RSpec.describe User do
  describe '#admin?' do
    it 'is true for admins' do
      expect(User.new('John', :admin).admin?).to be true
    end

    it 'is false for non-admins' do
      expect(User.new('John', :user).admin?).to be false
    end
  end
end

context

RSpec.describe User do
  describe '#admin?' do
    context 'when the user is an admin' do
      it 'is true' do
        expect(User.new('John', :admin).admin?).to be true
      end
    end

    context 'with a regular user' do
      it 'is false' do
        expect(User.new('John', :user).admin?).to be false
      end
    end
  end
end

Друг пример

class Game
  def initialize(name, genre)
    @name  = name
    @genre = genre
  end

  def recommend
    case genre
    when :mmorpg then "Hey! Did you hear about #{name}? It's better than WoW!"
    when :fps    then "Yo! You must try this new shooter - #{name}!"
    else              "Have you tried #{name}? It's awesome!"
    end
  end
end

RSpec.describe Game do
  describe '#recommend' do
    context 'when the game is an MMORPG' do
      it 'compares it to WoW' do
        game = Game.new('Guild Wars 2', :mmorpg)
        expect(game.recommend).to eq 'Hey! Did you hear about Guild Wars 2? It\'s better than WoW!'
      end
    end

    ...

...

context 'when the game is an FPS' do
  it 'calls it a shooter' do
    game = Game.new('FarCry 4', :fps)
    expect(game.recommend).to eq 'Yo! You must try this new shooter - FarCry 4!'
  end
end

...

...

context 'when the game is of an unknown genre' do
  it 'says it\'s awesome' do
    game = Game.new('The Witcher 3', :rpg)
    expect(game.recommend).to eq 'Have you tried The Witcher 3? It\'s awesome!'
  end
end

Конвенции

Още

Често използвани обекти

let

let(:user) { User.new('Georgi', :admin)      }
let(:game) { Game.new('The Witcher 3', :rpg) }

it 'can like a game' do
  user.like(game)

  expect(game.likes).to eq 1
  expect(user.favourite_games).to match_array [game]
end

it 'can play a game' do
  user.play(game, 2.hours)

  expect(game.played_hours).to eq 2
  expect(user.gameplay_hours).to eq 2
end

RSpec - повече информация

Въпроси