In the system developed by the author, there is a large number of data needs analysis, not only the data analysis is accurate, but also has certain requirements for speed. Before writing the test code, the author used several great methods to achieve this requirement. The result is conceivable, the code is complex, maintenance difficult, difficult to expand. The opportunity to adjust the business, the author, decided to start from the test code, and with continuous learning and application, slowly realize the benefits of testing code.
- Change the train of thought: can do from demand to code process conversion, gradually refinement;
- Simplify code: Try to make each method small, focusing only on one thing;
- Optimization code: When the test code is not written, or need to write a long time, the description of the code is problematic, can be decomposed, need further optimization;
- Easy to extend: When the new business is expanded or the old business is modified, if the test code does not succeed, the extension and modification are unsuccessful;
- Time-half power times: Seemingly write test code is time-consuming, in fact, in testing, deployment and subsequent extensions, the test code will save more time.
Environment construction
The author adopts the test environment is more popular general framework: RSpec + Factory Girl, and autotest automatic tool. RSpec is a descriptive language, which is easy to understand by describing system behavior in a workable example. Factory Girl can help to construct test data, eliminating the trouble of writing fixture. Autotest can automatically run the test code, at any time to detect the results of the test code, and there are a lot of plug-ins to support, you can make the test results appear very cool.
The first step is to install RSpec and rspec-rails
On the command line, execute the following command:
$ sudo gem install rspec v = 1.3.0
$ sudo gem install rspec-rails v = 1.3.2
When the installation is complete, enter the directory where the Rails application is located and run the following script to generate the Spec test framework:
$ script/generate rspec
exists lib/tasks
identical
lib/tasks/rspec.rake identical Script/autospec Identical script/spec
exists spec
identical spec/rcov.opts
identical spec/spec.opts
identical spec/ Spec_helper.rb
Second Step installation Factory-girl
On the command line, execute the following command:
$ sudo gem install rspec v = 1.3.0
$ sudo gem install rspec-rails v = 1.3.2
When the installation is complete, enter the directory where the Rails application is located and run the following script to generate the Spec test framework:
$ script/generate rspec
exists lib/tasks
identical lib/tasks/rspec.rake identical script/autospec
identical script/spec
exists spec
identical spec/rcov.opts
identical spec/spec.opts Identical spec/spec_helper.rb
Second Step installation Factory-girl
On the command line, execute the following command:
$ sudo gem install Factory-girl
In Config/environment/test.rb, join the Gem of Factory-girl:
Config.gem "Factory_girl"
In the spec/directory, add a factories.rb file for all predefined model factories.
Step three Install Autotest
On the command line, execute the following command:
$ sudo gem install zentest
$ sudo gem install autotest-rails
Then set up the integration with RSpec, and in the Rails application directory, run the following commands to display the test case run results.
Rspec=true Autotest or Autospec
In your home directory, add a. Autotest to set up all the Autotest plug-ins for the rails application. Of course, you can also add this file to the root directory of each application, which will overwrite the file settings in the home directory. Autotest Plug-ins are many, the author used the following plugin:
$ sudo gem install autotest-growl
$ sudo gem install autotest-fsevent
$ sudo gem install Redgreen
Set up the. autotest file, and in. Autotest, add the following code.
Require ' Autotest/growl '
require ' autotest/fsevent ' require ' redgreen/autotest ' Autotest.add_hook
: Initialize do |autotest|
%w{.git. SVN Hg. Ds_store. _* Vendor tmp log Doc}.each do |exception|
Autotest.add_exception (Exception)
end
Test experience
After you have installed the necessary libraries, you can write your test code. In this case, all applications are developed on Rails 2.3.4 and RSpec are 1.3.0 versions. To illustrate the problem well, we assume that the requirement is to determine whether a user is late for a period of time. Write test code is to follow a principle, only care about input and output, the specific implementation is not within the scope of the test code, is the behavior-driven development. Based on this requirement, we will design method Absence_at (start_time,end_time) with two input values start_time and end_time as well as an output value, the type is Boolean. The corresponding test code is as follows:
Describe "user absence or not during [Start_time,end_time]" does
Before:each do
@user = Factory (: User)
endit "should return false when user does absence" do
start_time = TIME.UTC (2010,11,9,12,0,0,0)
end_time = TIME.UTC ( 2010,11,9,12,30,0)
@user. Absence_at (start_time,end_time). Should be_false
end
It ' should return true When user absence ' do
start_time = TIME.UTC (2010,11,9,13,0,0,0)
end_time = TIME.UTC (2010,11,9,13,30,0)
@user. Absence_at (start_time,end_time). Should be_ture
end
The test code has been completed. As for the Absence_at method we do not care about its implementation, as long as the results of this method will allow the test code to run the correct results. Based on this test code, you can boldly complete the code and constantly modify the code according to the results of the test code until all the test cases pass.
Use of stub
Write test code, preferably starting from model. Because model method can be very good with the principle of input and output, easy to get started. At first, you will find that the mock and stub is very good, any object can be mock, and on its basis can stub some methods, save the construction of data trouble, once let the author feel that the test code is so beautiful, step-by-step in-depth, only to find themselves into a stub error. or refer to the above example, our code implementation is as follows:
Class User < ActiveRecord::Base
def absence_at (start_time,end_time) return
False if Have_connection_or_ Review? (start_time,end_time)
Return (Login_absence_at? Start_time,end_time)? True:false)
End
According to the original writing test code, there are three cases in this method that require three use cases, and also call the other two methods, which need to be stub, and then have the following test code. I remember being excited when I finished, and thinking, "It's interesting to write the test code."
before(:each) do
@user = User.new
end
describe "method <absence_at(start_time,end_time)>" do
s = Time.now
e = s + 30.minutes
# example one
it "should be false when user have interaction or review" do
@user.stub!(:have_connection_or_review?).with(s,e).and_return(true)
@user.absence_at(s,e).should be_false
end
# example two
it "should be true when user has no interaction and he no waiting at platform" do
@user.stub!(:have_connection_or_review?).with(s,e).and_return(false)
@user.stub!(:login_absence_at?).with(s,e).and_return(true)
@user.absence_at(s,e).should be_true
end
# example three
it "should be false when user has no interaction and he waiting at platform" do
@user.stub!(:have_connection_or_review?).with(s,e).and_return(false)
@user.stub!(:login_absence_at?).with(s,e).and_return(false)
@user.absence_at(s,e).should be_false
end
end
The above test code, is typical of the code implementation details into the test code, completely put the cart before the horse. Of course, when the test code runs, the results are correct. That's because the stub assumes that all the sub methods are true, but what happens if this have_connection_or_review changes and does not return a Boolean value? This test code is still correct, terrible! None of this has the effect of testing the code.
In addition, if so, we will not only modify the Have_connection_or_review test code, but also modify the Absence_at test code. Isn't this increasing the amount of code maintenance?
In contrast, without stub test code, without modification, if the factory data does not change, then the result of the test code will be wrong, because Have_connection_or_review? Failed to pass the test, resulting in absence_ The At method does not function correctly.
In fact, the stub is mainly to mock some of this method or the application can not get the object, such as in the Tech_finish method, called a file_service to get all the files of the Record object, in this method test code run process, Unable to get the service, the stub worked:
Class A < ActiveRecord::Base
has_many:records
def tech_finish?
Self.records.each do |v_a|
return true if V_a.files.size = 5 end return
false End class record
< activerecord::base< C10/>belongs_to:a
Has_files # Here's a service in the gem end
The corresponding test code is as follows:
Describe "Tech_finish" "Do
it" should return true when A ' s records have five files ' did record
= Factory (: Record)app = Factory (: A,:records=>[record])
record.stub! (: Files). And_return ([1,2,3,4,5])
app.tech_finish? should = = True End
It "should return false when A ' s records have less five files" does record
= Factory (: Record)
app = Factory (: A,:records=>[record])
record.stub! (: Files). And_return ([1,2,3,5])
App.tech_ Finish?. should = = False End
The use of factory
With this factory, it is convenient to construct different simulated data to run the test code. Or the above example, if you want to test the Absence_at method, involves multiple model:
- Historyrecord:user's class record.
- Calendar:user's Timetable
- Log information for Logging:user
If the test data is not factory-girl constructed, we will have to construct these test data in fixture. The data constructed in fixture cannot be specified as the test case, but you can specifically specify a set of test data for this method if you are using factory.
Factory.define:user_absence_example,:class => user do |user|
User.login "Test"
class << user
def default_history_records
[Factory.build (: History_record,: Started_at=>time.now),
factory.build (: history_record,:started_at=>time.now)]
end
def default_ Calendars
[Factory.build (: Calendar),
factory.build (: calendar)]
end
def default_loggings
[Factory.build (: Logging,:started_at=>1.days.ago),
Factory.build (: Logging,:started_at=>1.days.ago)] End end
user.history_records {default_ History_records}
user.calendars {default_calendars}
user.loggings {default_loggings}
end
The construction of this test data factory, can be placed in the Factories.rb file, to facilitate the use of other test cases, can also be placed directly in the before of the test file, only for this test file use. Through the construction of factory, not only can you share the same set of test data for multiple test cases, but also the test code is concise and clear.
Before:each do
@user = factory.create (: user_absence_example)
End
ReadOnly's Test
In the author's system, a lot of use of acts_as_readonly, from another database to read data. Since these model is not in the system, so when using factory to construct test data, there will always be problems. Although you can use mocks to do this, you cannot flexibly meet the need to construct test data because of the limitations of mocks. To this end, some code has been extended so that the model can still be tested. The core idea is that, depending on the configuration file settings, the corresponding readonly table is created in the test database, which executes before running the test, so that it achieves the same effect as the other model. In the Site_config configuration file, the configuration format for ReadOnly is as follows:
Readonly_for_test:
logings:
datetime:created_at
string:status
integer:trainer_id
The test of the gem
Gem is widely used in rails and is the most basic thing, so it is more important to be accurate. On the basis of continuous practice, the author's team summed up a way to test the gem with the spec. Suppose that the gem we are testing is platform_base, and the steps are as follows:
1. Create a catalog spec in the root of the gem (path is Platform_base/spec).
2. Create the file Rakefile in the root of the gem (path is Platform_base/rakefile) as follows:
Require ' RubyGems '
require ' rake '
require ' spec/rake/spectask ' spec::rake::spectask.new
(' spec ') do |t|
t.spec_opts = ['--options ', ' spec/spec.opts ']
t.spec_files = filelist[' spec/**/*_spec.rb ']
end
3. File in the Spec directory to create spec.opts (path is platform_base/spec/spec.opts), the contents are as follows:
Copy Code code as follows:
--colour
--format Progress
--loadby Mtime
--reverse
4. In the spec directory, create a rails app named Test_app. This new application requires a spec catalog and a spec_helper.rb file.
5. To keep it simple, tidy up this new app (Test_app), delete the vendor and public directories, and the final structure is as follows:
Copy Code code as follows:
Test_app
|-App
|-Config
| |-Environments
| |-Initializers
| |-APP_CONFIG.YML
| |-BOOT.RB
| |-DATABASE.YML
| |-ENVIRONMENT.RB
| \-routes.rb
|-DB
| \-Test.sqlite3
|-Log
\-Spec
\-spec_helper.rb
6. In the CONFIG/ENVIRONMENT.RB configuration file, add the following code:
Rails::initializer.run do |config|
Config.gem ' rails_platform_base ' End
7. Add the HELPERS_SPEC.RB document under the platform_base/spec/directory, which reads as follows:
Require File.join (File.dirname (__file__), ' Test_app/spec/spec_helper ')
Describe "helpers" do
describe "url_of" does before do rails.stub!
(: env). And_return ("development")
@ Controller = actioncontroller::base.new
end
It "should get URL from app ' Configration" do
@controller. Url_ Of (: article,: Comments,: article_id => 1). should = "http://www.idapted.com/article/articles/1/comments"
@ Controller.url_of (: Article,: Comments,: article_id => 1,:p arams=>{:category=> "good"}). Should = "http:// Www.idapted.com/article/articles/1/comments?category=good "End End"
At this point, the preparation is ready, you can run the rake spec to test in the Platform_base directory, of course, nothing will happen now, because there is no test code yet. The key to this approach is the following require statement, which not only loads rails environment, but also uses and tests gems in Test_app.
Require File.join (File.dirname (__file__), ' Test_app/spec/spec_helper ')
Controller's Test
For controller testing, generally simpler, basically three-stage: initialization parameters, request method, return render or redirect_to. In the following example, a test of the index method for a controller:
Describe "index action" do it "should render the" "The" "Month" and "" "The" "" Do
controller.stub! (: Curre Nt_user). And_return (@user)
get:index,{:flag => "Test"}
response.should render_template ("index")
End End
Some controller will set session or Flash, then the test code must check whether this value is set correctly, but also need to increase the test case to overwrite different values, so that the method can be fully tested. The following example:
Describe ' Create action ' do
it ' should donot create new user with wrong params ' do
post:create
Response.sho Uld redirect_to (users_path)
flash[:notice].should = = "Create fail!"
End
It ' should create a new user with right params ' do
post:create, {: email => ' abc@eleutian.com '}
resp Onse.should redirect_to (users_path)
flash[:notice].should = = "Create successful!"
End End
At the same time, it is necessary to test the controller assigns to ensure that the correct data is returned. The following example:
Before (: each) does
@course = Factory (: course)
end
describe ' show action ' do
it ' should render show page whe N Flag!= Assess and Success "do
get:show,: ID => @course. ID,: Flag =>" Test "
response.should Render_templat E ("show")
assigns[:test_paper].should = = @course
assigns[:flag].should = = "Test end
It"
should Render Show Page flag = = Assess and success do
get:show: ID => @course. ID,: Flag => "Assess"
respon Se.should render_template ("show")
assigns[:test_paper].should = = @course
Assigns[:flag].should = = "Assess "End End
View's Test
The view's test code is less written and basically integrates the core view part into the controller to test. Mainly used Integrate_views method. The following example:
Describe Accountscontroller do
integrate_views
describe ' index action ' do
it ' should render index.rhtml ' do
get:index
response.should render_template ("index")
response.should have_tag ("a[href=?]", New_account _path)
response.should have_tag ("a[href=?]", New_session_path) end end
Summary Outlook
When writing a test code, there is no need to be exhaustive, there are some simpler methods and the internal methods of rails, such as named_scope, are completely unnecessary to test. In this article, only the code that writes unit tests with RSpec is covered, not for integration testing, which is also a direction for future efforts.
In addition, the BDD development model with Cumumber + RSpec + Webrat is also quite good. In particular, cumumber to the requirements of the description, it can be used to do demand analysis.