Четвърта задача - Unit тестване

Предадени решения

Краен срок:
18.11.2016 23:59
Точки:
6

Срокът за предаване на решения е отминал

Unit тестване

В практиката постоянно пишем автоматизирани тестове за кода, който пишем.

Досега винаги сме ви карали да пишете само решенията на задачите. Този път ще си разменим ролите. Ще ви дадем решението на една задача - код, който е написан от нас и работи. Все пак, и на нас ни се решават задачи :)

Вашата задача е да напишете unit тестове за нашето решение, използвайки RSpec.

Ето какво трябва да направите:

  1. Прочетете условието на задачата няколко пъти - използвайте го като спецификация.
  2. Прочетете нашето решение. То се намира в хранилището с домашните - файлът version.rb.
  3. Напишете тестове в solution.rb, които минават успешно за нашето решение и сигнализират за възможно най-много евентуални проблеми. Документацията на RSpec е ваш най-добър приятел.

Целият код в решението ви (solution.rb) трябва да е в блок, подаден на RSpec.describe:

RSpec.describe 'Version' do
  # Тук пишете код - describe, context, it и т.н.
end

Не трябва да имате зареждания на библиотеки и код извън RSpec.describe.

Как ще проверяваме тестовете ви?

Ще напишем няколко грешни решения. Ще пускаме тестовете ви върху грешните решения и ще очакваме да fail-ват за тях. Тези наши счупени решения ще можете да видите чак след крайния срок на задачата. Тестваме тестовете с решения. Колко яко!

Постарайте се да тествате възможно най-голяма част от функционалността. Ако има нещо, което не сте тествали - тестовете ви няма да фейлнат за определеното счупено решение и ще получите по-малко точки.

Ще пускаме тестовете ви и с други правилни решения - не обвързвайте имплементацията с тестовете. Тоест:

  • Вярното решение не е само едно.
  • Не тествайте имплементационни детайли (например, private методи).
  • Тествайте само публичния интерфейс - тествайте какво прави нещото, не как точно е написано.
  • Пишете тестовете така, че да се счупят ако решението не работи по правилен начин.

ВАЖНО: Тестовете ви задължително трябва да минават успешно за решението, което сме ви дали (файлът version.rb). Ако тестовете ви НЕ минат успешно за това правилно решение, няма да получите нито една точка. Все пак, имате и тестовете, и решението.

Задачата - клас Version

Знаете, че програмите обикновено имат версии - числа, разделени с точки, обозначаващи "вариантът" на програмата.

Версия в тази задача ще наричаме произволен брой (положителни цели) числа, разделени с точка. Версиите 1.1.5 и 1.13.3 са валидни. .3 и 0..3 - не.

Това, което ни трябва е клас Version, който се инстанцира със стринг или друга инстанция от същия клас - версия.

Този аргумент трябва да е валидна версия или празен стринг. Ако подадем празен стринг (или не подадем параметъра изобщо) - приемаме версията за 0. Ако подадем невалидна версия - ще се хвърли ArgumentError и съобщението ще е във формата Invalid version string '<версията>' (очевидно, <версията> ще бъде заменено от реално подадения стринг).

Сравнение на версии

Основната задача на този клас е да сравнява версии. За целта, той имплементира >, <, <=, >=, == и <=>.

Сравнението на версии работи по следния алгоритъм:

  • Нулевите компоненти в края на версията нямат значение - 1.1.0 и 1.1 са една и съща версия. 0.1 и 1 са различни версии.
  • Сравнението става от ляво надясно - компонент по компонент - докато не попаднем на двойка компоненти, които се различават. Стигнем ли до такава двойка, сравняваме двете числа. Което число е по-малко, значи тази версия е по-ниска. Например, Version.new('1.2.3') < Version.new('1.3.1') #=> true.
  • Сравнение може да се извършва и върху версии с различен брой компоненти. Несъществуващите компоненти в по-късата версия ги считаме за нули. Пример: Version.new('1.2.3') < Version.new('1.3') #=> true.

Преобразуване на версия обратно в стринг

Класът Version има метод #to_s, който връща версията като стринг. Този стринг е каноничната репрезентация на версията - без нулеви компоненти в края. Version.new('1.1.0').to_s #=> '1.1'.

Компоненти

Друг метод на Version е #components. Той връща масив от компонентите на версията. Например:

Version.new('1.3.5').components #=> [1, 3, 5]

Отново, нулите отдясно трябва да не присъстват в масива.

Методът приема опционален аргумент - N - броят на компонентите, които трябва да бъдат върнати. Ако реалните са повече - се вземат първите N, останалите трябва да се игнорират. Ако са по-малко - трябва да бъдат допълнени с нули.

Този метод не трябва да позволява модифициране на вътрешното състояние на версията.

Range от версии

Освен Version, има и клас за област от версии - Version::Range. За създаването на обект от този клас са необходими два параметъра - начална и крайна версия. Тези версии могат да се подадат като инстанции на Version или като стрингове (в този случай стрингът трябва да се държи като това, което подаваме на Version.new).

Version::Range има два метода - #include? и #to_a.

Range#include?

include? проверява дали подадената му версия е в областта от версии. Тоест, дали е едновременно по-голяма или равна на началната версия и по-малка от крайната.

Ето пример:

Version::Range.new(Version.new('1'), Version.new('2')).include? Version.new('1.5') #=> true

И този метод трябва да може да приема и стринг вместо версия.

Range#to_a

to_a генерира всички версии между началото и края на областта. Началната версия е включена в резултата, а крайната - не. Ето пример:

Version::Range.new(Version.new('1.1.0'), Version.new('1.2.2')).to_a
#=> ['1.1', '1.1.1', '1.1.2', '1.1.3', '1.1.4', '1.1.5', '1.1.6', '1.1.7', '1.1.8', '1.1.9',
#    '1.2', '1.2.1']

Всеки компонент последователно се увеличава с 1 (от дясно наляво) докато не стигне 9. Тогава става 0 и този отляво се увеличава с 1. Този лимит от 9 се използва само тук. Останалите методи нямат подобни ограничения.

Този метод работи само с версии до 3 компонента (включително). Винаги се започва от третия компонент, дори да го няма в подадените версии:

Version::Range.new(Version.new('1.1'), Version.new('1.2')).to_a
#=> ['1.1', '1.1.1', '1.1.2', '1.1.3', '1.1.4', '1.1.5', '1.1.6', '1.1.7', '1.1.8', '1.1.9']

Когато използваме #to_a, на Version::Range няма да подаваме версия с компонент по-голям от 9 (например 1.10.1). Не тествайте с подобни версии. Това важи само за to_a - за include? са напълно валидни.

Други бележки

  • Разделяйте тестовете - не тествайте много функционалност в един it блок. В идеалния случай, ако се счупи един аспект на функционалността - трябва да се счупи точно един тест.
  • Слагайте описателни текстове за всеки it. Консултирайте се с лекцията за тестване за конвенциите.
  • Използвайте describe и context по предназначение.
  • RSpec позволява използването на два синтаксиса - should и expect. Използвайте expect.

За груби нарушения на горните ще отнемаме точки.

Други "други бележки"

  • Ако забележите противоречие между условието на задачата и нашето решение - пишете ни във форума или по имейл възможно най-скоро.
  • Ако има детайл, по който работи решението ни, но този детайл не е описан в условието - значи не е част от спецификацията. Щом не е част от спецификацията - не е задължително да работи по точно този начин. Следователно, може да има вярно решение, което да не работи така и вероятно ще тестваме с него.
  • За по-запознатите - сайтът ни използва RSpec версия 2.99. Старичка е, та ако ще си пишете custom matcher-и, внимавайте за коя версия гледате документацията. Тестовете спокойно могат да се напишат без подобни изпълнения.
  • Ще считаме споделянето на тестове и на алтернативни решения за преписване. За преписването
  • Не забравяйте да си пуснете примерните... ... Няма примерни тестове, защото ви даваме решението. Оставяме на вас да се ориентирате как да си пуснете локално вашите тестове върху нашето решение.

Ограничения

Тази задача има следните ограничения:

  • Най-много 80 символа на ред

Ако искате да проверите дали задачата ви спазва ограниченията, следвайте инструкциите в описанието на хранилището за домашните.

Няма да приемаме решения, които не спазват ограниченията. Изпълнявайте rubocop редовно, докато пишете кода. Ако смятате, че rubocop греши по някакъв начин, пишете ни на fmi@ruby.bg, заедно с прикачен код или линк към такъв като private gist. Ако пуснете кода си публично (например във форумите), ще смятаме това за преписване.