您的位置:首页 > 其它

学习札记――BDD与TDD几点思考及其对用工具实列

2013-03-15 09:23 218 查看
笔者最近使用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是进行控制器流程控制与模型测试。
参考资料
http://scriptogr.am/mafai/post/cucumber-best-practices很好的cucumber介绍~~/article/4307714.htmlvirsu 测试cucumber测试http://l404.blogspot.com/2009/02/cucumberrspec-1.html测试用例/article/5529052.html测试教材https://github.com/jnicklas/capybaracapybara使用https://github.com/jnicklas/capybara/blob/master/lib/capybara/node/actions.rbcapybara源代码
本文出自 “不怕错就怕不闯” 博客,请务必保留此出处http://jacksongblack.blog.51cto.com/6378693/1154634
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: