笔者最近使用Rspec与cucumber两个工具完成一个学习用project测试后,产生几点思考:
 
1.BDD与TDD是怎么样一个关系?   
2.到底一个项目中是不是需要测试?
3.谁来写这个测试?
4.对应的工具测试界线是什么?相应的思路又是什么?

问题一
TDD与BDD是怎么样的关系? 
 

搞清这个问题首先我们要知道这两个是什么概念
TDD:
测试驱动开发是一种开发方法,是开发人员参与的活动。 其效果是以可执行的形式文档化你的需求,迫使你分清职责隔离依赖以驱动你的设计,编织安全网以便将Bug扼杀在在摇篮状态,防止其逃逸。可传统测试人员的活动是试图找到已经逃逸的Bug。这两种活动都是必要的,而且毫不冲突,互为补充。 
BDD:行为驱动开发是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协作。BDD最初是由Dan North在2003年命名,它包括验收测试和客户测试驱动等的极限编程的实践,作为对测试驱动开发的回应。

我们可以得出这样一个结论:BDD是在TDD基础上发展而来的,其测试目的不同,TDD是为了抓住开发中BUG为目的,BDD是为了贴近项目需求为目的 
 
问题二

我的思考:不是必须,看实际情况。我个人是非常推荐使用,总要考虑几个问题
 

1.投入成本,包括编写,和维护(保持更新)
2.效果,是否需要持续集成
3.项目持续时间多长,代码人员代码水平如何?
好处
1.当要deploy前,绿色一大片的感觉让你觉得更有安全感。
2.自动化测试,减少大量的从复劳动
3.代码变更时,自动化测试可追踪代码变更是否对现有功能有所影响
4.规范代码人员代码风格,帮助代码人员理清业务逻辑。
5.增加代码复用率,帮助代码人员自觉抽象model结构,和API接口。
6.cucumber测试的Feature可以作为需求文档

坏处
 

1.增加劳动时间,和学习周期
2.虽然减少了生产代码行数,但是却增加测试代码行数。

结论 :现有的好坏,可有得出一个结论,实施测试是好处大于坏处!

问题三
谁来写这测试代码?

我个人是推荐由开发人员编写,但cucumber每个feature的定义与Scenario,Rspec测试的Describer与IT都是由PM/测试人员是定义(也就是共同编写,一般都应经过 故事分解会议 后制定) , 那我们就来讨论下这个几个方案的

Case1
 

 由PM/测试人员编写全部测试

优点
 

 1.PM/测试人员对需求和边界条件考虑周全。
 
缺点

1.PM /测试人员需要跟代码开发人员对业务逻辑学习,增加交流成本,特别是Rspec测试时。
2. 测试人员无法继续考虑到下一步操作。
3.代码开发人员可能会等PM/测试人员编写完了才能开始工作。

Case2

代码开发人员编写所有测试代码 


优点

1.增加了代码复用率,加快开发速度。
2.
开发人员一般都不喜欢写测试用例,强制要求,也只能写出基本流程的测试(总比没有强)
 
缺点

1. 一般开发人员都太喜欢写测试,边界条件可能想的很差。
2. PM/测试人员无法控制团队开发方向。
3. 增加与PM/测试人员交流成本

总结以上两case,优缺点结合起来我们可以得出结论:测试代码需要PM/测试人员与开发人员一起写 
也就是下面我们说的第三个Case

Case3

测试人员与开发人员一起编写

流程是

PM了解客户需求 -->初步确定产品方案 ---> 与测试人员初步探讨可行性方案 ----> 召开产品故事分解会议确定故事点---->代码开发人员按故事点编写测试代码---->生成代码开发---->运行测试代码------>代码重构----->下一迭代----->项目完成

这样的方法是综合Case1与Case2的方案优缺点。扬长避短。

体外话~~~关于测试代码使用英语还是中文,我的建议是看客户情况,因为cucumber也要拿给客户看的,可能客户也不懂技术问题,但是cucumber却很巧妙将技术故事变成一个个情节故事,如果遇到比较认真的客户,他也可以通过自己常用的语言看懂方案设计。

 
 

问题四:
对应的测试界线是什么?相应的思路又是什么?

这部分内容是笔者实践后得到的感受,可能不是很贴切抓住本质

首先是cucumber,根据BDD的定义,他应该是为了贴切项目需求来的,所以在测试时,关注客户行为,以笔者的新用户注册这个故事点为列,功能是 注册成为网站会员,目的是 为了能够浏览网站只对在线会员可见的那些内容。
那么我们在测试代码中应该这样 写 Feature
位置 :../features/user_login.feature
Feature: 注册成为网站会员
为了能够浏览网站只对在线会员可见的那些内容
作为一名访客
我希望注册成为网站会员

那么现在我们分解这个故事, 客户说我要需要拿到用户的 Email以便我随时向其发送我网站的最新情况,我还需要客户 电话号码,当然如果能有用户照片更好了,好吧~我们在拿到需求后,再次进行分析,用户要注册肯定必须有名称,还得有密码,那么怎么获取这些资料呢我们肯定得构建一个表单,供客户填写,既然有这些需求我们开始编cucumber测试代码。
位置:../features/user_login.feature
Scenario: 用户填写无效数据并注册
Given 我来到注册页面
And 我在输入框<user_name>中输入<invalidUsername>
And 我在输入框<user_email>中输入<songyuchaomeial@163.com>
And 我在输入框<user_password_confirmation>中输入<1234567810>
And 我在输入框<user_password>中输入<123456789>
And 我在输入框<user_phone_number>中输入<12345678913>
And 我上传文件<user_avatar>中选择<cat.jpg>
When 我按下<Create User>按钮
Then 我应该在错误提示信息<li>看到<Password 两次密码不正确>
        
可是电脑无法识别这些内容那么我们还需要写个解释文件如下
位置:../features/step_definitions/user_steps.rb
When /^我来到(.+)$/ do |page_name|
    visit path_to(page_name)
end

When /^我在输入框<(.+)>中输入<(.*)>$/ do |field, value|
    fill_in(field, :with => value)
end

When /^我按下<(.+)>按钮$/ do |button|
    click_button(button)
end

When /^我上传文件<(.+)>中选择<(.*)>$/ do |field,filename|
    attach_file(field,    File.join(Rails.root, "public/features", "assets", filename))
end

Then /^我应该在错误提示信息<(.+)>看到<(.*)>$/ do |field,msg|
    page.find(field).should    have_content msg
end    
但是电脑太傻不知道去哪个页面测试这些,所以我们还要写个地址文件
位置:../features/support/paths.rb

def path_to(page_name)
    case page_name
        when /首页/
            "http://127.0.0.1:3000/users/index"
        when /注册页面/
            "http://127.0.0.1:3000/users/new"

        # Add more page name => path mappings here

        else
            raise "Can't find mapping from \"#{page_name}\" to a path."
    end
end

这样需求分析完成了,那么代码人员也就该开发这个功能了,这是代码人员与测试人员进行探讨边界条件,并写出测试描述,满足上述需求模型为了满足这样的需求,我们需要写个model测试
describe User do

    describe"当任意属性为空时,user都不应被创建"do

        it "当name值为NIL是,user不应该被创建"do
         user = FactoryGirl.build(:user_name_nil)
         assert user.invalid?
        end

        it "当password为空时,user不应该被创建"do
         user = FactoryGirl.build(:user_password_nil)
         assert user.invalid?
        end

        it "当email为空时,user不应该被创建"do
         user = FactoryGirl.build(:user_email_nil)
         assert user.invalid?
        end

        it "当phone_number为NIL时,user不应该被创建"do
            user = FactoryGirl.build(:user_phone_number_nil)
            assert user.invalid?
        end
    end
    describe "当检察不正常的时候,user不应该被创建" do

        it "当password_confirmation与password验证不通过时,user不该被创建"do
         user = FactoryGirl.build(:user_password_confirmation_error)
         user.should_not be_valid
        end

     it "当已有相同name时,user不应该被创建"do
         user_fist = FactoryGirl.create(:user_right)
         user = FactoryGirl.build(:user_right)
         assert user.invalid?
     end

        it "当已有相同的email时,user不应该被创建"do
            user_fist = FactoryGirl.create(:user_right)
            user = FactoryGirl.build(:user_email_repeat)
            assert user.invalid?
        end

        it "当已有相同电话号码phone_number时,user不应该被创建"do
        user_fist = FactoryGirl.create(:user_right)
        user = FactoryGirl.build(:user_phone_number_repeat)
        assert user.invalid?
        end

        describe "当email地址不正确的时候,User不应该被创建"    do

        it "当缺少@符号时,User不能被创建"do
         user = FactoryGirl.build(:user_email_error_at)
            assert user.invalid?
            user.should have(1).errors_on(:email)
        end

        it "当缺少com结尾的,user不能被创建"do
            user = FactoryGirl.build(:user_email_error_com)
            assert user.invalid?
            user.should have(1).errors_on(:email)
        end

        it "当格式不对时,user不能被创建"do
            user = FactoryGirl.build(:user_email_error_name)
            assert user.invalid?
            user.should have(1).errors_on(:email)
        end

        end

        it "当password小于6位数时,User不应该被创建"do
            user = FactoryGirl.build(:user_password_error)
            assert user.invalid?
        end

        it"当phone_number不等于11位数时,User不应该被创建"do
            user = FactoryGirl.build(:user_phone_number_error)
            assert user.invalid?
        end
    end

        it "当保存到数据库时,hashed密码应该会生成"do
         user = FactoryGirl.create(:user_right)
         user.hashed_password.should_not be_nil
        end

describe "登录测试"do

    it "当密码不正确时,不会返回用户对象"do
     old_user = FactoryGirl.create(:user_right)
     user = User.authenticate('name','123458')
     user.should be_nil
    end

     it "当用户名不正确时,不会返回用户对象"do
         old_user = FactoryGirl.create(:user_right)
         user = User.authenticate("name1","123456")
         user.should be_nil
     end
describe "关于照片的测试" do
     before (:each)do
        @photo = Factory.create(:photo)
     end
    it"是否存入指定文件夹位置"do
     File.should be_exists(@photo.avatar.path(:medium))
    end
end
end
end

 
现在模型的设计也OK了,那么我们现在要开始设计流程控制的  controller,同样的需要与测试人员沟通后,我们需要写出下面的测试。

#encoding:utf-8
require 'spec_helper'
describe UsersController do

    describe "创建新用户测试" do
        before(:each) do
            @user = FactoryGirl.build(:user_right)
        end
        it "GET NEW 成功返回应该带有注册的界面" do
            User.stub(:new).and_return(@user)
            get :new
            assigns(:user).should eq(@user)
        end
    end

    describe "GET index" do
        before (:each) do
            @one = Factory.create(:user_two)
            @two = Factory.create(:user_right)
            @three = Factory.create(:user_one)
        end
        it "返回所有的用户并按名称排序" do
            get :index
            assigns(:users).should eq([@one, @two, @three])
        end
    end
#
    describe "GET show" do
        before (:each) do
            @user = Factory.create(:user_right)
        end
        it "assigns the requested user as @user" do
            get :show, {:id => @user.id}
            assigns(:user).should eq(@user)
        end
    end

    describe "GET edit" do
        before (:each) do
            @user = Factory.create(:user_right)
        end
        it "assigns the requested user as @user" do
            get :edit, {:id => @user.id}
            assigns(:user).should eq(@user)
        end
    end

    describe "POST create" do
        describe "with valid params" do
            before (:each) do
                @user = Factory.create(:user_right)
                User.stub(:new).and_return(@user)
            end

            def do_post_create
                post :create, :user => {}
            end

            it "creates a new User" do

                @user.should_receive(:save).and_return(true)
                do_post_create
                assigns(:user).should be_a(User)
                response.should redirect_to(User.last)
            end

            it "creates a new User" do
                @user.should_receive(:save).and_return(false)
                do_post_create
                assigns(:user).should be_a(User)
                response.should render_template(:new)
            end
        end
    end


    describe "PUT update" do
        describe "with valid params" do
            before (:each) do
                @user = Factory.create(:user_right)
            end

            def do_put_update
                put :update
            end

            it "updates the requested user" do
                User.stub(:find).and_return(@user)
                @user.should_receive(:update_attributes).and_return(true)
                do_put_update
                response.should redirect_to(@user)
                flash[:notice].should eq ('现在的用户为' + "#{@user.name}")
            end


            it "assigns the requested user as @user" do
                User.stub(:find).and_return(@user)
                @user.should_receive(:update_attributes).and_return(false)
                do_put_update
                response.should render_template(:edit)
            end
        end

#
        describe "DELETE destroy" do
            before (:each) do
                @user = Factory.create(:user_right)
                User.stub(:find).and_return(@user)
            end
            def do_destroy
                delete :destroy
            end
            it "destroys the requested user" do
                @user.should_receive(:destroy)
                 do_destroy
                response.should redirect_to(users_url)
            end
        end
    end
end


经过这样的实例额,我们得出这样的一个结论: cucumber的测试主要关注是页面上内容,并对页面进行操作,他模拟的是客户在点击页面后,应该看到的情况,Rspec是进行控制器流程控制与模型测试。