Эта статья не будет о FitNesse . На самом деле мне не очень нравится этот инструмент, и он, похоже, теряет импульс, судя по трафику в официальном списке рассылки . Вместо этого мы будем реализовывать простой внутренний DSL поверх Scala для упрощения тестирования кода, вдохновленного DoFixture
. DoFixture
в FitNesse позволяет писать очень читаемые приемные тесты почти на простом английском языке, используя вики-страницы:
1
2
3
4
5
6
|
!|CarRegistrationFixtureTest| ! 1 Registering car ! 2 Registering brand new car for the first time | register | brand new car | by | any owner | in | any country | |
Что может быть неочевидным, так это то, что последняя строка на самом деле является исполняемой и вызывает старый добрый метод Java (или Scala в этом отношении):
1
2
3
4
5
6
7
8
9
|
class CarRegistrationFixtureTest extends DoFixture { val carService = new CarService def registerByIn(car: Car, owner: Owner, where: Country) = { //... } } |
Обратите внимание, как странно названный метод registerByIn
отображает синтаксис вики « зарегистрировать новый автомобиль любым владельцем в любой стране ». Сегодня мы узнаем о том, как написать очень простой, настраиваемый Scala DSL, который еще более читабелен и не требует нового инструмента и инфраструктуры тестирования.
Тот же тест, написанный в ScalaTest, будет выглядеть примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
class CarRegistrationSpec extends FeatureSpec with GivenWhenThen { val carService = new CarService() feature( "Registering car" ) { scenario( "Registering brand new car for the first time" ) { Given( "Owner and brand new car" ) //... When( "Car registered" ) carService.registerCar(brandNewCar, anyOwner, anyCountry) //... } } } |
Ничего особенного, обычный вызов метода registerCar()
. Остальная часть теста (а также объявление brandNewCar
, anyOwner
и anyCountry
) не имеют отношения к нашему обсуждению. Мы можем сделать его немного более читаемым, явно указав параметры:
1
|
carService.registerCar(car = brandNewCar, owner = anyOwner, where = anyCountry) |
Однако не ясно, является ли это на самом деле более читабельным, например, для непрограммистов. Но так как мы уже используем описательный FeatureSpec
, можем ли мы сделать код Scala более удобным для глаз? Конечно! Наши самые большие друзья — это инфиксная нотация и свободный API-интерфейс :
1
2
3
4
5
6
|
def register(car: Car) = new { def by(owner: Owner) = new { def in(country: Country) = carService.registerCar(car, owner, country) } } |
Выглядит странно, но поместите этот код в свой тест и наслаждайтесь гораздо более беглым вызовом:
1
|
register(brandNewCar).by(anyOwner).in(anyCountry) |
Это насколько Java может пойти, но Scala имеет синтаксис вызова инфиксных методов, который эквивалентен и выглядит красиво:
1
|
register(brandNewCar) by anyOwner in anyCountry |
Почему мы не можем пропустить первые скобки? Это ограничение на то, где можно использовать инфиксную нотацию (только вызов объекта с одним аргументом). К счастью, мы можем легко провести рефакторинг нашего внутреннего тестирования DSL, поставив существительное ( владельца ) на первое место и используя неявное преобразование:
1
2
3
4
5
6
|
implicit def fluentCarRegister(owner: Owner) = new { def registers(car: Car) = new { def in(country: Country) = carService.registerCar(car, owner, country) } } |
… который можно использовать следующим образом:
1
|
anyOwner registers brandNewCar in anyCountry |
Если вы заблудились, строка кода выше по-прежнему Scala и все еще исполняемая. Если вы не совсем поняли, что происходит, вот синтаксис desugared :
1
|
fluentCarRegister(anyOwner).registers(brandNewCar).in(anyCountry) |
Тесты все о читаемости и ремонтопригодности. Многие люди боятся DSL, потому что зачастую их сложно реализовать и отладить. Как я показал в этой короткой статье, написание действительно простого, но впечатляющего тестового DSL в Scala является простым и полезным. Более того, здесь нет рефлексии или магического метапрограммирования.
Ссылка: FitNesse свой ScalaTest с пользовательским Scala DSL от нашего партнера JCG Томаша Нуркевича в блоге NoBlogDefFound .