Архив метки: Модульное тестирование

Главная / Модульное тестирование
3 Поста

В этом посте мы продолжим разбирать RSpec и поговорим о matchers.

«Matchers» не переводится с английского, но слово match в этом контексте означает «совпадать, равняться».

Итак.

RSpec «вешает» методы to и not_to на все результаты ожиданий.

Эти два метода принимают один параметр — matcher.

Примеры matcher’ов:

  • be_true / be_false
  • eq 3
  • raise_error(SomeError)

Be_predicate — логическое значение

Если объект, на котором проводится тест, имеет утвердительный (логический) метод, вы автоматически получите матчер be_predicate.

Так что, к примеру, be_nil является валидным матчером, так как каждый объект в Ruby имеет метод :nil?.

В нашем примере с калькулятором мы можем иметь такой тест:


it "should sum two odd numbers and become even" do
 expect(@calculator.add(3, 3)).to be_even
 expect(@calculator.add(3, 3)).not_to be_odd
end

Это полностью валидно и выглядит почти как естественный английский.

Так как мы исправили ошибки в калькуляторе, все тесты работают.

Но если вы хотите узнать подробнее, что работает, а что нет, введите rspec --format documentation или rspec -f d:

Больше матчеров

Полный список матчеров можно найти в документации RSpec.

https://relishapp.com/rspec/rspec-expectations/v/3-6/docs/built-in-matchers

В итоге

RSpec имеет множество встроенных матчеров, которые доступны для упрощения написания тестов, я думаю это отличный фреймворк для тестирования приложений.

Хотя если вы предпочитаете MiniTest, используйте его.

В этом посте мы продолжим говорить о юнит-тестировании в Ruby и рассмотрим RSpec.

Тестирование с помощью RSpec

Test::Unit делает свою работу, но было бы неплохо, если бы тесты были более описательными, более похожими на естественный английский.

RSpec позволяет это.

Так что, здесь написание тестов более интуитивно, как и вывод после выполнения тестов.

Установка RSpec

Устанавливаем RSpec командой gem install rspec.

describe()

Метод describe() это набор связанных тестов.

Он принимает строку либо класс в качестве аргумента.

Все тесты должны быть внутри этого блока.

Здесь нет подкласса для класcа (в отличие от Test::Unit), всё происходит внутри метода describe().

Методы before() и after()

Методы before() и after() похожи на setup() и teardown() в MiniTest.

Они принимают значения :each или :all, чтобы указать будет блок выполняться перед/после каждого теста или один раз перед/после всех тестов.

К примеру, before :all был бы полезен, если вы хотите подключиться к базе данных лишь единожды.

Метод it()

Используется для определения фактических спецификаций/примеров RSpec.

Принимает опциональную строку, описывающую тестируемое поведение.

Пример

Давайте возьмем для примера тот же код калькулятора, который был в предыдущей статье, но в этот раз протестируем его с помощью RSpec.


class Calculator

  attr_reader :name

  def initialize(name)
  	@name = name
  end

  def add(one, two)
  	one - two # скажем, вы допустили ошибку здесь (- вместо +)
  end

  def subtract(one, two)
  	one + two # и в этом методе
  end

  def divide(one, two)
  	one / two
  end
end

Выполним в терминале команду rspec --init в нужной папке, чтобы всё было подготовлено.

spec/calculator_spec.rb:


require 'rspec'
require_relative '../calculator' # или calculator.rb, без разницы

describe Calculator do # здесь вместо класса Calculator можно быть бы написать строку, но здесь это не имеет смысла
  before { @calculator = Calculator.new('RSpec calculator')}

# Фактические спецификации:

  it "should add 2 numbers correctly" do
  	expect(@calculator.add(2, 2)).to eq 4 
  end
  	
  it "should subtract 2 numbers correctly" do
   expect(@calculator.subtract(4, 2)).to eq 2 
  end  

  it "should sum two odd numbers and become even" do
   expect(@calculator.add(3, 3)).to be_even 
   expect(@calculator.add(3, 3)).not_to be_odd 
  end

end

Как мы видим, RSpec более интуитивный и больше похож на естественный английский.

Выполняем команду rspec.

После устранения ошибок результат выполнения теста будет выглядеть так:

В итоге, если вам нравится MiniTest, можете использовать его. Если вам нравится выразительность RSpec, попробуйте это.

Много раз вам придется писать тесты. Некоторые делают это после написания основного кода, некоторые после. Но в целом это довольно важная часть.

В этом посте мы рассмотрим юнит-тестирование (модульное тестирование).

Идея юнит-тестирования заключается в написании тестового кода, который проверяет ваш живой код.

Так почему люди пишут юнит-тесты?

Как вы узнаете, работает ли ваш код? Вы фактически понятия не имеете, пока не запустите его и не получите ожидаемые результаты.

Это особенно верно для Ruby, который является динамическим языком. В языке статического типа, таком как Java, если вы делаете опечатку, ваш редактор, вероятно, скажет вам об этом.

Еще одна причина для написания модульных тестов — когда вы хотите отрефакторить ваш код и быть уверенным, что вы ничего не сломали.

Для этого нужно добавить юнит-тесты. Когда у вас есть юнит-тесты, которые выполняются успешно, вы можете знать, что все работает как надо.

Другая причина для написания тестов заключается в том, что они могут послужить документацией для других разработчиков.

Test::Unit

Ruby очень серьезно относится к тестированию и предоставляет для этого фреймворк Test::Unit.

В Ruby 1.8 Unit::Test был загружен лишними библиотеками, которые включали много ненужного кода.

Ruby 1.9 уменьшил Test::Unit до необходимого минимума.

Новый фреймворк был официально назван MiniTest, хотя на него можно по-прежнему ссылаться под старым названием.

Обзор Test::Unit

  • Test::Unit входит в семью XUnit (наряду с JUnit, CppUnit)
  • Основная идея — расширение Test::Unit::TestCase
  • Перед именем метода приставляется test_
  • Если один из методов не удается, другие продолжают проверяться (это хорошая вещь)
  • Можно использовать методы setup() и teardown() для установки поведения, которое будет выполняться перед каждым тест-методом

Давайте посмотрим на реальный пример. Скажем, вы пишите калькулятор.


class Calculator

  attr_reader :name

  def initialize(name)
  	@name = name
  end

  def add(one, two)
  	one - two # скажем, вы допустили ошибку здесь (- вместо +)
  end

  def subtract(one, two)
  	one + two # и в этом методе
  end

  def divide(one, two)
  	one / two
  end
end

Тестируем:


require 'test/unit'
require_relative 'calculator.rb'

class CalculatorTest < Test::Unit::TestCase

  def setup
  	@calc = Calculator.new('test')
  end

  def test_addition
    assert_equal 4, @calc.add(2,2)
  end

  def test_subtraction
    assert_equal 2, @calc.subtract(4,2)
  end

  def test_division
    assert_equal 2, @calc.divide(4,2)
  end

end

Когда вы выполните тест, вы увидите череду ошибок:

Если исправить наши ошибки, тест будет полностью доволен.

Также можно попробовать протестировать деление на ноль, в этом случае мы ожидаем, что наша программа должна выдать исключение.


require 'test/unit'
require_relative 'calculator.rb'

class CalculatorTest < Test::Unit::TestCase
  def setup
  	@calc = Calculator.new('test')
  end

  def test_divide_by_zero
    assert_raise ZeroDivisionError do 
      @calc.divide(1, 0)
    end
  end
end

С этим наш калькулятор должен справиться, так что этот тест покажет успех.