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

Ruby Nation 2017: Building the New Rails System Test Framework

Ruby Nation 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

June 16, 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

    View full-size slide

  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

    View full-size slide

  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 @

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  7. At the 2014
    RailsConf…

    View full-size slide

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

    View full-size slide

  9. 3 years later…

    View full-size slide

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

    View full-size slide

  11. Wait, what’s a
    system test?

    View full-size slide

  12. Tests your application
    as a system

    View full-size slide

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

    View full-size slide

  14. application_system_test_case.rb
    require 'test_helper'
    class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
    driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
    end

    View full-size slide

  15. require "application_system_test_case"
    class PostsTest < ApplicationSystemTestCase
    test "visiting the index" do
    visit posts_url
    assert_selector "h1", text: "Post"
    end
    end

    View full-size slide

  16. rails test:system
    Running System Tests

    View full-size slide

  17. 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?

    View full-size slide

  18. Integration tests
    were slowww….

    View full-size slide

  19. Yo, can you
    make my system
    test promise
    come true?

    View full-size slide

  20. I had never used
    Capybara
    before

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  23. Optimize for programmer
    happiness

    View full-size slide

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

    View full-size slide

  25. There is zero configuration
    required to use Capybara
    with Rails 5.1

    View full-size slide

  26. require 'test_helper'
    class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
    driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
    end

    View full-size slide

  27. Value integrated systems

    View full-size slide

  28. Rails as a whole now
    addresses system testing

    View full-size slide

  29. Progress over stability

    View full-size slide

  30. When system tests were
    merged there were a few
    known bugs

    View full-size slide

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

    View full-size slide

  32. Config defaults:
    Why Selenium?

    View full-size slide

  33. Can see the tests running in
    the browser

    View full-size slide

  34. Better for beginners learning
    Rails and Capybara

    View full-size slide

  35. driven_by :selenium
    Driver Options

    View full-size slide

  36. driven_by :poltergeist
    Driver Options

    View full-size slide

  37. Config defaults:
    Why Chrome?

    View full-size slide

  38. Chrome is widely used

    View full-size slide

  39. For awhile, Firefox was
    broken with Selenium

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    screen_size: [1400, 1400]

    View full-size slide

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

    options: {
    url: "http://chrome:4444/hub"
    }

    View full-size slide

  44. Config defaults:
    Failure Screenshots

    View full-size slide

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

    View full-size slide

  46. test "the page contains users" do
    visit users_path
    take_screenshot
    assert_selector "h1", text: "Arya"
    end
    Take a Screenshot

    View full-size slide

  47. Database Cleaner
    not required

    View full-size slide


  48. Test transaction
    opens a
    connection

    View full-size slide


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

    View full-size slide

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

    View full-size slide

  51. I asked for help from
    @tenderlove & @matthewd

    View full-size slide


  52. Test transaction
    opens a
    connection

    View full-size slide


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

    View full-size slide

  54. # 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

    View full-size slide

  55. # 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

    View full-size slide

  56. # 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
    […]

    View full-size slide

  57. # 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

    View full-size slide

  58. # 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

    View full-size slide

  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

    View full-size slide

  60. # 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
    […]

    View full-size slide

  61. # 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

    View full-size slide

  62. # 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

    View full-size slide

  63. # 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
    […]

    View full-size slide

  64. # 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

    View full-size slide

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

    View full-size slide

  66. Feels like everyone is
    judging you

    View full-size slide

  67. Stakeholders

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  72. Consensus is the enemy
    of vision

    View full-size slide

  73. Dealing with multiple
    stakeholders

    View full-size slide

  74. Manage expectations

    View full-size slide

  75. Know who you are building
    the feature for

    View full-size slide

  76. Be open to change

    View full-size slide

  77. Open source doesn’t work
    without contributors

    View full-size slide

  78. Build a foundation for others
    to work off of

    View full-size slide

  79. Thanks @twapole

    View full-size slide

  80. Thanks @robin850

    View full-size slide

  81. Thanks @mtsmfm &
    @lucasmazza

    View full-size slide

  82. Thanks @renchap

    View full-size slide

  83. Open source doesn’t work
    without contributors

    View full-size slide

  84. Open source doesn’t work
    without you

    View full-size slide

  85. 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
    Ruby Nation!

    View full-size slide

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

    View full-size slide