13. Тестване

13. Тестване

13. Тестване

23 ноември 2015

Днес

Шеста задача

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

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

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

Мотивация

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

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

Продукт

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

Legacy код

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

Какво е legacy код?

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

Лош опит

Аз съм допринесъл много за света с код тип "хвърчащ монтаж".

Достигнах до някои изводи по трудния начин.

Завършен продукт = функционалност + тестове

Едва напоследък започнах да виждам софтуера като завършен продукт, с кутия и пазарен вид, когато идва с пълен пакет автоматизирани тестове.

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

To test or not to test?

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

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

In testing I 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

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

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

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

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

To mock or not to mock

class User
  def like(game)
    game.likes += 1
  end
end

Без mock-ване

it 'increases the like counter of the game' do
  game = Game.new('The Witcher 3', :rpg)
  user = User.new('Georgi', :admin)

  user.like(game)

  expect(game.likes).to eq 1
end

С mock-ване

it 'increases the like counter of the game' do
  game = double
  user = User.new('Georgi', :admin)

  expect(game).to receive(:likes=).with(1)

  user.like(game)
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

Stub-ване

class Game
  def popular?() likes >= 100 end
end

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

describe '#popular?' do
  it 'returns true if the game is liked by at least 100 users' do
    allow(game).to receive(:likes).and_return(123)

    expect(game.popular?).to be true
  end
end

Друг пример

class NukeSilo
  def launch_nukes_at(city)
    # [CLASSIFIED]

    "Nukes sent at #{city}"
  end
end

class User
  def launch_nukes(silo)
    raise "No permissions" unless rank == :superadmin

    silo.launch_nukes_at '[CLASSIFIED]'
  end
end

Друг пример

context 'when the user is a superadmin' do
  let(:silo) { NukeSilo.new }

  it 'launches the nukes and reports what happened' do
    user = User.new('Obama', :superadmin)

    expect(silo).to receive(:launch_nukes_at).and_return('World peace achieved')

    expect(user.launch_nukes(silo)).to eq 'World peace achieved'
  end
end

Before

describe '#launch_nukes' do
  before do
    @silo = NukeSilo.new

    allow(@silo).to receive(:launch_nukes_at).and_return('World peace achieved')
  end

  # ...
end

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

Въпроси