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

Test asynchronous functions with RSpec

Test asynchronous functions with RSpec

Lightning talks for Ruby Kaigi 2018 @ 2018/05/31

shigeru. nakajima

May 31, 2018
Tweet

More Decks by shigeru. nakajima

Other Decks in Technology

Transcript

  1. Test asynchronous
    functions with RSpec
    4IJHFSV/BLBKJNBBLB!MFETVO
    -VYJBSDP MUE
    3VCZ,BJHJ

    View Slide

  2. Summary
    I’m talking about how to
    write RSpec test codes for
    asynchronous functions

    View Slide

  3. My Experience
    I implemented asynchronous
    functions for the RoR application to
    support WebSocket.
    I was confusing to test
    asynchronous functions with
    RSpec.

    View Slide

  4. synchronous functions
    We have wrote
    synchronous functions for
    web applications for a long
    time.

    View Slide

  5. Why we can wrote
    synchronous functions?
    Web servers create a thread per
    received HTTP request. Server
    keeps receiving next requests.
    We can treat received request
    synchronously. We don't have to
    care subsequent requests.

    View Slide

  6. Why does WebSocket require
    asynchronous functions?
    Now, many web browsers supports
    WebSocket. To support it, web
    application needs asynchronous
    functions. Why?
    Because we can’t create threads
    automatically for WebSocket
    messages. Why?

    View Slide

  7. Threads per messages bring
    C10K problems
    We send and receive messages on the
    WebSocket connections. Type of messages
    and amount of messages depend on the
    application.
    If we want to send a big message in split
    chunk. Number of chunks may be 10
    thousands.
    Can we create threads per messages? They
    will spent huge amount of memory.

    View Slide

  8. Event loop model architecture
    We know "Event loop model
    architecture" is a nice solution
    for the C10K problem.
    They have only one thread and
    the control flow is switched at
    waiting IO(Input/Output).

    View Slide

  9. Ruby has EventMachine
    The EventMachine gem
    provides event loop model
    architecture.
    “faye-websocket-ruby” or other
    gems for WebSocket use it.

    View Slide

  10. Event loop model brings a
    new problem
    Event loop model has only one
    thread. If you spent long time
    in one function, all subsequent
    events will wait until finishing it.
    We MUST NOT run slow
    functions.

    View Slide

  11. Asynchronous functions
    If we need to run slow functions, we
    create a thread by myself and run the
    function in it.
    We wait end of it and send messages
    on the WebSocket connection.
    This is an asynchronous function.

    View Slide

  12. Sample implementation of
    asynchronous function
    • Create a new thread
    • Run slow calculation
    • Return result by calling block
    def async_func
    Thread.start do
    yield slow_caluculation
    end
    end

    View Slide

  13. Use this function
    with faye-websocket-ruby
    def call
    # Create a WebSocket connection.
    ws = Faye::WebSocket.new(env)
    ws.on :open do
    async_func do |result|
    # Send results on the WebSocket connection.
    ws.send result
    end
    end
    ws.rack_response
    end

    View Slide

  14. We can write asynchronous
    functions

    We can support the WebSocket.

    Next:

    View Slide

  15. Can we test asynchronous
    functions?
    RSpec is a major testing
    framework
    Use RSpec here

    View Slide

  16. RSpec `async_func`
    Does this spec success?
    it 'is expected answer' do
    async_func do |result|
    expect(result).to eq('expected answer')
    end
    end

    View Slide

  17. RSpec `async_func`
    Does this spec fail?
    it 'is expected answer' do
    async_func do |result|
    expect(result).not_to eq('expected answer')
    end
    end

    View Slide

  18. No, the spec don’t fail

    View Slide

  19. Why does not spec fail?
    RSpec captures failed assertions by exception.
    But, this assertion runs in sub thread.
    it 'is expected answer' do
    async_func do |result|
    # --- in sub thread ---
    expect(result).not_to eq('expected answer')
    # --- in sub thread ---
    end
    end

    View Slide

  20. OMG!
    Tests that never fail are not
    helpful.

    View Slide

  21. How can we fail this spec?
    An important thing is
    “Assertions should run in
    the RSpec thread”.
    Ruby has `Queue` to wait
    other threads.

    View Slide

  22. Collect assertion
    Spec fails expectedly!
    it 'result is details' do
    queue = Queue.new
    async_func do |r|
    # --- in sub thread ---
    queue.push r
    # --- in sub thread ---
    end
    # Wait to `queue.push` from other threads.
    result = queue.pop
    # Resume next code when `queue.push` is called.
    expect(result).not_to eq('expected answer')
    end

    View Slide

  23. We can write asynchronous
    functions.
    We can support the WebSocket.
    We can write test codes for
    asynchronous functions.

    View Slide

  24. Let’s Play Async!

    View Slide

  25. AsycPlay gem
    To avoid boilerplate code, I created 'async_play' gem.
    https://rubygems.org/gems/async_play
    it 'result is details' do
    result = AsyncPlay.opening do |curtain|
    async_func do |val|
    curtain.call val
    end
    end
    expect(result).to eq('expected answer')
    end

    View Slide