Эта статья не будет о 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 .