Справка
Телеграм чат начинающих программистов. Общаемся и помогаем друг другу
Если ссылка не открывается, можно найти нас в поиске по чатам @rubyrush
или
пойти другим путем
Как был бы прекрасен мир вообще без багов.
Сейчас мы научим вас секретам успеха волшебной технике снижения количество
ошибок в любой программе!
В идеальном мире вы бы написали программу один раз и все бы ей бы пользовались без проблем. В реальном так не бывает.
Зарубите на носу :) В любой настоящей программе всегда есть баги. К тому же, программы нужно постоянно улучшать, делать новые версии. Любое изменение программы создает почву для появления новых багов.
Но не волнуйтесь, не все так мрачно. Цель хорошего программиста: во-первых писать программы так, чтобы свести количество багов к минимуму, а во-вторых писать программы так, чтобы они работали несмотря на несущественные баги.
Поиск ошибок в уже написанных программа это целая наука — тестирование. В крупных фирмах сидят целые отделы тестировщиков которые только и занимаются, что пытаются сломать чьи-то программы.
Но то, что вы (или ваши пользователи) во время использования программы могут найти ошибки, не отменяет того факта, что лучше этих ошибок избежать ещё на стадии разработки. Добиться этого помогают специальные практики индивидуального применения. Эти же практики помогает реже краснеть перед тестировщиками :)
Вы можете проверять работу своих программ руками. Но раз вы уже умеете программировать, почему бы не написать отдельную программу, которая бы проверяла вашу программу. Логично?
Эта гениальная идея давно пришла в голову программистам и хороший программист всегда пишет еще и тесты. Это убивает сразу кучу зайцев.
Вот лишь некоторые из них:
Тесты — как закрытая трасса, где проходят испытания написанных вами программ, где моделируются экстремальные условия использования программы или её частей.
Подходов к тестированию очень много. Но самое главное в тестах не подход, а просто наличие тестов!
Любой тест подразумевает, что даны какие-то условия и программа (или её часть) должны в этих условия выдать конкретный (тоже заранее известный) результат.
Именно поэтому тесты помогают понять задачу: вы не можете написать тест, пока не поняли, что в каких условиях должно получиться.
Для написания тестов есть множество утилит. И в разных языках они могут быть очень разные. Давайте изучим самую популярную в руби — RSpec.
gem install rspec
По своей структуре тесты — это отдельные программки, проверяющие ключевые функции вашей программы (или несколько функций вплоть до всей программы целиком).
Они передают программе заданные параметры, а потом смотрят, соответствует ли её ответ заданному в тесте. Если соответствует — хорошо, а нет — тест считается упавшим. И это скорее всего означает, что в программе ошибка.
Нагляднее всего протестировать нашу функцию склонения слов из 9 урока базового блока ([lesson_id=9]). Мы там написали метод, который должен выводить нужную форму слова в зависимости от того, какое число ему передали.
1 → крокодил
3 → крокодила
6 → крокодилов
Давайте напишем тесты для метода sklonenie
:
Создайте в папке урока папку sklonjator
и создайте в ней файл sklonjator.rb
, наш метод мы для удобства завернем в класс Sklonjator
:
# encoding: utf-8
# метод для склонения русских слов в соответствии с числительным, преобразовано к классу
class Sklonjator
# Статический метод будет возвращать правильно склонение слова,
# когда нужно его использовать с числом
# Например во фразах, типа "1 крокодил, 23 крокодила, 7 крокодилов"
def self.sklonenie(number, krokodil, krokodila, krokodilov)
# проверим входные данные на правильность
if (number == nil || !number.is_a?(Numeric))
number = 0 # если первый параметр пустой или не число, то продолжаем как будто он нулевой
end
ostatok = number % 10 # склонение определяется последней цифрой в числе
if (ostatok == 1) # для 1 - именительный падеж (Кто? Что?)
return krokodil
end
if (ostatok >= 2 && ostatok <= 4) # для 2-4 - родительный падеж (Кого? Чего?)
return krokodila
end
# 5-9 или ноль – родительный падеж и множественное число
if (ostatok >= 5 && ostatok <= 9 || ostatok == 0)
return krokodilov
end
end
end
И давайте теперь его протестируем, создайте в папке sklonjator
файл sklonjator_spec.rb
.
Этот файл называется «тестовый сценарий», он состоит из отдельных тестов. Обычно один тестовый сценарий посвящен тестированию одного отдельного класса. Тесты внутри сценария друг от друга в принципе не зависят и тестируют различные методы, различные сценарии взаимодействия.
Все тесты пишутся на языке Ruby, но с дополнительными конструкциям, которые добавляет гем RSpec:
# подключаем сам rspec
require 'rspec'
# подключаем склонятор
require_relative 'sklonjator.rb'
# так в RSpec начинается сценарий для конкретного класса/модуля/метода
describe Sklonjator do
# внутри идет набор кейсов внутри it '...' do ... end
# каждый такой кейс выполняется rspec-ом при запуске всего сценария в случайном порядке
it 'should do ok for KROKODILOV' do
# ключевое слово-метод expect(...).to ...
# ожидаем-что( нечто ).to - будет чем-то, например "eq" значит равно
# обо всех возможностях RSpec см. документацию и материалы к уроку
expect(Sklonjator.sklonenie(0, 'krokodil', 'krokodila', 'krokodilov')).to eq 'krokodilov'
expect(Sklonjator.sklonenie(5, 'krokodil', 'krokodila', 'krokodilov')).to eq 'krokodilov'
expect(Sklonjator.sklonenie(6, 'krokodil', 'krokodila', 'krokodilov')).to eq 'krokodilov'
end
# простые случаи для КРОКОДИЛ
it 'should do ok for KROKODIL ' do
[1, 21, 31].each do |i|
expect("#{i} #{Sklonjator.sklonenie(i, 'krokodil', 'krokodila', 'krokodilov')}").to eq "#{i} krokodil"
end
end
# простые случаи для КРОКОДИЛА
it 'should do ok for KROKODILA ' do
[2, 3, 4, 22, 33].each do |i|
expect("#{i} #{Sklonjator.sklonenie(i, 'krokodil', 'krokodila', 'krokodilov')}").to eq "#{i} krokodila"
end
end
end
Для того, чтобы запустить наш тест, надо перейти в консоль и запустить rspec, передав ему тест в качестве параметра:
cd c:\rubytut2\lesson17\sklonjator
rspec sklonjator_spec.rb
Или можно запустить тест прямо в RubyMine.
Все наши тесты прошли. Давайте теперь напишем какой-нибудь тест на тот случай, который сейчас в нашей программе не учтён.
# ОСОБЫЕ случаи
# этот тест должен упасть, чтобы он заработал — надо починить склонятор (см. исходник склонятора)
it 'should do ok for KROKODILOV - SPECIAL' do
[10, 11, 12, 13, 14, 111, 312, 1013, 2414].each do |i|
expect("#{i} #{Sklonjator.sklonenie(i, 'krokodil', 'krokodila', 'krokodilov')}").to eq "#{i} krokodilov"
end
end
Снова запустим тесты:
Ну вот, теперь другое дело! Скорее чинить склонятор!
ostatok100 = number % 100
if (ostatok100 >= 11 && ostatok100 <= 14)
return krokodilov
end
Теперь все тесты должны пройти.
Для того, чтобы продемонстрировать пользу тестов, давайте сделаем улучшение нашей программы.
Раньше мы передавали массив с четырьмя параметрами и ждали только нужную форму слова. А теперь давайте допишем метод так, чтобы он принимал пятый необязательный параметр with_number
, который указывает, хотим ли мы увидеть в возвращаемой строке наше число:
Sklonjator.sklonenie(5, 'krokodil', 'krokodila', 'krokodilov', false)
# → krokodilov
Sklonjator.sklonenie(5, 'krokodil', 'krokodila', 'krokodilov', true)
# → 5 krokodilov
Сперва напишем соответствующие тесты:
# вывод числа вместе с формой слова
it 'should print number if with_numbers = true' do
expect(Sklonjator.sklonenie(5, 'krokodil', 'krokodila', 'krokodilov', true)).to eq "5 krokodilov"
end
it 'should not print number if with_numbers = false' do
expect(Sklonjator.sklonenie(5, 'krokodil', 'krokodila', 'krokodilov', false)).to eq "krokodilov"
end
После того, как мы написали тесты, можно писать наши улучшения в программе:
# encoding: utf-8
# метод для склонения русских слов в соответствии с числительным, преобразовано к классу
# ОТРЕФАКТОРЕННАЯ версия — с другим методом, с дополнительной опцией вывода строки вместе с числом
class Sklonjator
def self.sklonenie(number, krokodil, krokodila, krokodilov, with_number)
# проверим входные данные на правильность
if (number == nil || !number.is_a?(Numeric))
number = 0 # если первый параметр пустой или не число, то продолжаем как будто он нулевой
end
# определяем выводить ли число перед крокодилами, в зависимости от опции
prefix = ""
prefix = "#{number.to_s} " if with_number
ostatok = number % 10 # склонение определяется последней цифрой в числе
ostatok100 = number % 100
if (ostatok100 >= 11 && ostatok100 <= 14)
return "#{prefix}#{krokodilov}"
end
if (ostatok == 1) # для 1 - именительный падеж (Кто? Что?)
return "#{prefix}#{krokodil}"
end
if (ostatok >= 2 && ostatok <= 4) # для 2-4 - родительный падеж (Кого? Чего?)
return "#{prefix}#{krokodila}"
end
# 5-9 или ноль – родительный падеж и множественное число
if (ostatok >= 5 && ostatok <= 9 || ostatok == 0)
return "#{prefix}#{krokodilov}"
end
end
end
Если запустить тесты сейчас, то мы увидим, что те тесты, где мы указали последний параметр (true
или false
), прошли, а вот все предыдущие упали. Потому что в них мы вызываем метод sklonenie
с недостаточным количеством параметров.
Тесты сыграли важную роль! Они помогли нам понять, что новый метод нарушил «обратную совместимость». Новая реализация не позволяет использовать метод по-старому. Без тестов эта ошибка могла бы остаться незамеченной и потом обнаружить себя неприятным багом.
Давайте это исправим, сделав параметр with_numbers
необязательным, как мы и хотели. Для этого нужно просто в скобках, где при объявлении метода мы указывали параметры, указать значение по умолчанию. Это такая фишка руби, которая все равно передает это значение аргумента, если метод вызван без параметра.
Чтобы программа в прошлых тестах работала также, значение по умолчанию должно быть равно false
.
def self.sklonenie(number, krokodil, krokodila, krokodilov, with_number = false)
Теперь все тесты должны пройти.
У тестирования есть и обратная сторона, из-за непонимания которой, многие разработчики от него отказываются.
Если вы пишете тесты, вы пишете дополнительный код. Который непосредственно не решает ваши задачи, а лишь помогает качественно написать тот, который решает.
В какой-то момент вам обязательно покажется, что тестирование — не нужно, ведь вы и так уже крутой программист, а с тестами иногда приходится возиться дольше, чем с самим кодом.
Это признак того, что вы во-первых зазнались :), а во-вторых — скорее всего пишите тесты не оптимально.
Тесты — это хорошо, а всё хорошее хорошо в меру. Чувству меры нельзя научиться за один день, это будет приходить постепенно с опытом.
А пока несколько советов, когда стоит писать тесты, а когда не обязательно.
Избегайте разрастающихся тестовых файлов, ваши тесты — это тоже ваш код, делайте его компактным, аккуратным, используйте ассоциативные массивы и циклы, как мы вам показывали. Комментируйте свои тесты.
Понимание как писать тесты придёт с опытом — пока начните тестировать хоть что-нибудь, хоть как-нибудь. Потому что на тесты (как на что-то неважное) забивают частенько даже опытные программисты. Не будьте из их числа.