CoffeeScript:一个TDD的例子
CoffeeScript是一种在JavaScript基础上建立抽象的语言(正如其名字所暗示的那样)。它是对JavaScript语法的抽象,而不是对其概念的抽象:该语言仍然基于作为对象的函数,可以与其他对象绑定,以及原型继承。
CoffeeScript更倾向于JavaScript的最佳实践,它将你本来就会写的抽象概念,或从框架中借用的抽象概念,转化为语言概念,以达到最大的简洁性。它有一个编译步骤--因为每一种语言都必须编译成较低级别的语言,如C或Java。
由于牛仔式编码不是我喜欢的工作方式,我通过使用jsTestDriver准备了一个测试驱动开发的例子。在这篇文章中,你可以得到两样东西:CoffeeScript的介绍,以及单元测试的工具(也就是如何用CoffeeScript进行TDD)。
构建在JavaScript之上
对于那些不遗余力地试图改善现有程序的基础设施的人来说,有一点是值得肯定的。许多人已经通过创建工具来尝试,这些工具是对JavaScript的赞美,而且在这种情况下,似乎效果很好。原因何在?因为JavaScript正需要一些改进。
人们想出的程序之一是 CoffeeScript。从字面上看,它是一点计算机代码,覆盖在现有的JavaScript代码上,使其阅读更加流畅。人们从中得到的好处是,他们可以开始以更有效的方式使用JavaScript,减少道路上的颠簸。就目前的情况来看,JavaScript有许多具有挑战性的问题,我们必须承认。然而,这一切都可以通过使用JavaScript的本意来修正,只是要努力完善它。
CoffeeScript当然不是改进JavaScript的全部程序,但它是朝着正确方向迈出的一大步。人们至少应该尝试一下CoffeeScript代码,看看它是否可能对他们提高从JavaScript收到的代码的质量有帮助。如果是这样,那就值得一试了。
基础设施
基本结构由两个文件夹组成:src/和lib/;记住编译步骤。我们将把.coffee文件放到src/中,然后在lib/中的对称树中把它们编译成.js的等价物。
我们还添加了一个jsTestDriver.conf文件来告诉单元测试框架要加载的所有文件,这些文件只是 "二进制".js脚本:
server: http://localhost:4224
load:
- lib/*.js
编译
这是我成功编写的第一个测试版本,fizzbuzztest.coffee。它是一个同义词,应该总是通过的:
mytest = () -> assertEquals(1, 1)
tests = {
"test1is1": mytest
}
TestCase("tests for fizzbuzz kata", tests)
你在这里看到,函数仍然是第一类对象,但只支持匿名函数。 CoffeeScript是一种没有分号的类似Python/Ruby的语言,与后者的语言有一些亲缘关系和共同背景。
我现在仍然使用旧的语法来调用函数,尽管在很多情况下可以省略括号。 CoffeeScript很保守,如果你想写分号,甚至可以接受分号。
我用coffee -o lib/ -c src/编译了这个脚本,结果是fizzbuzztest.js:
(function(){
var mytest, tests;
mytest = function() {
return assertEquals(1, 1);
};
tests = {
"test1is1": mytest
};
TestCase("tests for fizzbuzz kata", tests);
})();
全局命名空间默认不被触及,var关键字被自动引入以保留它。当我后来需要全局命名空间时,我写道:
this.fizzbuzz = /* ... function definition ... */
在这种情况下,这就是窗口对象或其他你执行代码的全局对象。
运行
为了运行测试,我们必须初始化 测试驱动(只有一次)。
jsTestDriver java -jar JsTestDriver-1.3.2.jar --port 4224
jsTestDriver现在将在localhost:4224处监听。在你的浏览器中加载这个URL,并通过点击链接来捕获它。当请求时,测试将在浏览器中执行:更多细节见相关文章。
每次你想运行测试时,从命令行执行它们:
java -jar JsTestDriver-1.3.2.jar --tests all
这就是我的卡塔的完整历史。这里是代码的最终版本(扰流警报!),支持添加3或5以外的其他因素。这段代码可能比一般的要难看,但它可以编译:
tests = {
"test ordinary numbers are unchanged": ->
assertEquals(1, fizzbuzz(1))
assertEquals(2, fizzbuzz(2))
assertEquals(4, fizzbuzz(4))
assertEquals(142, fizzbuzz(142))
"test multiples of 3 become fizz": ->
assertEquals("Fizz", fizzbuzz(3))
assertEquals("Fizz", fizzbuzz(6))
assertEquals("Fizz", fizzbuzz(9))
"test multiples of 5 become buzz": ->
assertEquals("Buzz", fizzbuzz(5))
assertEquals("Buzz", fizzbuzz(10))
"test multiples of 3 and 5 become fizzbuzz": ->
assertEquals("FizzBuzz", fizzbuzz(15))
assertEquals("FizzBuzz", fizzbuzz(45))
}
TestCase("tests for fizzbuzz kata", tests)
newRule = (word, divisor) ->
(number) ->
return word if number % divisor == 0
""
newFizzBuzz = (rules) ->
(number) ->
result = ""
concatenation = (rule) ->
result = result + rule(number)
concatenation rule for rule in rules
return result if result
number
fizzRule = newRule("Fizz", 3)
buzzRule = newRule("Buzz", 5)
this.fizzbuzz = newFizzBuzz([fizzRule, buzzRule])
对经验的评论
CoffeeScript提供了一个较短的语法,这带来了一些学习曲线,但不是很陡峭。我在1小时内完成了整个例子(不过我已经知道如何使用jsTestDriver。)
语法塑造了你写代码的方式,使一些事情变得更容易。我发现自己更经常使用创建其他函数的高阶函数,因为现在创建一个函数只需在几行代码前加上->即可。
变量的命名也更简单了,因为你只需要考虑名字,而不是var或污染范围的问题。更多的时间用于设计,而更少的时间用于语言问题。一些单行的指令如if表达式很方便,但不是必须的,它的存在是由于Ruby的启发。
还有,甚至更多,在CoffeeScript中可以发现,比如函数的绑定选项,这有助于不失去对这一点的参考。然而,问题是,所有这些便利是否比花时间学习一种新的语言和添加基础设施来使其工作更有价值--编译器、构建钩子和版本控制系统中忽略的并行树。