Rails测试《四》实战单元测试unit test

简介:

之前的博客介绍了一些rails测试相关的知识。测试文件的位置,测试的类型,测试常用命令,以及可用的一些资源,以及如何利用fixtures生成模拟数据。

今天我们来实际的写一下单元测试,用到的知识主要是fixtures和unit test。fixtures用来模拟数据,unit test就是我们今天的主角-单元测试。

今天的代码将以blog项目为背景,为这个项目写一些单元测试。

这个项目的代码可以在https://github.com/woaigithub/blog获取到,而且项目已经部署到http://42.121.5.68:10000/,大家也可以直接在部署的生产环境上面写自己的博客。

master为主分支,develop为开发分支,开发分支中的代码是最新的。

项目使用ruby on rails开发,开发及测试环境使用sqlite3作为数据存储,生产环境使用mysql作为数据存储。

 

单元测试

rails的单元测试主要针对model,model是我们的业务实体。单元测试主要测试model的validates,以及model的业务规则,测试经过业务规则的执行,我们的model的变化,就是model的属性值,是否符合规则的描述,是否变为预期的值。

 

==广告开始

这个博客定位为一个协作的博客平台。就是大家每个人都可以在上面写博客,进行博客的管理,包括分类,tag等等。

博客的主要目标用户是开发者,同时也欢迎更多的其他类型从业者在这个平台发表博客。

通过右上角的注册,就可以成为这个博客平台的一个admin,然后就可以写博客,并且管理博客。当然了,看博客是不需要注册的,直接可以浏览。

在footer部分,有一个admin链接,通过链接可以进入后台。当然,进入后台需要登录。

博客平台还在继续开发中,如果大家有什么好的想法,或者想加入开发,都可以在https://github.com/woaigithub/blog上留言。

==广告结束

 

我们这次的项目是一个博客,有一些基本的功能。

  • 浏览博客列表,分类浏览博客,浏览单个博文。
  • 查看博文留言,给博文留言。
  • 注册用户,登陆平台。
  • 管理博客,管理博文,管理分类,管理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

  • post_id
  • tag_id

 

在我们的model中写了一些validates规则,针对长度,类型,必填。今天的单元测试的主要目标就是这些validates。

我们先来看一个user的model。

 

 
  1. class User < ActiveRecord::Base 
  2.   attr_accessible :email:nickname:password:password_confirmation 
  3.   attr_accessor :password:password_confirmation 
  4.     
  5.   validates :password:confirmation => true 
  6.   validates :password_confirmation:presence => true 
  7.   
  8.   validates :email:presence => true:uniqueness => true:format => { :with => /^\w+@\w+\.\w+$/ }, 
  9.                     :length => { :maximum => 40 } 
  10.   validates :nickname:presence => true:length => { :in =>1..30 } 
  11.  
  12.   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文件,包含一些基本的内容,不包含任何的测试。

 
  1. require 'test_helper' 
  2.  
  3. class UserTest < ActiveSupport::TestCase 
  4.   
  5. 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中的规则。

 
  1. require 'test_helper' 
  2.  
  3. class UserTest < ActiveSupport::TestCase 
  4.   
  5.   test "user's nickname should be valid" do 
  6.      user = User.new(:nickname => "nickname":email => "jor@123.com":password => "123":password_confirmation => "123"
  7.      assert user.valid? 
  8.   end  
  9.  
  10.  
  11. end 

一个test...do...end就是一个测试方法,根据SRP(职责单一原则),我们的测试也应该职责单一,保持一个测试只做一件事,只针对一个内容进行测试。

在这个测试中有两行代码,第一行我们用mass-assignment创建一个user对象,每个属性都赋上合法的值。第二行是一个断言,用断言函数来判断user对象是否有效。

我们用rake test命令跑一下这个测试。会看到下面的输出结果。

 
  1.   blog git:(develop) rake test TEST=test/unit/user_test.rb 
  2. Run options:  
  3.  
  4. # Running tests: 
  5.  
  6.  
  7. Finished tests in 0.803616s, 1.2444 tests/s, 1.2444 assertions/s. 
  8.  
  9. 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips 

也可以通过ruby -Itest test/unit/user_test.rb来跑这个测试。

第一行是测试用到的命令,第二行开始就是测试的结果。

第六行是一个.,代表执行了一个测试,如果是两个.,代表有两个测试。(如果显示F代表测试失败,显示E代表抛出异常)。

第八行是一些执行时间的统计,总耗时,每秒执行的测试数,每秒执行的断言数。

第十行是一些统计信息,测试个数,断言个数,失败的个数,错误的个数,跳过的个数。

 

上面的测试结果显示我们有一个测试,一个断言,消耗了0.8秒,测试通过。

我们再来添加一个失败的测试,也就是一个非法的nickname,看看会发生什么。

 
  1. def  test_user_nickname_should_be_invalid  
  2.      user = User.new(:nickname => "nickname"*5, :email => "jor@123.com", :password => "123", :password_confirmation => "123") 
  3.      assert user.valid?, "nickname is invalid" 
  4. end 

测试方法还可以写成上面的格式,def定义方法,但是按照约定,方法要以test_开头。assert函数包含一个可选的string参数,string的信息在测试失败之后会显示出来。

执行的结果如下:

 
  1.   blog git:(develop)  ruby -Itest test/unit/user_test.rb  
  2. Run options:  
  3.  
  4. # Running tests: 
  5.  
  6. .F 
  7.  
  8. Finished tests in 0.264424s, 7.5636 tests/s, 7.5636 assertions/s. 
  9.  
  10.   1) Failure: 
  11. test_user_email_should_be_invalid(UserTest) [test/unit/user_test.rb:12]: 
  12. email is invalid 
  13.  
  14. 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。

把第二个测试修一下。

 
  1. def  test_user_with_invalid_nickname  
  2.     user = User.new(:nickname => "nickname"*5, :email => "jor@123.com":password => "123":password_confirmation => "123"
  3.     assert user.invalid?, "nickname is invalid" 
  4.  end 

继续执行测试命令,看看测试结果。

 
  1.   blog git:(develop)  ruby -Itest test/unit/user_test.rb 
  2. Run options:  
  3.  
  4. # Running tests: 
  5.  
  6. .. 
  7.  
  8. Finished tests in 0.458872s, 4.3585 tests/s, 4.3585 assertions/s. 
  9.  
  10. 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips 

这次显示两个测试都通过了。

说明我们的针对nickname的validates起了正确的作用。

相对完善的单元测试应该包括各种可能的情况,要提高测试的覆盖率。例如:边界值的检查。

我们再来添加几个针对nickname的测试,检查一些有效的情况,检查一些无效的情况,nickname空值,长度=30,长度=31,长度超过31。

 
  1. require 'test_helper' 
  2.  
  3. class UserTest < ActiveSupport::TestCase 
  4.   
  5.   test "user have valid nickname" do 
  6.      user = User.new(:nickname => "nickname":email => "jor@123.com":password => "123":password_confirmation => "123"
  7.      assert user.valid?, "user should have valid nickname" 
  8.   end  
  9.    
  10.   def test_user_nickname_length_equal_min_1 
  11.     user = User.new(:nickname => "n":email => "jor@123.com":password => "123":password_confirmation => "123"
  12.     assert user.valid?, "use should have valid nickname" 
  13.   end 
  14.      
  15.   def test_user_nickname_length_equal_max_30 
  16.     user = User.new(:nickname => "m"*30, :email => "jor@123.com":password => "123":password_confirmation => "123"
  17.     assert user.valid?, "use should have valid nickname" 
  18.   end 
  19.  
  20.   def  test_user_without_nickname  
  21.      user = User.new(:email => "jor@123.com":password => "123":password_confirmation => "123"
  22.      assert user.invalid? 
  23.   end   
  24.  
  25.   def  test_user_nickname_length_equal_31  
  26.     user = User.new(:nickname => "d"*31, :email => "jor@123.com":password => "123":password_confirmation => "123"
  27.     assert user.invalid? 
  28.   end 
  29.  
  30.   def  test_user_nickname_length_more_than_31 
  31.     user = User.new(:nickname => "nickname"*5, :email => "jor@123.com":password => "123":password_confirmation => "123"
  32.     assert user.invalid? 
  33.   end 
  34.   
  35. end 

我们还可以加入一些针对User.authenticate方法的测试。

 
  1. def test_should_authenticate_with_matching_email_and_password 
  2.     user = User.create(:nickname=>"asdf",:email=>"qq@123.com",:password=>"123",:password_confirmation=>"123"
  3.      
  4.     assert User.authenticate({:email=>"qq@123.com",:password=>"123"}) 
  5.   end 
  6.  
  7.  
  8.   def test_should_not_authenticate_with_incorrect_password  
  9.     user = User.create(:nickname =>"ddd",:email=>"ww@123.com",:password=>"123",:password_confirmation=>"123"
  10.     assert_nil User.authenticate({:email=>"ww@123.com",:password=>"234"}) 
  11.   end 
  12.   

 




本文转自 virusswb 51CTO博客,原文链接:http://blog.51cto.com/virusswb/1075602,如需转载请自行联系原作者

目录
相关文章
|
1月前
|
Java 测试技术 开发者
Java单元测试与集成测试:确保代码质量的最佳实践
【4月更文挑战第2天】在软件开发中,单元测试验证单个代码单元(如Java类或方法)的功能,确保其正确性;而集成测试则关注多个组件协作时的交互。JUnit是常见的Java单元测试框架,集成测试则检验组件间接口的兼容性。Spring框架提供了集成测试的支持。遵循良好编码习惯,编写可测试代码,设计全面的测试用例,是保证代码质量和稳定性的关键。
|
3月前
|
Java 测试技术 Maven
JAVA单元测试概念与实战
单元测试是软件开发中的一个测试方法,用于验证软件代码中最小的、独立的单元是否按照预期工作。在Java中,这通常指的是单个的方法或者一个类的个别功能。单元测试的目的是隔离代码的每个部分,并确保各个部分是正确的。
54 4
|
28天前
|
Java 测试技术
SpringBoot整合单元测试&&关于SpringBoot单元测试找不到Mapper和Service报java.lang.NullPointerException的错误
SpringBoot整合单元测试&&关于SpringBoot单元测试找不到Mapper和Service报java.lang.NullPointerException的错误
21 0
|
6天前
|
安全 测试技术 Go
Golang深入浅出之-Go语言单元测试与基准测试:testing包详解
【4月更文挑战第27天】Go语言的`testing`包是单元测试和基准测试的核心,简化了测试流程并鼓励编写高质量测试代码。本文介绍了测试文件命名规范、常用断言方法,以及如何进行基准测试。同时,讨论了测试中常见的问题,如状态干扰、并发同步、依赖外部服务和测试覆盖率低,并提出了相应的避免策略,包括使用`t.Cleanup`、`t.Parallel()`、模拟对象和检查覆盖率。良好的测试实践能提升代码质量和项目稳定性。
10 1
|
6天前
|
监控 JavaScript 前端开发
【TypeScript技术专栏】TypeScript的单元测试与集成测试
【4月更文挑战第30天】本文讨论了在TypeScript项目中实施单元测试和集成测试的重要性。单元测试专注于验证单个函数、类或模块的行为,而集成测试关注不同组件的协作。选用合适的测试框架(如Jest、Mocha),配置测试环境,编写测试用例,并利用模拟和存根进行隔离是关键。集成测试则涉及组件间的交互,需定义测试范围,设置测试数据并解决可能出现的集成问题。将这些测试整合到CI/CD流程中,能确保代码质量和快速响应变化。
|
13天前
|
资源调度 JavaScript 测试技术
单元测试:编写和运行Vue组件的单元测试
【4月更文挑战第23天】本文探讨了为Vue组件编写单元测试的重要性,以及如何设置测试环境、编写和运行测试。通过使用Jest或Mocha作为测试框架,结合Vue Test Utils,可以独立测试组件的功能,如渲染、事件处理和状态管理。编写测试用例时,应注意覆盖各种行为,并使用断言验证组件状态。运行测试并观察结果,确保测试独立性和高覆盖率。单元测试是保证代码质量和维护性的关键,应随着项目发展持续更新测试用例。
|
Java 测试技术
Java 中的单元测试和集成测试策略
【4月更文挑战第19天】本文探讨了Java开发中的单元测试和集成测试。单元测试专注于单一类或方法的功能验证,使用测试框架如JUnit,强调独立性、高覆盖率和及时更新测试用例。集成测试则验证模块间交互,通过逐步集成或模拟对象来检测系统整体功能。两者相辅相成,确保软件质量和降低修复成本。
|
19天前
|
测试技术 Python
Python 的自动化测试:什么是单元测试和集成测试?在 Python 中如何进行自动化测试?
【4月更文挑战第17天】本文介绍了软件测试中的单元测试和集成测试。单元测试针对单个函数或方法,确保其功能正确;集成测试则检验多个单元交互是否正常。Python 自带的 unittest 模块提供自动化测试框架,示例代码展示了如何创建测试类及测试方法,通过断言验证字符串方法的行为。
11 1
|
21天前
|
测试技术 数据库 开发者
Django自动化测试入门:单元测试与集成测试
【4月更文挑战第15天】本文介绍了Django的自动化测试,包括单元测试和集成测试。单元测试专注于单个视图、模型等组件的正确性,而集成测试则测试组件间的交互。Django测试框架提供`TestCase`和`Client`进行单元和集成测试。通过编写测试,开发者能确保代码质量、稳定性和应用的正确协同工作。运行测试使用`python manage.py test`命令,建议将其纳入日常开发流程。
|
24天前
|
缓存 自动驾驶 测试技术
如何进行有效的Apollo测试:单元测试和集成测试指南
如何进行有效的Apollo测试:单元测试和集成测试指南
50 13