Статьи

ScaTDD: взгляд на три основных тестовых фреймворка в Scala

Тестирование традиционно являлось шлюзом для разработки Scala во многих магазинах Java, стремящихся развивать свой технологический стек с минимальными нарушениями или обязательствами. В этом посте мы надеемся охватить три основных среды тестирования в среде Scala ( Specs2 , Scalatest и Scalacheck ) на примере классического теста FizzBuzz , чтобы узнать, как их можно использовать для развлечения и получения прибыли. Итак, краткий план требования FizzBuzz — это последовательность чисел:

  • Любое число, кратное 3, должно возвращать строку «Fizz»
  • Любое число, кратное 5, должно возвращать строку «Buzz»
  • Любое число, делимое на 3 и 5, должно возвращать строку «FizzBuzz»
  • В противном случае номер должен быть возвращен в виде строки

Для сравнения я также включил для этого пример теста Java JUnit . Фактический код реализации (включенный здесь) для этого тривиален (частично из-за замысла), поскольку он просто показывает иллюстрацию используемых библиотек.

Для этого я использовал Eclipse v3.7.2 в Windows 7 с плагином Scala IDE v2.0.1.v-2_09 и Scala версии 2.9.0.1.

SPECS2

В двух словах

Ссылка на сайт

http://etorreborre.github.com/specs2/

Версия

v2.9.2_v1.10

Ohloh

http://www.ohloh.net/p/specs2

Активное сообщество

Да . Проект, начатый как Specs (v1) в середине 2007 года, а затем развивающийся до более общей структуры, как Specs2 в 2010 году. В проекте есть несколько комиттеров (хотя в первую очередь им руководит Эрик Торреборре) и вспомогательные ресурсы, такие как группа Google, блог и корпоративная поддержка проекта.

пример

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package org.scalabound.scatdd.specs2
 
import org.scalabound.scatdd.FizzBuzz
import org.junit.runner.RunWith
import org.specs2.runner.JUnitRunner
 
// package unitTest { |> Scala IDE will not pick the class up as runnable if I sub-package it like this 🙁
@RunWith(classOf[JUnitRunner])
class FizzBuzzJUnitSpec extends org.specs2.mutable.Specification {
     
  "Multiples of both three and five" should {
    "print 'FizzBuzz'" in { FizzBuzz.eval(15) must_== "FizzBuzz" }
  }
  "Multiples of three only" should {
    "print 'Fizz'" in { FizzBuzz.eval(12) must_== "Fizz" }
  }
  "Multiples of five only" should {
    "print 'Buzz'" in { FizzBuzz.eval(10) must_== "Buzz" }
  }
  "Non multiples of five or three" should {
    "print the number back" in { FizzBuzz.eval(11) must_== "11" }
  }
}
 
// package acceptanceTest { |> Scala IDE will not pick the class up as runnable if I sub-package it like this 🙁
@RunWith(classOf[JUnitRunner])
class FizzBuzzUATSpec extends org.specs2.Specification { def is =
  "This specification is to check the FizzBuzz evaluator"          ^
               p^
  "The FizzBuzz evaluator should"                                  ^
    "process a multiple of both three and five to return FizzBuzz" ! e1^
    "process a multiple of three only to return Fizz"              ! e2^
    "process a multiple of five only to return Buzz"               ! e3^
    "process a non multiple of three or five to return the input"  ! e4^
                                                                   end                                           
                                
  def e1 = FizzBuzz.eval(15) must_== "FizzBuzz"
  def e2 = FizzBuzz.eval(12) must_== "Fizz"
  def e3 = FizzBuzz.eval(10) must_== "Buzz"
  def e4 = FizzBuzz.eval(11) must_== "11"
}
 
@RunWith(classOf[JUnitRunner])
class FizzBuzzDataTableSpec extends org.specs2.Specification with org.specs2.matcher.DataTables { def is = 
  "Fizz Buzz testing with DataTables" ! e1  
 
  // note: when the first column of a DataTable is a String, '!!' needs to be used instead of '!'
  def e1 =
   "spec name"                       || "input val" | "expected output" |
   "Multiple of both three and five" !! 15          ! "FizzBuzz"        |
   "Multiple of three only"          !! 12          ! "Fizz"            |
   "Multiple of five only"           !! 10          ! "Buzz"            |
   "Non multiple of five or three"   !! 11          ! "11"              |> {
     (a, b, c) => FizzBuzz.eval(b) must_== c // (a, b, c) is a structural match on the table
   }
}

Обратная связь

Обычно болевые точки, с которыми я сталкивался, были в Scala IDE, а не в Specs2. В частности, я изначально хотел включить разные типы тестов в один и тот же исходный файл, но различать по разным подпакетам. К сожалению, Scala IDE не получил субпакеты, так что это был не стартер. Одна небольшая странность заключалась в невозможности изменить порядок моих аргументов для спецификации спецификации DataTable.

Обновление : благодаря Эрику Торреборре это было решено с помощью !! вместо ! когда тип String используется для первого столбца в DataTable !! Это было небольшое раздражение, хотя.

В противном случае, спецификации 2 выглядят зрелыми, с хорошей поддержкой инструментов и, скорее всего, я выберу «оболочку» для тестирования. Я нашел API по умолчанию для спецификаций понятным и простым в использовании, и мне понравилось разделение тестов на выражения, оцененные для их поддержки или опровержения. Ницца.

Сравнения

RSpec , JBehave , easyb , Instinct , JUnit
<
SCALATEST

В двух словах

  • Обобщенная структура для Java и Scala, в которой используются различные черты для объединения нескольких различных стилей и стратегий тестирования. Из коробки поддерживаются тесты TDD, BDD, функциональные, интеграционные TestNG и JUnit.

Ссылка на сайт

http://www.scalatest.org/

Версия v1.7.2

Ohloh

http://www.ohloh.net/p/scalatest

Активное сообщество

Да Это фреймворк второго поколения, созданный Биллом Веннерсом в 2001 году в рамках проекта SuiteRunner. Активно поддерживаются форумы, доступные через: http://www.scalatest.org/community.

пример

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.scalabound.scatdd.scalatest
 
import org.junit.runner.RunWith
 
import org.scalatest.FunSpec
import org.scalatest.junit.JUnitRunner
import org.scalabound.scatdd.FizzBuzz
 
@RunWith(classOf[JUnitRunner])
class FizzBuzzScalaTest extends FunSpec {
   
  describe('A FizzBuzz processor') {
     
    it('should return 'FizzBuzz' from a mulitple of three and five') { assert(FizzBuzz.eval(15) == 'FizzBuzz') }
     
    it('should return 'Fizz' from a multiple of three only') { assert(FizzBuzz.eval(12) == 'Fizz') }
     
    it('should return 'Buzz' from a multiple of five only') { assert(FizzBuzz.eval(10) == 'Buzz') }
     
    it('should return the stringified input from a non multiple of three or five') { assert(FizzBuzz.eval(11) == '11') }
     
  }
 
}

Обратная связь
Я использовал только один тип спецификации, и он казался одновременно ясным и лаконичным. Язык, продиктованный этим API ( опишите .. это .. ), был простым, но немного неловким на первом сайте. Я также предпочитаю более четкое различие между моими тестами и утверждениями (это вопрос вкуса). Тем не менее, было бы трудно найти более простую и «дружественную» среду, которая позволила бы команде Java начать работать с первого раза.

Сравнения

JUnit , TestNG

SCALACHECK

В двух словах

  • Основанная на спецификации структура генерации тестов для Scala и Java. Библиотека была первоначально вдохновлена QuickCheck в Haskell.

Ссылка на сайт

https://github.com/rickynils/scalacheck#readme

Версия

v2.9.0-1-1.9

Ohloh

http://www.ohloh.net/p/scalacheck

Активное сообщество

Да , но в основном это связано с Рикардом Нильссоном. Похоже, что нет поддержки форума для этого проекта.

Обновление: благодаря @Daniel Sobral здесь есть (фактически) группа Google в поддержку ScalaCheck!

пример

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.scalabound.scatdd.scalacheck
 
import org.scalabound.scatdd.FizzBuzz
import org.scalacheck.ConsoleReporter.testReport
import org.scalacheck.Prop.forAll
import org.scalacheck.Prop.propBoolean
import org.scalacheck.ConsoleReporter
import org.scalacheck.Test
 
object FizzBuzzScalaCheck {
   
  val propFizzBuzzCheck = forAll { n: Int =>{
     if(n % 15 == 0) FizzBuzz.eval(n) == 'FizzBuzz'
     else if(n % 3 ==0) FizzBuzz.eval(n) == 'Fizz'
     else if(n % 5 ==0) FizzBuzz.eval(n) == 'Buzz'
     else '' + n == FizzBuzz.eval(n)
   }
  }
   
   def main(args : Array[String] ) = {
    ConsoleReporter.testStatsEx('blah', testReport(Test.check(propFizzBuzzCheck)))
  }
   
}

Обратная связь По сравнению с другими фреймворками, мне потребовалось некоторое время, чтобы поработать с ним (имейте в виду, что я бегал с другими через несколько минут!). Основными источниками моей боли были:

  • Нет очевидной поддержки JUnit из коробки
  • Первоначально я запускал свои тесты как приложение Scala (то есть, что-то, что расширяло приложение ), хотя продолжало
    1
    GenException(java.lang.NullPointerException)from the ConsoleReporter whenever I tried to run my tests.
  • Всякий раз, когда я пытался добавить слишком много условий к своему оператору импликации, я обнаруживал, что генерировалось недостаточно тестов для проверки моего свойства (отсюда и довольно неуклюжий пример, который я привожу ).

Сказав все это, я все еще считаю эту структуру уникальной и действительно ценной. В нескольких тестовых прогонах я регистрировал сгенерированные входы, и Scalacheck действительно великолепен (достаточный диапазон входов, сгенерированных и пропущенных). Это была, вероятно, самая сложная структура из трех, но она также предлагала такие функции, которые я, безусловно, хотел бы использовать снова и снова. Вероятно, в следующий раз я запустил бы это через Specs!

Сравнения

Java QuickCheck , JUnit quickcheck

Вывод

В заключение, все три фреймворка намного понятнее (или выразительнее), чем сравнительные фреймворки, которые я лично использовал в Java ранее. Если бы я пытался заставить команду «перейти» в Scala, я бы выбрал ScalaTest. Если бы я работал с командой, которая чувствовала себя немного более комфортно со Scala, я бы пошел по маршруту Specs2. В любом случае я бы попытался использовать ScalaCheck для генерации тестов (хотя в идеале абстрагироваться от одной из других платформ). В любом случае я могу понять, почему использование тестовых сред Scala помогает сформировать мнение о командах, прогрессирующих в Scala на предприятии. Я надеюсь, что этот отзыв был полезным и не слишком сухим. Как всегда, лучшая «проверка» — это идти по пути самостоятельно (если только ScalaCheck не сделает это за вас!). Удачи с вашим TDD и счастливого взлома!

Ссылка: ScaTDD: взгляд на три основных тестовых среды в Scala от нашего партнера по JCG Кингсли Дэвиса в блоге Scalabound .