测试在Rails中测试很轻松,与Django比较起来更需要着重强调。 Fixture两个框架以相似的方式都支持fixture(示例数据)。我却给Rails更高的评价,因为它更实用,能从文件的名称得知你在使用哪个模板。Rails使用YAML格式的fixture,这是人类可读的数据序列化格式。 1 2 3 4 5 6 7 8 9 10 | john:
name: John Smith
birthday: 1989 - 04 - 17
profession: Blacksmith
bob:
name: Bob Costa
birthday: 1973 - 08 - 10
profession: Surfer
|
所有的fixture会自动加载而且在测试中可以作为本地变量来访问。 Django也支持YAML格式的fixture但是开发人员更倾向于使用JSON格式。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | [
{
"model" : "auth.user" ,
"fields" : {
"name" : "John Smith" ,
"birthday" : "1989-04-17" ,
"profession" : "Blacksmith" ,
}
},
{
"model" : "auth.user" ,
"fields" : {
"name" : "Bob Costa" ,
"birthday" : "1973-08-10" ,
"profession" : "Surfer" ,
}
}
]
|
这没什么吸引力,注意它有多啰嗦,因为你必须显式的定义它属于哪个模板,然后在 fields 下面列出每个字段。
测试模板在单元测试模板时两种框架的方式基本一致。使用一组不同类型的断言来进行确定,比如assert_equal ,assert_not_equal ,assert_nil ,assert_raises 等等。 1 2 3 4 5 6 | class AnimalTest < ActiveSupport::TestCase
test "Animals that can speak are correctly identified" do
assert_equal animals( :lion ).speak(), 'The lion says "roar"'
assert_equal animals( :cat ).speak(), 'The cat says "meow"'
end
end
|
类似功能的代码在Django非常相似。 1 2 3 4 5 6 7 8 9 | class AnimalTestCase(TestCase):
def test_animals_can_speak( self ):
lion = Animal.objects.get(name = "lion" )
cat = Animal.objects.get(name = "cat" )
self .assertEqual(lion.speak(), 'The lion says "roar"' )
self .assertEqual(cat.speak(), 'The cat says "meow"' )
|
测试控制器(controller)Rails又因为它魅力而更胜一筹。Rails 使用类名称来决定哪个控制器正在被测试,而测试某个特定动作(action)就像调用http_verb :action_name 一样简单。我们看一下例子。 1 2 3 4 5 6 7 8 | class UsersControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns( :users )
end
end
|
上面的代码很容易理解正在发生什么。第一行测试模拟了向 User 控制器的 index 动作发起一个请求。第二行随后检查请求是否成功(返回代码200-299)。 assigns 是一个hash,包含了传递到视图(view)的实例变量。所以第三行检查是否存在名为 users 的实例变量并且值不是 nil 。
也有一些类似于assert_difference 这样方便的断言帮助方法。 1 2 3 4 5 |
assert_difference( 'Post.count' ) do
post :create , post: {title: 'Some title' }
end
|
在Django中测试控制器可以通过使用一个叫 Client 类来完成,它扮演着虚拟浏览器(dummy web browser)的角色。下面是Django中对应的代码。 1 2 3 4 5 6 7 8 9 | class UsersTest(unittest.TestCase):
def setUp( self ):
self .client = Client()
def test_index( self ):
response = self .client.get(reverse( 'users:index' ))
self .assertEqual(response.status_code, 200 )
self .assertIsNotNone(response.context[ 'users' ])
|
首先我们必须在测试设置时初始化 Client 。 test_index 的第一行模拟了向 Users 控制器的 index 动作申请了一个 GET 请求。 reverse 查找对应index动作的URL。注意代码是如此冗余并且没有类似于assert_response :success 的帮助方法。 response.context 包含我们传递给视图的变量。 很显然Rails的magic是相当有帮助的。Rails/Ruby同时也拥有很多第三方app,比如factory_girl,RSpec,Mocha,Cucumber,这使得编写测试是一种乐趣。
工具和其他特征依赖性管理(Dependency management)两种框架都有出色的依赖性管理工具。Rails使用 Bundler 来读取Gemfile文件并跟踪文件中对应ruby应用程序运行所依赖的gem。 1 2 3 4 | gem 'nokogiri'
gem 'rails' , '3.0.0.beta3'
gem 'rack' , '>=1.0'
gem 'thin' , '~>1.1'
|
简单的在Gemfile文件中添加一个新行即可完成增加依赖( Dependency)。通过简单调用如下命令即可安装所有需要的gem: Django强烈推荐使用 virtualenv 来隔离Python环境。 pip 则用来管理python包。单独的python包的安装可以通过以下命令完成: 1 | pip install django-debug-toolbar
|
而项目依赖文件可以通过以下集中起来: 1 | pip freeze > requirements.txt
|
管理命令基本上每个项目完成时都有相同的管理工作要做,比如预编译文件(precompiling assets),清理记录(log)等等。Rails使用Rake来管理这些任务。Rake非常灵活而且将开发任务变得简单,特别是依赖于其他任务的。 1 2 3 4 5 | desc "吃掉食物。在吃之前需要烹饪(Cooks)和设置表格(table)。"
task eat: [ :cook , :set_the_table ] do
end
|
Rake的任务可以有前提条件(prerequisite)。上面称为 eat 的任务,在执行之前必须运行任务 cook 和任务set_the_table 。Rake也支持命名空间(namespace),能将相同的任务结合成组(group)来完成。执行任务只需简单的调用任务的名称: Django管理命令就没那么灵活而且不支持前提条件和命名空间。虽然任务最终也会完成,但不是很出色。 1 2 3 4 5 6 7 | class Command(BaseCommand):
help = '吃掉食物'
def handle( self , * args, * * options):
call_command( 'cook' )
set_the_table()
|
如果我们将上面内容保存到eat.py 中,我们可以如下调用它:
国际化和本地化在Rails中国际化有些偏弱。在文件夹config/locales,翻译字符串在文件中作为ruby哈希来定义。 1 2 3 4 5 6 7 | en:
greet_username: "Hello, %{user}!"
pt:
greet_username: "Olá, %{user}!"
|
通过函数t进行翻译。函数第一个变量是决定哪个字符串需要使用的key(比如greet_username)。Rails会自动选择正确的语言。 1 | t( 'greet_username' , user: "Bill" )
|
我发现处理本地语言文件中key名字和手动注册这些内容很繁琐。Django将其打包进非常便捷的gettext。翻译也是通过一个帮助函数完成(ugettext),但是这次的key是不需要翻译的字符串本身。 1 | ugettext( 'Hi, %(user)s.' ) % { 'user' : 'Bill' }
|
Django会检查所有的源代码并调用以下命令自动收集将要翻译的字符串: 1 | django-admin.py makemessages -a
|
上面的命令执行后会为每一种你想要的翻译的语言生成一个文件。文件内容可能像这样: 1 2 3 | msgid "Hi, %(user)s."
msgstr "Olá, %(user)s"
|
注意到我已经在msgstr填充了翻译内容(那些本来是空的)。一旦翻译完成,必须对它们进行编译。 1 | django-admin.py compilemessages
|
这种本地化项目的方法实际上更实用,因为不需要考虑key的名字,在需要的时候也不需要现查找。 【译注】即无需自定义key,django会将整句话作为key值代入
|