之前的博客介绍了一些rails测试相关的知识。测试文件的位置,测试的类型,测试常用命令,以及可用的一些资源,以及如何利用fixtures生成模拟数据。
今天我们来实际的写一下单元测试,用到的知识主要是fixtures和unit test。fixtures用来模拟数据,unit test就是我们今天的主角-单元测试。
今天的代码将以项目为背景,为这个项目写一些单元测试。
这个项目的代码可以在获取到,而且项目已经部署到,大家也可以直接在部署的生产环境上面写自己的博客。
master为主分支,develop为开发分支,开发分支中的代码是最新的。
项目使用ruby on rails开发,开发及测试环境使用sqlite3作为数据存储,生产环境使用mysql作为数据存储。
单元测试
rails的单元测试主要针对model,model是我们的业务实体。单元测试主要测试model的validates,以及model的业务规则,测试经过业务规则的执行,我们的model的变化,就是model的属性值,是否符合规则的描述,是否变为预期的值。
==广告开始
这个博客定位为一个协作的博客平台。就是大家每个人都可以在上面写博客,进行博客的管理,包括分类,tag等等。
博客的主要目标用户是开发者,同时也欢迎更多的其他类型从业者在这个平台发表博客。
通过右上角的注册,就可以成为这个博客平台的一个admin,然后就可以写博客,并且管理博客。当然了,看博客是不需要注册的,直接可以浏览。
在footer部分,有一个链接,通过链接可以进入后台。当然,进入后台需要登录。
博客平台还在继续开发中,如果大家有什么好的想法,或者想加入开发,都可以在上留言。
==广告结束
我们这次的项目是一个博客,有一些基本的功能。
- 浏览博客列表,分类浏览博客,浏览单个博文。
- 查看博文留言,给博文留言。
- 注册用户,登陆平台。
- 管理博客,管理博文,管理分类,管理tag,管理评论。
表结构如下
### users
- id
- nickname
- email
- password_digest
- salt
- created_at
- updated_at
### tags
- id
- title
- created_at
- updated_at
### categorys
- id
- title
- created_at
- updated_at
### comments
- id
- commenter
- commenter_email
- commenter_url
- content
- post_id
- created_at
- updated_at
### posts
- id
- title
- slug
- category_id
- summary
- content
- created_at
- updated_at
- user_id
### posts_tags
在我们的model中写了一些validates规则,针对长度,类型,必填。今天的单元测试的主要目标就是这些validates。
我们先来看一个user的model。
- class User < ActiveRecord::Base
- attr_accessible :email, :nickname, :password, :password_confirmation
- attr_accessor :password, :password_confirmation
-
- validates :password, :confirmation => true
- validates :password_confirmation, :presence => true
-
- validates :email, :presence => true, :uniqueness => true, :format => { :with => /^\w+@\w+\.\w+$/ },
- :length => { :maximum => 40 }
- validates :nickname, :presence => true, :length => { :in =>1..30 }
-
- has_many :posts
在model中我们声明了四个mass-assignment:email, nickname, password, password_confirmation。
每一个都有一些validates,长度的,格式的,还有密码confirmation的。
打开test/unit/user_test.rb文件,这个文件是在你使用rails g model user或者rails g scaffold user之后自定创建的,里面的测试时针对user这个model的单元测试。
如果你的model不是用命令生成的,而是自己写的代码,这个测试文件可能也需要手动创建。
可以先创建一个空白的user_test.rb文件,包含一些基本的内容,不包含任何的测试。
- require 'test_helper'
-
- class UserTest < ActiveSupport::TestCase
-
- end
里面的一些命令需要遵守约定,rails的三个特性之一就是convention over configuration(约定胜过配置)。约定有几个好处:一是减少大家的沟通,而且任何知道约定的人都可以读懂代码,可以很快的了解文件以及代码的内容;二是,针对约定的名称进行统一的代码处理。
rails三特性:
- DRY,do not repeat yourself。
- RESTFul。
- convention over configuration。
第一行的require是加载test/test_helper文件,在这个文件中定义了测试环境的一些配置。
测试类用model的名字+Test作为类名,类需要继承ActiveSupport::TestCase。
我们先来写一个针对nickname的validates的测试,nickname的validates包括两个内容:必填,以及长度范围1-30。
思路就是创建一个user对象,然后看看user是否有效,通过user.valid?判断user是否有效,也就是针对user的validates是否通过。
为了保证其他属性的validates不会干扰我们对nickname的validates的测试,我们需要保证其他属性的值都符合属性对应的validates。
先加入一个合法的nickname的测试,就是说我们的user对象的nickname符合validates中的规则。
- require 'test_helper'
-
- class UserTest < ActiveSupport::TestCase
-
- test "user's nickname should be valid" do
- user = User.new(:nickname => "nickname", :email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.valid?
- end
-
-
- end
一个test...do...end就是一个测试方法,根据SRP(职责单一原则),我们的测试也应该职责单一,保持一个测试只做一件事,只针对一个内容进行测试。
在这个测试中有两行代码,第一行我们用mass-assignment创建一个user对象,每个属性都赋上合法的值。第二行是一个断言,用断言函数来判断user对象是否有效。
我们用rake test命令跑一下这个测试。会看到下面的输出结果。
- blog git:(develop) rake test TEST=test/unit/user_test.rb
- Run options:
-
- # Running tests:
-
- .
-
- Finished tests in 0.803616s, 1.2444 tests/s, 1.2444 assertions/s.
-
- 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
也可以通过ruby -Itest test/unit/user_test.rb来跑这个测试。
第一行是测试用到的命令,第二行开始就是测试的结果。
第六行是一个.,代表执行了一个测试,如果是两个.,代表有两个测试。(如果显示F代表测试失败,显示E代表抛出异常)。
第八行是一些执行时间的统计,总耗时,每秒执行的测试数,每秒执行的断言数。
第十行是一些统计信息,测试个数,断言个数,失败的个数,错误的个数,跳过的个数。
上面的测试结果显示我们有一个测试,一个断言,消耗了0.8秒,测试通过。
我们再来添加一个失败的测试,也就是一个非法的nickname,看看会发生什么。
- def test_user_nickname_should_be_invalid
- user = User.new(:nickname => "nickname"*5, :email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.valid?, "nickname is invalid"
- end
测试方法还可以写成上面的格式,def定义方法,但是按照约定,方法要以test_开头。assert函数包含一个可选的string参数,string的信息在测试失败之后会显示出来。
执行的结果如下:
- blog git:(develop) ruby -Itest test/unit/user_test.rb
- Run options:
-
- # Running tests:
-
- .F
-
- Finished tests in 0.264424s, 7.5636 tests/s, 7.5636 assertions/s.
-
- 1) Failure:
- test_user_email_should_be_invalid(UserTest) [test/unit/user_test.rb:12]:
- email is invalid
-
- 2 tests, 2 assertions, 1 failures, 0 errors, 0 skips
我们看到第六行是一个.和一个F,一个成功,一个失败。
然后下面显示了失败的测试信息,测试的方法,在assert中指定的测试失败之后应该显示的信息。
为什么会失败呢?
因为在测试中我们用了nickname => "nickname"*5
字符串*num,代表字符串会被重复num次,nickname的validates中定义长度的范围是1..30,最大长度是30,但是"nickname"重复5次之后显然超过了我们的定义。但是断言还是用户对象有效,这显然不对,所以测试失败了,没有通过。
不过这不是我们的目的,我们的目的是生成一个nickname不符合validates的对象,然后看看nickname的validates是否起作用,就是判断user.invalid?是否等于true。
把第二个测试修一下。
- def test_user_with_invalid_nickname
- user = User.new(:nickname => "nickname"*5, :email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.invalid?, "nickname is invalid"
- end
继续执行测试命令,看看测试结果。
- blog git:(develop) ruby -Itest test/unit/user_test.rb
- Run options:
-
- # Running tests:
-
- ..
-
- Finished tests in 0.458872s, 4.3585 tests/s, 4.3585 assertions/s.
-
- 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
这次显示两个测试都通过了。
说明我们的针对nickname的validates起了正确的作用。
相对完善的单元测试应该包括各种可能的情况,要提高测试的覆盖率。例如:边界值的检查。
我们再来添加几个针对nickname的测试,检查一些有效的情况,检查一些无效的情况,nickname空值,长度=30,长度=31,长度超过31。
- require 'test_helper'
-
- class UserTest < ActiveSupport::TestCase
-
- test "user have valid nickname" do
- user = User.new(:nickname => "nickname", :email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.valid?, "user should have valid nickname"
- end
-
- def test_user_nickname_length_equal_min_1
- user = User.new(:nickname => "n", :email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.valid?, "use should have valid nickname"
- end
-
- def test_user_nickname_length_equal_max_30
- user = User.new(:nickname => "m"*30, :email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.valid?, "use should have valid nickname"
- end
-
- def test_user_without_nickname
- user = User.new(:email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.invalid?
- end
-
- def test_user_nickname_length_equal_31
- user = User.new(:nickname => "d"*31, :email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.invalid?
- end
-
- def test_user_nickname_length_more_than_31
- user = User.new(:nickname => "nickname"*5, :email => "jor@123.com", :password => "123", :password_confirmation => "123")
- assert user.invalid?
- end
-
- end
我们还可以加入一些针对User.authenticate方法的测试。
- def test_should_authenticate_with_matching_email_and_password
- user = User.create(:nickname=>"asdf",:email=>"qq@123.com",:password=>"123",:password_confirmation=>"123")
-
- assert User.authenticate({ :email=>"qq@123.com",:password=>"123"})
- end
-
-
- def test_should_not_authenticate_with_incorrect_password
- user = User.create(:nickname =>"ddd",:email=>"ww@123.com",:password=>"123",:password_confirmation=>"123")
- assert_nil User.authenticate({ :email=>"ww@123.com",:password=>"234"})
- end
-
本文转自 virusswb 51CTO博客,原文链接:http://blog.51cto.com/virusswb/1075602,如需转载请自行联系原作者