На эти размышления меня натолкнуло знакомство с асинхронным BDD фрэймворком для nodejs vows. Однако топик совсем не про vows и не совсем про BDD.
Фруктовый пример взятый с сайта vows:
Здесь отлично видно, какие требования мы проверяем (или «поклялись выполнить» по терминологии vows), но чтобы разобраться, действительно ли написанный код проверяет указанное требование, нужно напрячь извилины.
Можно скрыть детали реализации за фасадом объекта-языка.
Теперь понять, совпадают ли проверяемые требования с заявленными, сможет даже ваша бабушка.
Красота.
Ничего не напрягает? Меня напрягает. Во-первых, написание кода для «клятвы» при наличии хорошего языка становится практически механическим, а никто не любит механическую работу.
Во-вторых, наткнувшись однажды на такую спецификацию
можно надолго впасть в ступор. Кто ошибается: спецификация или код, который её проверяет?
Определенно стоит угостить себя заслуженой печенькой за красивый язык, а перевод спецификации в код доверить бездушной машине.
Обещанная цепочка вызовов (тоже проще некуда).
А вот так будет выглядеть фруктовая спецификация.
Пока ещё непонятно, в чем выигрыш. Немножко магии в парсере, чтобы убрать дублирование строк, и — вуаля!
Исходный код ну очень простого dsl парсера лежит тут.
Фруктовый пример взятый с сайта vows:
vows.describe('The Good Things').addBatch({
'A strawberry': {
topic: new(Strawberry),
'is red': function (strawberry) {
assert.equal (strawberry.color, '#ff0000');
},
'and tasty': function (strawberry) {
assert.isTrue (strawberry.isTasty());
}
}
}).export(module); // Export the Suite
Здесь отлично видно, какие требования мы проверяем (или «поклялись выполнить» по терминологии vows), но чтобы разобраться, действительно ли написанный код проверяет указанное требование, нужно напрячь извилины.
Можно скрыть детали реализации за фасадом объекта-языка.
lang = {
strawberry: function(){
return new(Strawberry);
},
is: function(color){
var colors = {red: '#ff0000', yellow: '#fff333'};
if (color){
return function (fruit) {
assert.equal (fruit.color, colors[color]);
}
}
return {
tasty: function(){
return function (strawberry) {
assert.isTrue (strawberry.isTasty());
}
}
}
},
}
Теперь понять, совпадают ли проверяемые требования с заявленными, сможет даже ваша бабушка.
vows.describe('The Good Things').addBatch({
'A strawberry': {
topic: lang.strawberry(),
'is red': lang.is('red'),
'and tasty': lang.is().tasty()
}
}).export(module);
Красота.
Ничего не напрягает? Меня напрягает. Во-первых, написание кода для «клятвы» при наличии хорошего языка становится практически механическим, а никто не любит механическую работу.
Во-вторых, наткнувшись однажды на такую спецификацию
'A strawberry': {
topic: lang.strawberry(),
'is red': lang.is('orange')
}
можно надолго впасть в ступор. Кто ошибается: спецификация или код, который её проверяет?
Определенно стоит угостить себя заслуженой печенькой за красивый язык, а перевод спецификации в код доверить бездушной машине.
function translateString(str){
var chain = new Chain(lang); // создаем цепочку вызовов (код будет ниже)
split(str).each(function(word){ // разбиваем строку на слова, которые будем переводить
if (isArgument(word)){ // если слово - аргумент
chain.argument(JSON.parse(word)); // добавляем его в цепочку вызовов как аргумент
} else { // иначе
chain.next(); // перемещаем корень цепочки
chain.add(word); // добавляем слово в цепочку как вызов
}
});
chain.next(); // вызываем последний метод в цепочке
return chain.root;
}
Обещанная цепочка вызовов (тоже проще некуда).
function Chain(root){
this.root = root;
this.f = null;
this.args = [];
}
Chain.prototype = {
argument: function(arg){
this.args.push(arg)
},
current: function(){
return this.root[this.f];
},
next: function(){
if (this.f){
this.root = this.current().apply(this.current(), this.args);
}
this.f = null;
this.args = [];
},
add: function(f){
this.f = f;
}
}
А вот так будет выглядеть фруктовая спецификация.
vows.describe('The Good Things').addBatch({
'A strawberry': {
topic: dsl.translate('strawberry', lang),
'is "red"': dsl.translate('is "red"', lang), //все, что заключено в двойные кавычки, считаем аргументом
'and tasty': dsl.translate('is tasty', lang)
}
}).export(module);
Пока ещё непонятно, в чем выигрыш. Немножко магии в парсере, чтобы убрать дублирование строк, и — вуаля!
vows.describe('The Good Things').addBatch(dsl.translate({
'a strawberry': {
'is "red"': null,
'is tasty': null
}
}, lang, {ignore: ['a']})).export(module);
Исходный код ну очень простого dsl парсера лежит тут.