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

More Decks by shigeru. nakajima

Other Decks in Technology


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

    to support WebSocket. I was confusing to test asynchronous functions with RSpec.
  2. 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.
  3. 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?
  4. 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.
  5. 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).
  6. Ruby has EventMachine The EventMachine gem provides event loop model

    architecture. “faye-websocket-ruby” or other gems for WebSocket use it.
  7. 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.
  8. 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.
  9. 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
  10. 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
  11. RSpec `async_func` Does this spec success? it 'is expected answer'

    do async_func do |result| expect(result).to eq('expected answer') end end
  12. 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
  13. 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
  14. How can we fail this spec? An important thing is

    “Assertions should run in the RSpec thread”. Ruby has `Queue` to wait other threads.
  15. 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
  16. We can write asynchronous functions. We can support the WebSocket.

    We can write test codes for asynchronous functions.
  17. 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