rails敏捷开发《3》.doc

上传人:11****ws 文档编号:3737486 上传时间:2019-07-10 格式:DOC 页数:59 大小:471.27KB
下载 相关 举报
rails敏捷开发《3》.doc_第1页
第1页 / 共59页
rails敏捷开发《3》.doc_第2页
第2页 / 共59页
rails敏捷开发《3》.doc_第3页
第3页 / 共59页
rails敏捷开发《3》.doc_第4页
第4页 / 共59页
rails敏捷开发《3》.doc_第5页
第5页 / 共59页
点击查看更多>>
资源描述

1、14.4 应用程序的集成测试Integration Testing of Applications下一个层面的测试是要验证应用程序的工作流程。在某种意义上,这就是在测试客户交给我们的用户故事我们正是根据这些故事来开发应用程序的。譬如说,有这样一个故事:“用户进入商店首页。用户选择一件货品,将其放入购物车。用户结账,在表单中填入详细信息。用户提交表单之后,数据库中创建一份订单,其中包含用户详细信息,以及与购物车中所有货品对应的订单项。 ”这正是集成测试的理想材料。集成测试需要模拟一个或多个虚拟用户与应用程序之间的一组连续的会话,你可以在其中发送请求、监控应答、跟踪重定向,等等。当创建模型,控制器

2、的同时,Rails 就会创建对应的单元测试/功能测试。集成测试却不是自动创建的,你需要自己动手来创建它们。depot ruby script/generate integration_test user_storiesexists test/integration/create test/integration/user_stories_test.rb可以看到,Rails 自动地给测试文件的名称加上了_test 后缀。现在来看看这个生成的文件。require “#File.dirname(_FILE_)/./test_helper“class UserStoriesTest ruby_book

3、.idassert_response :successcart = session:cartassert_equal 1, cart.items.sizeassert_equal ruby_book, cart.items0.product话锋一转,用户故事的第三句话说“用户结账” 。这很容易测试depot_r/test/integration/user_stories_test.rbpost “/store/checkout“assert_response :successassert_template “checkout“此刻,用户需要在结账表单中填入详细信息。在他们提交表单之后,应用程序

4、会创建一份订单,并将用户重定向到商店首页。我们先来测试 HTTP 这边:将表单数据提交到 save_order action,然后验证是否被重定向到首页,同时还要检查购物车是否为空。post_via_redirect 辅助方法可以生成一个 POST 请求,然后跟随重定向,直到收到常规的 200 应答为止。depot_r/test/integration/user_stories_test.rbpost_via_redirect “/store/save_order“ ,:order = :name = “Dave Thomas“ ,:address = “123 The Street“ ,:e

5、mail = ““ ,:pay_type = “check“ assert_response :successassert_template “index“assert_equal 0, session:cart.items.size最后,我们会察看数据库,确定其中有新建的订单与对应的订单项存在,并且其中的数据也正确。由于在测试开始时已经清空了 orders 表,现在其中应该只包含我们新建的订单。depot_r/test/integration/user_stories_test.rborders = Order.find(:all)assert_equal 1, orders.sizeord

6、er = orders0assert_equal “Dave Thomas“ , order.nameassert_equal “123 The Street“ , order.addressassert_equal ““ , order.emailassert_equal “check“ , order.pay_typeassert_equal 1, order.line_items.sizeline_item = order.line_items0assert_equal ruby_book, line_item.product就是这样了。下面是完整的集成测试代码。depot_r/test

7、/integration/user_stories_test.rbrequire “#File.dirname(_FILE_)/./test_helper“class UserStoriesTest ruby_book.idassert_response :successcart = session:cartassert_equal 1, cart.items.sizeassert_equal ruby_book, cart.items0.productpost “/store/checkout“assert_response :successassert_template “checkout

8、“post_via_redirect “/store/save_order“ ,:order = :name = “Dave Thomas“ ,:address = “123 The Street“ ,:email = ““ ,:pay_type = “check“ assert_response :successassert_template “index“assert_equal 0, session:cart.items.sizeorders = Order.find(:all)assert_equal 1, orders.sizeorder = orders0assert_equal

9、“Dave Thomas“ , order.nameassert_equal “123 The Street“ , order.addressassert_equal ““ , order.emailassert_equal “check“ , order.pay_typeassert_equal 1, order.line_items.sizeline_item = order.line_items0assert_equal ruby_book, line_item.productendend更高层面的测试Even Higher-Level Tests(本节包含的内容相当高阶,读者可以放心地

10、跳过它们。)Rails 提供的集成测试功能确实很棒,我们还没见过别的框架内建如此高层面的测试功能。不过我们还可以进行更高层面的测试:可以给 QA 们提供一种专门用于测试这个应用程序的小型语言有时候也被称为领域专用语言(domain-specific language,DSL)。使用这种语言,前面看到的集成测试可以写成这样:depot_r/test/integration/dsl_user_stories_test.rbdef test_buying_a_productdave = regular_userdave.get “/store/index“dave.is_viewing “index

11、“dave.buys_a ruby_bookdave.has_a_cart_containing ruby_bookdave.checks_out DAVES_DETAILSdave.is_viewing “index“check_for_order DAVES_DETAILS, ruby_bookend上述代码使用了 DAVES_DETAILS 这样一个 hash,其定义如下:depot_r/test/integration/dsl_user_stories_test.rbDAVES_DETAILS = :name = “Dave Thomas“ ,:address = “123 The S

12、treet“ ,:email = ““ ,:pay_type = “check“从文学的角度,这段代码也许算不上优秀;但它至少根容易读懂。那么,如何提供这样的功能?不算困难,只要使用 Ruby 提供的一个漂亮的小工具:singleton 方法(singleton method)。假设 obj 是一个变量,它引用任意 Ruby 对象。使用下列语法,我们就可以定义一个方法,该方法只存在于 obj 这个对象上。def obj.method_name# .end然后,我们就可以在 obj 上调用 method_name(),就像调用别的方法一样。这就是我们创造这种测试语言的办法:首先用 open_se

13、ssion()方法新建一个测试会话,然后在会话中定义所有的辅助方法在我们的例子中,这一切都是在 regular_user()方法中完成的。depot_r/test/integration/dsl_user_stories_test.rbdef regular_useropen_session do |user|def user.is_viewing(page)assert_response :successassert_template pageenddef user.buys_a(product)xml_http_request “/store/add_to_cart“ , :id = pr

14、oduct.idassert_response :successenddef user.has_a_cart_containing(*products)cart = session:cartassert_equal products.size, cart.items.sizefor item in cart.itemsassert products.include?(item.product)endenddef user.checks_out(details)post “/store/checkout“assert_response :successassert_template “check

15、out“post_via_redirect “/store/save_order“ ,:order = :name = details:name,:address = details:address,:email = details:email,:pay_type = details:pay_typeassert_response :successassert_template “index“assert_equal 0, session:cart.items.sizeendendendregular_user()方法返回增强后的会话对象,我们在测试过程中会使用这个对象。定义好这种小语言之后,

16、编写别的测试就容易多了。譬如说,我们需要这么一个测试:如果两个用户同时购买同一件货品,两人之间不应该有任何交互。(我们把“mike”这个用户相关的代码行缩进了,以便读者看清程序的流程。)depot_r/test/integration/dsl_user_stories_test.rbdef test_two_people_buyingdave = regular_usermike = regular_userdave.buys_a ruby_bookmike.buys_a rails_bookdave.has_a_cart_containing ruby_bookdave.checks_out

17、 DAVES_DETAILSmike.has_a_cart_containing rails_bookcheck_for_order DAVES_DETAILS, ruby_bookmike.checks_out MIKES_DETAILScheck_for_order MIKES_DETAILS, rails_bookend在第 676 页,我们列出了完整的、实现了小语言的测试类源代码。集成测试支持Integration Testing Support集成测试看上去和功能测试很像,在单元测试和功能测试中使用的那些断言在集成测试中也同样管用。尽管如此,你仍然需要小心,因为很多辅助方法存在一些微

18、妙的差异。集成测试经常涉及到“会话”的概念,后者代表了用户在浏览器上与应用程序的交互。控制器中有一个 session 变量,概念和这里的“会话”有些相似:但同样是“session”这个词,两处的意义却有所不同 *。当开始集成测试时,你会得到一个默认会话(如果需要的话,通过 integration_session 实例变量就可以访问会话对象)。集成测试中用到的方法(例如 get 方法)实际上都是会话对象上的方法:测试框架会帮你将方法调用委派给会话对象。不过你也可以显式创建会话对象(使用 open_session()方法),然后直接调用这些方法,这样就可以同时模拟多个用户的交互(或者为不同的人物创

19、建不同的会话,然后在测试中先后而不是同时使用它们)。在第 209 页,我们就看到了“在测试中使用多个会话”的例子。集成测试的会话对象有下列属性。请注意,在集成测试中如果要对它们赋值,必须明确指定赋值的接收者。self.accept = “text/plain“ # worksopen_session do |sess|sess.accept = “text/plain“ # worksendaccept = “text/plain“ # doesnt work-local variable在下列代码中,sess 变量代表一个会话对象。accept要发送给服务器的 accept 头信息。sess

20、.accept = “text/xml,text/html“controller指向最后一个请求使用的控制器实例。cookies一个 hash,其中包含所有的 cookie。在这个 hash 中放入内容就会随请求发送 cookie;读取其中的值则可以看到应答中设置了哪些 cookie。headers一个 hash,其中包含最后一次应答所返回的头信息。host通过这个值设置下一次请求所针对的主机名。如果应用程序的行为与主机名有关,则可以用这个属性来进行测试。sess.host = “fred.blog_per_“* 译者注:因此译者也采用了不同的方式处理。谈论控制器中的 HTTP session

21、 时, “session”一词不翻译;谈论测试会话时,“session”一词译作“会话” 。path最后一次请求的 URI。remote_addr下一次请求所使用的 IP 地址。如果应用程序区别对待本地请求和远端请求,这个属性口可能会有用。sess.remote_addr=“127.0.0.1“request最后一次请求所使用的请求对象。response最后一次请求所使用的应答对象。status最后一次请求的 HTTP 状态码(200、302、404,等等)。status_message最后一次请求的状态码所对应的状态信息(OK、Not found,等等)。集成测试的便利工具Integrati

22、on Testing Convenience Methodss在集成测试中可以使用下列辅助方法:follow_redirect!()如果控制器处理最后一次请求时进行了重定向,则跟随重定向。get(path, params=nil, headers=nil)post(path, params=nil, headers=nil)xml_http_request(path, params=nil, headers=nil)使用给定参数执行 GET、POST 或者 XML_HTTP 请求。path 参数应该是一个字符串,其中包含要调用的 URI。path 参数不需要包含 URL 中的“协议”和“主机”

23、部分,如果提供了这些内容并且协议部分为 HTTPS,则集成测试会模拟 https 请求。params 参数应该是一个 hash;或者是一个字符串,其中包含经过编码的表单数据 1。get “/store/index“assert_response :successget “/store/product_info“ , :id = 123, :format = “long“get_via_redirect(path,args=)post_via_redirect(path,args=)执行 get 或者 post 请求。如果应答是重定向,则跟随重定向以及后续的 所有重定向,直到收到不是重定向的应答

24、为止。host!(name)设置下一次请求针对的主机名,效果和设置 host 属性一样。https!(use_https=true)如果传入 true(或者不传入参数),后续的请求会使用 https 协议。https?1 application/x-www-form-urlencoded or multipart/form-data返回 https 标志的值。open_session|sess|)新建一个会话对象。如果在参数中传入了一个代码块,则会话对象会被传递给该代码块,否则直接返回会话对象。redirect? ()如果最后一个应答是重定向,则返回 true。reset!()重置会话,这样测

25、试就可以复用会话对象。ur1_for(options)根据指定选项构造一个 URL。此方法可以用于生成 GET 或者 POST 方法的参数。get url_for(:controller = “store“ , :action = “index“ )14.5 性能测试Performance Testing测试不仅要检查程序的功能是否正确,还要检查程序运行是否够快。在继续深入之前,应该先给你一个警告。大部分程序在大部分时候都没有性能问题,而且出现性能问题的地方常常是出乎我们意料的。正因为如此,在开发早期就关注性能通常不是个好主意。我们推荐,只有在两种情况下才进行性能测试都是在开发阶段的晚期: 当

26、规划负载量时,你需要知道一些实际数据,例如需要多少台机器才能承受预期的负载。性能测试可以帮助你获得(并调优) 这些数据。 如果应用程序部署上线之后性能不佳,性能测试可以帮助你找到问题的根源。找到原因之后,测试还可以防止同一问题再次出现。有一个典型的例子,那就是与数据库相关的性能问题。应用程序可能连续运行好几个月都没问题,直到有一天,有人给数据库加上了索引。虽然索引可以帮助解决某个特定的问题,但它却会带来一些意料之外的副作用,会严重影响应用程序其他部分的性能。从前( 其实也就是去年的事),我们推荐的做法是用单元测试来监控性能问题。这样做的基本想法是:当性能出现局限时,测试可以及早给我们提出警告;

27、我们可以在测试过程中了解到性能状况,不必等到部署之后。实际上,正如稍后将会看到的,我们仍然推荐这样做。但这种隔离的性能测试还不是全部,在本节的最后部分我们还会推荐另一种性能测试的方式。我们先从一个不那么真实的场景开始:需要知道 store 控制器是否能够在 3 秒钟内创建 100 份订单,在进行测试时数据库中应该有 1000 种货品(因为我们认为货品种类有可能相当多)。那么,应该如何针对这个场景编写测试呢?要生成这么多货品,我们需要用到动态夹具。depot_r/test/fixtures/performance/products.ymlproduct_:id: title: Product N

28、umber description: My descriptionimage_url: product.gifprice: 1234可以看到,我们把夹具文件放在 fixtures/performance 目录下了。夹具文件的名称必须与数据库表名匹配,因此无法在同一个目录下保存多份针对 products 表的夹具。我们把单元测试使用的夹具文件放在 fixtures 目录下,性能测试使用的 products.yml 文件则放在 performance 子目录下。我们从 1 循环到 1000 以便生成测试数据。一开始我们打算用 1000.times do |i|.这样的写法,但这是不对的,因为 ti

29、mes()方法会生成从 0 到 999 的数值,如果我们把 0 作为 id 值传递给MySQL,数据库则会忽略这个值,转而使用自动生成的主键值。这有可能导致主键冲突。现在该编写性能测试了。同样,我们希望将性能测试与普通测试区分开,所以我们把order_speed_test.rb 文件放在了 test/performance 目录中。由于是对控制器进行测试,这个测试将会基于标准的功能测试。我们从 store_controller_test.rb 拷贝了一份样板文件,再经过简单的修改,现在它看上去就像这样:require File.dirname(_FILE_) + /./test_helperr

30、equire store_controller# Reraise errors caught by the controller.class StoreController; def rescue_action(e) raise e end; endclass OrderSpeedTest DAVES_DETAILS , :cart = cart assert_redirected_to :action = :indexendendassert_equal 100, Order.countassert elapsed_time ruby test/performance/order_speed

31、_test.rb.Finished in 3.840708 seconds.1 tests, 102 assertions, 0 failures, 0 errors我们的性能测试在测试环境下运转良好。但性能问题总是在投入生产运行之后才冒出头来的,所以我们希望能够监控生产环境。还好,在生产环境下我们也有一些工具可以使用。性能监控与评测Profiling and Benchmarking如果你只想知道一个特定方法(或者语句)的性能,你可以使用 Rails 提供的 script/profiler和 script/benchmarker 脚本。benchmarker 脚本可以告诉你一个方法耗费多少时

32、间,profiler则可以告诉你一个方法把时间耗费在什么地方。benchmarker 可以给出相对精确的时间值,而profiler 则会增加较大的开销它所给出的绝对时间值并不重要,重要的是各个操作所需时间的相对比例。下面来看一个故意营造的例子:假如我们感觉 User.encrypted_password()方法耗费了太长的时问,首先需要判断情况是否的确如此。depotruby script/performance/benchmarker User.encrypted_password(“secret“,“salt“)user system total real#1 1.650000 0.030

33、000 1.680000 ( 1.761335)哇!区区一个方法就耗费了 1.8 秒,这确实有点过分!我们再用 profiler 来一探究竟。depot ruby script/performance/profiler User.encrypted_password(“secret“,“salt“)Loading Rails.Using the standard Ruby profiler.% cumulative self self totaltime seconds seconds calls ms/call ms/call name78.65 58.63 58.63 1 58630.00

34、 74530.00 Integer#times21.33 74.53 15.90 1000000 0.02 0.02 Math.sin1.25 75.46 0.93 1 930.00 930.00 Profiler_.start_profile0.01 75.47 0.01 12 0.83 0.83 Symbol#to_sym.0.00 75.48 0.00 1 0.00 0.00 Hash#update这很奇怪:大半的时间似乎都被耗在 times()和 sin()方法上了。我们来看看源代码:def self.encrypted_password(password, salt)1000000.

35、times Math.sin(1)string_to_hash = password + saltDigest:SHA1.hexdigest(string_to_hash)end哎哟! 顶上的那个循环是我在进行某个手工测试时加上的,以便让程序运行得慢一点,然后我就忘了在部署之前把它去掉。我一定是忘了给自己留下一张小纸条最后,别忘了日志文件。如果你需要了解与时间相关的信息,它们能够提供大量宝贵的信息。14.6 使用 Mock 对象Using Mock Objects未来的某个时候,我们肯定需要在 Depot 应用中加上一些代码,以便真正收到来自顾客的付款。所以,假设我们已经搞定了所有那些文案工作

36、,并可以把那些信用卡数字变成我们银行账户上实实在在的钱了。然后,我们创建了一个 PaymentGateWay 类(位于 lib/payment_gateway.rb 文件中),它可以和负责处理信用卡的网关交互。最后,我们在 StoreController 的 save_order()这个 action 中添加下列代码,就可以让 Depot 应用处理信用卡了。gateway = PaymentGateway.newresponse = gateway.collect(:login = username ,:password = password ,:amount = cart.total_pri

37、ce,:card_number = order.card_number,:expiration = order.card_expiration,:name = order.name)当 collect()方法被调用时,这些信息会通过网络发送给后端的信用卡处理系统。这对于我们的钱包有好处,但对于功能测试就不那么好了,因为这样一来,StoreController 就必须能够连接到真正的信用卡处理系统才行。而且,即便网络连接和信用卡系统都不成问题,我们也不能每次运行功能测试就提交一堆信用卡交易事务。所以,我们并不想真的用 PaymentGateWay 对象进行测试,而是想用一个 mock 对象来替换

38、它。在mock 的帮助下,测试就不必依赖于网络连接,从而确保结果的一致性。还好,Rails 让对象的模拟替换也易如反掌。为了在测试环境模拟 collect()方法,我们只需要在 test/mocks/test 目录下创建payment_gateway.rb 文件即可。下面就来看看这个名字中的奥妙吧。首先,文件名必须与试图替换的文件名相同。我们可以模拟模型、控制器或者库文件:唯一的要求就是文件名必须相同。第二,看看占位文件的路径,我们将它放在了 test/mocks 目录的 test 子目录下,这个子目录用于存放所有用于测试环境的占位文件。如果我们希望在开发环境中替换某些文件,就应该将占位文件放

39、在 test/mocks/development 目录下。现在,看看占位文件本身。require lib/payment_gatewayclass PaymentGateway# Im a stubbed out methoddef collect(request)trueendend请注意,占位文件实际上加载了原来的 PaymentGatway 类(通过调用 require()方法),然后对其进行了修改,覆盖了其中的 collect()方法。也就是说,我们不必模拟出 PaymentGateway 的所有方法,只需要修改那些运行测试时有必要修改的方法即可。在这里,修改后的 collect()方法直接返回一个伪造的应答信息。有了这个文件以后,StoreController 就会试用我们模拟出来的 PaymentGateway 类。之所以如此,是因为 Rails 把 mock 路径放在整个搜索路径的最前面,因此它会加载test/mocks/test/payment_gateway.rb 而不是 lib/payment_gateway.rb。

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 实用文档资料库 > 策划方案

Copyright © 2018-2021 Wenke99.com All rights reserved

工信部备案号浙ICP备20026746号-2  

公安局备案号:浙公网安备33038302330469号

本站为C2C交文档易平台,即用户上传的文档直接卖给下载用户,本站只是网络服务中间平台,所有原创文档下载所得归上传人所有,若您发现上传作品侵犯了您的权利,请立刻联系网站客服并提供证据,平台将在3个工作日内予以改正。