RubyConf Colombia 2017: Building the New Rails System Test Framework

RubyConf Colombia 2017: Building the New Rails System Test Framework

At the 2014 RailsConf DHH declared system testing would be added to Rails. Three years later, Rails 5.1 makes good on that promise by introducing a new testing framework: ActionDispatch::SystemTestCase. The feature brings system testing to Rails with zero application configuration by adding Capybara integration. After a demonstration of the new framework, we'll walk through what's uniquely involved with building OSS features & how the architecture follows the Rails Doctrine. We'll take a rare look at what it takes to build a major feature for Rails, including goals, design decisions, & roadblocks.

C44e1f7e22c3f23cff7bc130871047ef?s=128

Eileen M. Uchitelle

September 08, 2017
Tweet

Transcript

  1. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run BUILDING THE NEW RAILS System Tests
  2. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run ¡Hola! Soy Eileen M. Uchitelle
  3. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run I’m a Systems Engineer @
  4. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run I’m on the Core Team
  5. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run You can find me online @eileencodes
  6. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run BUILDING THE NEW RAILS System Tests
  7. At the 2014 RailsConf…

  8. None
  9. IS DEAD

  10. Today we do [Rails does] nothing to encourage full system

    tests. There's no default answer in the stack. That's a mistake we’re going to fix. “ —DHH in “TDD is dead. Long live testing.” http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
  11. 3 years later…

  12. Zomg, Rails 5.1 has system testing!!1!11!

  13. Wait, what’s a system test?

  14. Tests your application as a system

  15. gem 'capybara', '> 2.13.0' gem 'selenium-webdriver' Rails 5.1 Gemfile

  16. application_system_test_case.rb require 'test_helper' class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using:

    :chrome, screen_size: [1400, 1400] end
  17. require "application_system_test_case" class PostsTest < ApplicationSystemTestCase test "visiting the index"

    do visit posts_url assert_selector "h1", text: "Post" end end
  18. rails test:system Running System Tests

  19. None
  20. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run SYSTEM TESTING Why did it take 3 years?
  21. Integration tests were slowww….

  22. None
  23. None
  24. Yo, can you make my system test promise come true?

  25. I had never used Capybara before

  26. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run SYSTEM TESTING Guiding Principles
  27. 1. Optimize for programmer happiness 2. Convention over configuration 3.

    The menu is omakase 4. No one paradigm 5. Exalt beautiful code 6. Provide sharp knives 7. Value integrated systems 8. Progress over stability 9. Push up a big tent The Rails Doctrine
  28. Optimize for programmer happiness

  29. require 'test_helper' # set the driver Capybara.current_driver = :selenium #

    register the driver & browser Capybara.register_driver :selenium do |app| Capybara::Selenium::Driver.new(app, browser: :chrome).tap do |driver| driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*[1400, 1400]) end end # set the server Capybara.server = :puma Capybara.always_include_port = true # register the server Capybara.register_server :puma do |app, port, host| Rack::Handler::Puma.run(app, Port: port, Threads: "0:1") end
  30. There is zero configuration required to use Capybara with Rails

    5.1
  31. require 'test_helper' class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome,

    screen_size: [1400, 1400] end
  32. Value integrated systems

  33. Rails as a whole now addresses system testing

  34. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run SYSTEM TESTING Implementation & Architecture
  35. Config defaults: Why Selenium?

  36. Can see the tests running in the browser

  37. Better for beginners learning Rails and Capybara

  38. driven_by :selenium Driver Options

  39. driven_by :poltergeist Driver Options

  40. Config defaults: Why Chrome?

  41. Chrome is widely used

  42. For awhile, Firefox was broken with Selenium

  43. driven_by :selenium, using: :chrome Driver Options

  44. driven_by :selenium, using: :firefox Driver Options

  45. Driver Options driven_by :selenium, using: :firefox
 screen_size: [1400, 1400]

  46. Driver Options driven_by :selenium, using: :firefox
 options: { url: "http://chrome:4444/hub"

    }
  47. Config defaults: Failure Screenshots

  48. def after_teardown take_failed_screenshot Capybara.reset_sessions! super end Driver Options

  49. None
  50. Database Cleaner not required

  51. Test transaction opens a connection

  52. Puma opens a second connection Test transaction opens a connection

  53. The first connection can’t see the data inserted on the

    second connection
  54. None
  55. I asked for help from @tenderlove & @matthewd

  56. Test transaction opens a connection

  57. Puma connects on the same connection Test transaction opens a

    connection
  58. Plumbing

  59. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    initialize(*) # :nodoc: super self.class.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end
  60. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    initialize(*) # :nodoc: super self.class.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end
  61. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run end […]
  62. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}) @driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size) end driven_by :selenium end SystemTestCase.start_application end
  63. # In action_dispatch/system_testing/driver.rb module ActionDispatch module SystemTesting class Driver #

    :nodoc: def initialize(name, **options) @name = name @browser = options[:using] @screen_size = options[:screen_size] @options = options[:options] end def use register if selenium? setup end
  64. # In actionpack/lib/action_dispatch/system_test_case.rb module ActionDispatch class SystemTestCase < IntegrationTest def

    initialize(*) # :nodoc: super self.class.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end
  65. # In action_dispatch/system_testing/driver.rb module ActionDispatch module SystemTesting class Driver #

    :nodoc: def use register unless rack_test? setup end private def rack_test? @name == :rack_test end […]
  66. # In action_dispatch/system_testing/driver.rb def register Capybara.register_driver @name do |app| case

    @name when :selenium then register_selenium(app) when :poltergeist then register_poltergeist(app) when :webkit then register_webkit(app) end end end
  67. # In action_dispatch/system_testing/driver.rb def register Capybara.register_driver @name do |app| Capybara::Selenium::Driver.new(app,

    { browser: @browser }.merge(@options)) .tap do |driver| driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) end end end
  68. # In action_dispatch/system_testing/driver.rb module ActionDispatch module SystemTesting class Driver #

    :nodoc: def use register if selenium? setup end private def selenium? @name == :selenium end […]
  69. # In action_dispatch/system_testing/driver.rb module ActionDispatch module SystemTesting class Driver #

    :nodoc: private def setup Capybara.current_driver = @name end end end end
  70. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run BUILDING Open Source Features
  71. Public Work

  72. Feels like everyone is judging you

  73. None
  74. Stakeholders

  75. None
  76. None
  77. $%&' ()*+ ,-./

  78. $%&' ()*+ ,-./ 70

  79. $%&' ()*+ ,-./ Perfect?

  80. Consensus is the enemy of vision

  81. Dealing with multiple stakeholders

  82. Manage expectations

  83. Know who you are building the feature for

  84. Be open to change

  85. Open source doesn’t work without contributors

  86. Build a foundation for others to work off of

  87. Thanks @twapole

  88. Thanks @robin850

  89. Thanks @mtsmfm & @lucasmazza

  90. Thanks @renchap

  91. Open source doesn’t work without contributors

  92. Open source doesn’t work without you

  93. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run ¡Muchas gracias RubyConf Colombia!
  94. module ActionDispatch class SystemTestCase < IntegrationTest include Capybara::DSL include Capybara::Minitest::Assertions

    include SystemTesting::TestHelpers::SetupAndTeardown include SystemTesting::TestHelpers::ScreenshotHelper def initialize(*) # :nodoc: super self.class.superclass.driver.use end def self.start_application # :nodoc: Capybara.app = Rack::Builder.new do map "/" do run Rails.application end end SystemTesting::Server.new.run Find me everywhere @eileencodes