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

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.

Eileen M. Uchitelle

September 08, 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
    ¡Hola! Soy
    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. 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

  30. Config defaults:
    Why Selenium?

    View full-size slide

  31. Can see the tests running in
    the browser

    View full-size slide

  32. Better for beginners learning
    Rails and Capybara

    View full-size slide

  33. driven_by :selenium
    Driver Options

    View full-size slide

  34. driven_by :poltergeist
    Driver Options

    View full-size slide

  35. Config defaults:
    Why Chrome?

    View full-size slide

  36. Chrome is widely used

    View full-size slide

  37. For awhile, Firefox was
    broken with Selenium

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    screen_size: [1400, 1400]

    View full-size slide

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

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

    View full-size slide

  42. Config defaults:
    Failure Screenshots

    View full-size slide

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

    View full-size slide

  44. Database Cleaner
    not required

    View full-size slide


  45. Test transaction
    opens a
    connection

    View full-size slide


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

    View full-size slide

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

    View full-size slide

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

    View full-size slide


  49. Test transaction
    opens a
    connection

    View full-size slide


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

    View full-size slide

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

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

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

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

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

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

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

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

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

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

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

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

  63. Feels like everyone is
    judging you

    View full-size slide

  64. Stakeholders

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  68. Consensus is the enemy
    of vision

    View full-size slide

  69. Dealing with multiple
    stakeholders

    View full-size slide

  70. Manage expectations

    View full-size slide

  71. Know who you are building
    the feature for

    View full-size slide

  72. Be open to change

    View full-size slide

  73. Open source doesn’t work
    without contributors

    View full-size slide

  74. Build a foundation for others
    to work off of

    View full-size slide

  75. Thanks @twapole

    View full-size slide

  76. Thanks @robin850

    View full-size slide

  77. Thanks @mtsmfm &
    @lucasmazza

    View full-size slide

  78. Thanks @renchap

    View full-size slide

  79. Open source doesn’t work
    without contributors

    View full-size slide

  80. Open source doesn’t work
    without you

    View full-size slide

  81. 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!

    View full-size slide

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