BDD тестирование в Swift с помощью Sleipnir

от автора


Objective-C разработчики могут пользоваться различными фреймворками для BDD тестирования своего кода.
Некоторые из них:

С появлением языка программирования Swift мы решили реализовать фреймворк для тестирования в стиле BDD на чистом Swift, без привязки к Objective-C.
После пары недель имлементации мы выпустили первую публичную версию фреймворка Sleipnir.
Sleipnir был вдохновлен фреймворком Cedar и позволяет писать BDD тесты в таком стиле:

class SampleSpec : SleipnirSpec {     var spec : () = describe("Horse") {         context("usual") {             it("is not awesome") {                 let usualHorse = UsualHorse()                 expect(usualHorse.legsCount).to(equal(4))                 expect(usualHorse.isAwesome()).to(beFalse())             }         }                  context("Sleipnir") {             it("is awesome") {                 let sleipnirHorse = Sleipnir()                 expect(sleipnirHorse.legsCount).to(equal(8))                 expect(sleipnirHorse.isAwesome()).to(beTrue())             }         }     } } 

Основные принципы Sleipnir

  • Sleipnir не зависит от NSObject, это BDD фреймворк на чистом Swift
  • Sleipnir не использует XCTest
  • Sleipnir выводит результаты тестов в командную строку в удобном виде и позволяет расширять или дополнять вывод результатов
  • Другие возможности, такие как рандомное исполнение тестов, фокусированные/исключаемые группы тестов

Мы также нашли некоторые альтернативные фреймворки для BDD тестирования на Swift, например Quick.
Выбор между ними — вопрос личных предпочтений разработчика.

Пример использования

Определим два класса — Book и Library и напишем для них тесты.
Класс Book содержит информацию об авторе и названии книги:

class Book {         var title: String     var author: String          init(title: String, author: String) {         self.title = title         self.author = author     }    } 

Класс Library — простая коллекция книг:

class Library {       var books: Book[]          init() {         self.books = Book[]()     }          func addBook(book: Book) {         books.append(book)     }          func removeLastBook() {         books.removeLast()     }          func clear() {         books.removeAll()     }          func size() -> Int {         return books.count     }          func hasBooks() -> Bool {         return size() > 0     }          func filterBy(#author: String) -> Book[] {         return books.filter { $0.author == author }     }          func filterBy(#title: String) -> Book[] {         return books.filter { !$0.title.rangeOfString(title).isEmpty }     } } 

Для начала протестируем корректность инициализации класса Book:

class LibrarySpec : SleipnirSpec {          var book : () = context("Book") {                  var swiftBook: Book?         beforeAll {             swiftBook = Book(title: "Introduction to Swift", author: "Apple Inc.")         }                  it("has title") {             expect(swiftBook!.title).to(equal("Introduction to Swift"))         }                 it("has author") {             expect(swiftBook!.author).to(equal("Apple Inc."))         }     } } 

Мы создали класс LibrarySpec, который наследуется от класса SleipnirSpec. Он содержит в себе основной context и определяет два exampla, которые проверяют свойства созданного объекта класса Book.

Объект класса Book создается в блоке beforeAll{ }.
Sleipnir поддерживает несколько блоков инициализации и деинициализации тестов: beforeAll, afterAll, beforeEach и afterEach.

Результат вызова всех examplов (describe или context) верхнего уровня в тесте должен быть присвоен переменной для корректного инстанциирования:

var book : () = context("Book") {  } 

Теперь протестируем поведение класса Library:

class LibrarySpec : SleipnirSpec {          ...      var library : () = context("Library") {                  var swiftLibrary: Library?         beforeAll {             swiftLibrary = Library()         }                  afterAll {             swiftLibrary = nil         }                  describe("empty") {             it("has no books") {                 expect(swiftLibrary!.hasBooks()).to(beFalse())             }         }                  describe("with books") {                          beforeEach {                 swiftLibrary!.addBook(Book(title: "Introduction to Swift", author: "Apple Inc."))                 swiftLibrary!.addBook(Book(title: "Using Swift with Cocoa", author: "Apple Inc."))                 swiftLibrary!.addBook(Book(title: "Swift tutorials", author: "John Doe"))                 swiftLibrary!.addBook(Book(title: "Programming iOS with Swift", author: "Vladimir Swiftin"))             }                          afterEach {                 swiftLibrary!.clear()             }                          it("is not empty") {                 expect(swiftLibrary!.hasBooks()).to(beTrue())             }                          it("has correct number of books") {                 expect(swiftLibrary!.size()).to(equal(4))                 swiftLibrary!.removeLastBook()                 expect(swiftLibrary!.size()).to(equal(3))             }                          describe("filters books") {                 it("by author") {                     expect(swiftLibrary!.filterBy(author: "Apple Inc.").count).to(equal(2))                 }                                  it("by title") {                     expect(swiftLibrary!.filterBy(title: "tutorials").count).to(equal(1))                 }             }         }     } } 

Запуск этих тестов выведет в командную строку следующую информацию:

Running With Random Seed: 657464010  .......   Finished in 0.0091 seconds  7 examples, 0 failures 

В случае упавшего теста выведется детальная информация об ошибке, включая файл и номер строки:

Running With Random Seed: 2027508247  ..F....  FAILURE Library with books has correct number of books: /Users/atermenji/Coding/objc/Sleipnir/Sample/LibrarySpec.swift:64 Expected 3 to equal [2]   Finished in 0.0043 seconds  7 examples, 1 failures 

Мы протестировали поведение класса Library используя простые expectaionы и matcherы.
На данный момент Sleipnir поддерживает только три типа matcherов: equal, beTrue и beFalse, однако вскоре будут добавлены новые.

Планы на будущее

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

  • Механизм распространения фреймворка
  • Поддержка pending examples
  • Реализация фокусируемых/исключаемых групп тестов
  • Шаблоны XCode
  • Поддержка shared examples
  • Поддержка синтаксиса should (some_value should equal(some_another_value))
  • Вики документация
  • Тестирование Sleipnirа с помощью Sleipnirа
  • Дополнительные matcherы, среди которых:
    • beNil
    • beGreaterThan, beLessThan, beInRangeOf
    • асинхронные matcherы (will, willNot, after)
    • matcherы для коллекций и строк (contains, haveCount, beginWith, endWith, и т.д.)

Оставляйте багрепорты и фидбек на гитхабе или в комментах и следите за обновлениями!

ссылка на оригинал статьи http://habrahabr.ru/post/228811/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *