Working Effectively With Legacy Tdiary Code Using Cucumber And Rspec

4,890 views

Published on

Trying to make tdiary testable with cucumber on SapporoRubyKaigi01

0 Comments
8 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,890
On SlideShare
0
From Embeds
0
Number of Embeds
694
Actions
Shares
0
Downloads
24
Comments
0
Likes
8
Embeds 0
No embeds

No notes for slide

Working Effectively With Legacy Tdiary Code Using Cucumber And Rspec

  1. 1. Working Effectively with Legacy tDiary Code using Cucumber and RSpec KAKUTANI Shintaro; Eiwa System Management,Inc.; Nihon Ruby-no-kai
  2. 2. Working Effectively with Legacy tDiary code using Cucumber and Rspec
  3. 3. Working Effectively with Legacy tDiary code using Cucumber and Rspec
  4. 4. Working Effectively with Legacy tDiary code using Cucumber and Rspec
  5. 5. Working Effectively with Legacy tDiary code using Cucumber and Rspec
  6. 6. Working Effectively with Legacy tDiary code using Cucumber and Rspec
  7. 7. ✓ ✓ ✓ ✓
  8. 8. ✓ ✓ ✓ ✓ ✓
  9. 9. ✓ ✓ ✓ ✓ ✓
  10. 10. ✓ ✓ ✓
  11. 11. ✓ ‣ ‣ ‣ ✓ ‣ ‣
  12. 12. ✓ ✓ ✓ ✓ ✓
  13. 13. eval( quot;@#{key} = params['#{key}']quot; ) end # for 1.4 compatibility @index = @conf.index @update = @conf.update @author_name = @conf.author_name || '' @author_mail = @conf.author_mail || '' @index_page = @conf.index_page || '' @html_title = @conf.html_title || '' @theme = @conf.theme @css = @conf.css @date_format = @conf.date_format @referer_table = @conf.referer_table @options = @conf.options # for ruby 1.6.x support if @conf.secure then @cgi.params.each_value do |p|
  14. 14. eval( quot;@#{key} = params['#{key}']quot; ) end # for 1.4 compatibility @index = @conf.index ✓ @update = @conf.update @author_name = @conf.author_name || '' @author_mail = @conf.author_mail || '' ✓ @index_page = @conf.index_page || '' @html_title = @conf.html_title || '' ✓ @theme = @conf.theme @css = @conf.css @date_format = @conf.date_format ✓ @referer_table = @conf.referer_table @options = @conf.options ✓ # for ruby 1.6.x support if @conf.secure then @cgi.params.each_value do |p|
  15. 15. ✓ ✓ ✓ ✓ ✓
  16. 16. Photo by rla579: http://flickr.com/photos/rla579/2482286520/
  17. 17. ✓ ✓ ✓ ✓ ✓
  18. 18. ✓ ✓ ✓
  19. 19. eval( quot;@#{key} = params['#{key}']quot; ) end # for 1.4 compatibility @index = @conf.index ✓ @update = @conf.update @author_name = @conf.author_name || '' @author_mail = @conf.author_mail || '' ✓ @index_page = @conf.index_page || '' @html_title = @conf.html_title || '' ✓ @theme = @conf.theme @css = @conf.css @date_format = @conf.date_format ✓ @referer_table = @conf.referer_table @options = @conf.options ✓ # for ruby 1.6.x support if @conf.secure then @cgi.params.each_value do |p|
  20. 20. To me, legacy code is simply code without tests. Michael Feathers,“Workin Effectively with Legacy Code”
  21. 21. ✓ ‣ ✓ ‣
  22. 22. Photo by pfish: http://flickr.com/photos/pfish/16518187/
  23. 23. http://rack.rubyforge.org/ ✓ ✓ ✓ ✓ ✓
  24. 24. class HelloApp def call(env) [200, {quot;Content-Typequot; => quot;text/plainquot;}, quot;Hello, Sapporo!quot;] end end require 'hello_app' run HelloApp.new $ rackup hello.ru
  25. 25. if /HEAD/i =~ @cgi.request_method then head['Pragma'] = 'no-cache' head['Cache-Control'] = 'no-cache' print @cgi.header( head ) else #!/usr/bin/env ruby if @cgi.mobile_agent? then # body = conf.to_mobile( tdiary.eval_rhtml( 'i.' ) ) # index.rb $Revision: 1.35 $ head['charset'] = conf.mobile_encoding # head['Content-Length'] = body.size.to_s # Copyright (C) 2001-2006, TADA Tadashi <sho@spc.gr.jp> else # You can redistribute it and/or modify it under GPL2. require 'digest/md5' # body = tdiary.eval_rhtml BEGIN { $defout.binmode } head['ETag'] = %Q[quot;#{Digest::MD5.hexdigest( body )}quot;] $KCODE = 'n' if ENV['HTTP_IF_NONE_MATCH'] == head['ETag'] and /^GET$/i =~ @cgi. head['status'] = CGI::HTTP_STATUS['NOT_MODIFIED'] begin body = '' if FileTest::symlink?( __FILE__ ) then else org_path = File::dirname( File::readlink( __FILE__ ) ) head['charset'] = conf.encoding else head['Content-Length'] = body.size.to_s org_path = File::dirname( __FILE__ ) end end head['Pragma'] = 'no-cache' $:.unshift( org_path.untaint ) head['Cache-Control'] = 'no-cache' require 'tdiary' end head['cookie'] = tdiary.cookies if tdiary.cookies.size > 0 @cgi = CGI::new print @cgi.header( head ) conf = TDiary::Config::new(@cgi) print body tdiary = nil end status = nil rescue TDiary::ForceRedirect if %r[/d{4,8}(-d+)?.html?$] =~ @cgi.redirect_url and not @cgi.valid?( 'date' ) then head = { @cgi.params['date'] = [@cgi.redirect_url.sub( /.*/(d+)(-d+)?.html$/, '12' )] #'Location' => $!.path status = CGI::HTTP_STATUS['OK'] 'type' => 'text/html', end } head['cookie'] = tdiary.cookies if tdiary.cookies.size > 0 begin print @cgi.header( head ) if @cgi.valid?( 'comment' ) then print %Q[ tdiary = TDiary::TDiaryComment::new( @cgi, quot;day.rhtmlquot;, conf ) <html> elsif @cgi.valid?( 'date' ) <head> date = @cgi.params['date'][0] <meta http-equiv=quot;refreshquot; content=quot;1;url=#{$!.path}quot;> if /^d{8}-d+$/ =~ date then <title>moving...</title> tdiary = TDiary::TDiaryLatest::new( @cgi, quot;latest.rhtmlquot;, conf ) </head> elsif /^d{8}$/ =~ date <body>Wait or <a href=quot;#{$!.path}quot;>Click here!</a></body> tdiary = TDiary::TDiaryDay::new( @cgi, quot;day.rhtmlquot;, conf ) </html>] elsif /^d{6}$/ =~ date then rescue TDiary::NotFound tdiary = TDiary::TDiaryMonth::new( @cgi, quot;month.rhtmlquot;, conf ) if @cgi then elsif /^d{4}$/ =~ date then print @cgi.header( 'status' => CGI::HTTP_STATUS['NOT_FOUND'], 'type' tdiary = TDiary::TDiaryNYear::new( @cgi, quot;month.rhtmlquot;, conf ) else end print quot;Status: 404 Not Foundnquot; elsif @cgi.valid?( 'category' ) print quot;Content-Type: text/htmlnnquot; tdiary = TDiary::TDiaryCategoryView::new( @cgi, quot;category.rhtmlquot;, conf ) end elsif @cgi.valid?( 'q' ) puts quot;<h1>404 Not Found</h1>quot; tdiary = TDiary::TDiarySearch::new( @cgi, quot;search.rhtmlquot;, conf ) puts quot;<div>#{' ' * 500}</div>quot; else end tdiary = TDiary::TDiaryLatest::new( @cgi, quot;latest.rhtmlquot;, conf ) rescue Exception end if @cgi then rescue TDiary::PermissionError print @cgi.header( 'status' => CGI::HTTP_STATUS['SERVER_ERROR'], 'type' raise else rescue TDiary::TDiaryError print quot;Status: 500 Internal Server Errornquot; end print quot;Content-Type: text/htmlnnquot; tdiary = TDiary::TDiaryLatest::new( @cgi, quot;latest.rhtmlquot;, conf ) if not tdiary end puts quot;<h1>500 Internal Server Error</h1>quot; begin puts quot;<pre>quot; head = { puts CGI::escapeHTML( quot;#{$!} (#{$!.class})quot; ) 'type' => 'text/html', puts quot;quot; 'Vary' => 'User-Agent' puts CGI::escapeHTML( $@.join( quot;nquot; ) ) } puts quot;</pre>quot; head['status'] = status if status puts quot;<div>#{' ' * 500}</div>quot; body = '' end
  26. 26. ✓ ✓ ✓
  27. 27. ✓ ✓ ✓ ✓
  28. 28. ✓ ✓ ✓ ‣
  29. 29. ✓ ✓ ✓
  30. 30. ✓ ✓
  31. 31. “Programming is the art of doing one thing at a time.” Michael Feathers,“Workin Effectively with Legacy Code”
  32. 32. Refactoring GREEN E D R
  33. 33. ✓ ✓ ✓ ✓
  34. 34. ✓ ✓ ✓ ✓
  35. 35. ✓ ✓ ✓ ✓
  36. 36. SUMMARY
  37. 37. ✓ ‣ ✓ ‣ ✓ ‣
  38. 38. ✓ ✓ ✓ ✓ ✓
  39. 39. ✓ ✓
  40. 40. #!/usr/bin/env ruby require 'webrick' ... class TDiaryDevelopmentServer ... def initialize @server = WEBrick::HTTPServer.new( :Port => 10080, :BindAddress => '127.0.0.1', :DocumentRoot => TDIARY_CORE_DIR, :MimeTypes => tdiary_mime_types, :Logger => WEBrick::Log::new($stderr, WEBrick::Log::DEBUG) ) @server.logger.level = WEBrick::Log::DEBUG trap(quot;INTquot;) { @server.shutdown } trap(quot;TERMquot;) { @server.shutdown } @server.mount(quot;/index.cgiquot;, WEBrick::HTTPServlet::CGIHandler, File.expand_path(quot;index.rbquot;, TDIARY_CORE_DIR)) @server.mount(quot;/update.cgiquot;, WEBrick::HTTPServlet::CGIHandler, File.expand_path(quot;update.rbquot;, TDIARY_CORE_DIR)) end ... end if $0 == __FILE__ TDiaryDevelopmentServer.run end
  41. 41. ✓ ✓ ✓
  42. 42. module TDiary ... # # class Config # configuration class # class Config ... private # loading tdiary.conf in current directory def load @secure = true unless @secure @options = {} eval( File::open( quot;tdiary.confquot; ){|f| f.read }.untaint, binding, quot;(tdiary.conf)quot;, 1 ) # language setup @lang = 'ja' unless @lang begin ...
  43. 43. ✓ ‣ ✓ ‣ ‣ ✓
  44. 44. ✓ ‣ ‣ ✓ ‣ ‣
  45. 45. ✓ ✓ ✓ ‣ ✓ ‣
  46. 46. Photo by whiteoakart: http://www.flickr.com/photos/whiteoakart/249859836/
  47. 47. http://github.com/aslakhellesoy/cucumber/ ✓ ✓ ✓ ✓ ‣
  48. 48. ✓ ‣ ✓ ‣ ‣ ✓
  49. 49. ✓ ✓ ✓ ‣
  50. 50. ✓ ✓ ✓
  51. 51. module TDiary ... class Config def self.tdiary_config_file_path( filename = quot;tdiary.confquot; ) filename end ... private # loading tdiary.conf in current directory def load @secure = true unless @secure @options = {} load_current_directory_conf ... end def load_current_directory_conf eval( File::open( Config.tdiary_config_file_path ) {|f| f.read }.untaint, b, quot;(#{Config.tdiary_config_file_path})quot;, 1 ) end ...
  52. 52. ✓ ✓ ✓ ‣ ✓
  53. 53. # -*- coding: utf-8 -*- Given quot; tdiary.confquot; do ... @driver = TDiaryDriver.configure do data_path File.expand_path( quot;../fixtures/just_installed/tdiary.confquot;, File.dirname(__FILE__)) end end ... Given /(?: | ) (.*)/ do |param| @driver.append_param(param) end When /(.*) (?: | ) / do |uri| @status, @header, @response = @driver.invoke(uri) end ...
  54. 54. class TDiaryDriver ... def data_path(path) stub(TDiary::Config).tdiary_config_file_path { path } end ... def invoke(path) raw_result = StringIO.new begin stdin_spy = StringIO.new(quot;quot;) ... # $stdin = stdin_spy ... $stdout = raw_result # tdiary_cgi_path = File.expand_path(path, tdiary_base_dir) load tdiary_cgi_path ensure ... # RSpce expectation wrap @res = ResponseHelper.parse(raw_result.read) end ... [@res.status_code, @res.headers, @res.body] end
  55. 55. class TDiaryDriver ... def data_path(path) stub(TDiary::Config).tdiary_config_file_path { path } end ... def invoke(path) raw_result = StringIO.new begin stdin_spy = StringIO.new(quot;quot;) ... # $stdin = stdin_spy ... $stdout = raw_result # tdiary_cgi_path = File.expand_path(path, tdiary_base_dir) load tdiary_cgi_path ensure ... # RSpce expectation wrap @res = ResponseHelper.parse(raw_result.read) end ... [@res.status_code, @res.headers, @res.body] end
  56. 56. stub(TDiary::Config).tdiary_config_file_path { path } TDiary::Config.stub!(:tdiary_config_file_path). and_return { path }
  57. 57. Photo by thomasvignaud: http://www.flickr.com/photos/thomasvignaud/2160847847/
  58. 58. http://github.com/btakita/rr/ ✓ ✓ ✓ ‣ ‣
  59. 59. http://capsctrl.que.jp/kdmsnr/wiki/bliki/?TestDouble
  60. 60. http://xunitpatterns.com/Test%20Double.html
  61. 61. ✓ ‣ ‣ ‣ ✓ ✓
  62. 62. ✓ ‣ ‣ ✓ ‣ ‣ ‣
  63. 63. ✓ ‣ ‣ ✓ ‣
  64. 64. class TDiaryDriver ... def data_path(path) stub(TDiary::Config).tdiary_config_file_path { path } end ... def invoke(path) raw_result = StringIO.new begin stdin_spy = StringIO.new(quot;quot;) ... # $stdin = stdin_spy ... $stdout = raw_result # tdiary_cgi_path = File.expand_path(path, tdiary_base_dir) load tdiary_cgi_path ensure ... # RSpce expectation wrap @res = ResponseHelper.parse(raw_result.read) end ... [@res.status_code, @res.headers, @res.body] end ...
  65. 65. module Rack class TDiaryApp class << self def index new(TDiaryDriver.index) end ... end ... def call(env) # req = Request.new(env) tdiary_conf = ::File.expand_path( quot;../../tdiary.confquot;, ::File.dirname(__FILE__)) driver = @driver.configure { data_path tdiary_conf } driver.invoke end end end
  66. 66. ... use Rack::ShowExceptions use Rack::CommonLogger use Rack::Lint use Rack::Reloader use Rack::Static, :urls => [quot;/themequot;], :root => parent_dir map quot;/quot; do run Rack::TDiaryApp.index end map quot;/index.rbquot; do run Rack::TDiaryApp.index end map quot;/update.rbquot; do run Rack::TDiaryApp.update end
  67. 67. ... module Rack app = ShowExceptions.new( CommonLogger.new( Lint.new( TDiaryApp.index))) Handler::CGI.run app end
  68. 68. ✓ ‣ ‣ ‣ ✓ ‣
  69. 69. ✓ ‣ ✓ ‣ ‣ ‣ ‣
  70. 70. Let’s face it, working in legacy code is surgery, and doctors never operate alone. Michael Feathers,“Workin Effectively with Legacy Code”
  71. 71. github http://github.com/kakutani/testable_tdiary/
  72. 72. ✓ ✓ ✓ ✓ ✓

×