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 end
按照最初写测试代码的思路,本方法中存在三种情况,即需要三个用例,而且还调用了其他两个方法,需要对他们进行stub,于是就有了下面的测试代码。记得当时完成后还很兴奋,心中还想:这么写测试代码真有趣。
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
上面的测试代码,是典型把代码的实现细节带到了测试代码中,完全是本末倒置的。当然这个测试代码运行的时候,结果都是正确的。那是因为用stub来假定所有的子方法都是对的,但是如果这个子方法have_connection_or_review?发生变化,它不返回boolean值,那么将会发生什么呢?这个测试代码依然正确,可怕吧!这都没有起到测试代码的作用。
另外,如果是这样,我们不仅要修改have_connection_or_review?的测试代码,而且还要修改absence_at的测试代码。这不是在增大代码维护量吗?
相比而言,不用stub的测试代码,不用修改,如果Factory的数据没有发生变化,那么测试代码的结果将是错误的,因为have_connection_or_review?没有通过测试,导致absence_at方法无法正常运行。
其实stub主要是mock一些本方法或者本应用中无法得到的对象,比如在tech_finish?方法中,调用了一个file_service来获得Record对象的所有文件,在本方法测试代码运行过程中,无法得到这个service,这时stub就起作用了:
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 end class Record < ActiveRecord::Base belongs_to :a has_files # here is a service in gem end
所对应的测试代码如下:
describe "tech_finish?" do it "should return true when A's records have five files" do 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" do record = Factory(:record) app = Factory(:a,:records=>[record]) record.stub!(:files).and_return([1,2,3,5]) app.tech_finish?.should == false end end










