Pro Yearly is on sale from $80 to $50! »

Test asynchronous functions with RSpec

Test asynchronous functions with RSpec

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


shigeru. nakajima

May 31, 2018


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

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

    for asynchronous functions
  3. My Experience I implemented asynchronous functions for the RoR application

    to support WebSocket. I was confusing to test asynchronous functions with RSpec.
  4. synchronous functions We have wrote synchronous functions for web applications

    for a long time.
  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.
  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?
  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.
  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).
  9. Ruby has EventMachine The EventMachine gem provides event loop model

    architecture. “faye-websocket-ruby” or other gems for WebSocket use it.
  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.
  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.
  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
  13. Use this function with faye-websocket-ruby def call # Create a

    WebSocket connection. ws = ws.on :open do async_func do |result| # Send results on the WebSocket connection. ws.send result end end ws.rack_response end
  14. We can write asynchronous functions We can support the WebSocket.

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

    framework Use RSpec here
  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
  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
  18. No, the spec don’t fail

  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
  20. OMG! Tests that never fail are not helpful.

  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.
  22. Collect assertion Spec fails expectedly! it 'result is details' do

    queue = 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
  23. We can write asynchronous functions. We can support the WebSocket.

    We can write test codes for asynchronous functions.
  24. Let’s Play Async!

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