Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RailsConf 2017: Building the New Rails System Test Framework

RailsConf 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.

Eileen M. Uchitelle

April 25, 2017
Tweet

More Decks by Eileen M. Uchitelle

Other Decks in Programming

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 Hi! I’m 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. 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
  8. 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?
  9. 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
  10. 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
  11. 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
  12. 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
  13. # 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
  14. # 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
  15. # 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 […]
  16. # 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
  17. # 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
  18. # 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
  19. # 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 […]
  20. # 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
  21. # 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 […]
  22. # 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
  23. 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
  24. 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 Thank you RailsConf!
  25. 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