【问题标题】:Connecting to background EventMachine application for unit testing连接到后台 EventMachine 应用程序进行单元测试
【发布时间】:2015-08-01 04:29:24
【问题描述】:

我正在使用EventMachine 编写一个通过套接字进行通信的无头Ruby 应用程序。我想为这个应用程序编写一些单元测试。这意味着我的 Ruby 测试脚本需要在后台启动该应用程序,与它执行套接字通信,然后关闭该进程。

此代码失败。套接字连接被拒绝。

require 'socket'
PORT = 7331
CMD = File.expand_path("../../bin/rb3jay",__FILE__)
@thread = Thread.new{ `#{CMD} -D --port #{PORT}` }
@socket = TCPSocket.open('localhost', PORT)
#=> Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 7331

如果我在尝试套接字连接之前注入 2 秒延迟,它会按预期工作:

@thread = Thread.new{ `#{CMD} -D --port #{PORT}` }
sleep 2
@socket = TCPSocket.open('localhost', PORT)

这似乎是一个严重的黑客行为。也许 2 秒对我的机器来说足够长,但在其他地方太短了。

我应该如何在后台正确启动我的 EventMachine 应用程序,并在它准备好后立即创建到它的套接字连接?

【问题讨论】:

  • 我曾考虑在我的单元测试文件中使用 EventMachine 作为客户端,而不是标准的 Ruby socket 库,但我无法弄清楚如何以某种方式将其事件循环与 MiniTest::Unit 一起使用这允许我在每次测试之前重新建立服务器和连接。

标签: ruby macos sockets eventmachine


【解决方案1】:

不确定是否有更好的方法,但我通过使用retry 解决了这个问题:

@thread = Thread.new{ `#{CMD} -D --port #{PORT}` }
begin
  @socket = TCPSocket.open('localhost', PORT)
rescue Errno::ECONNREFUSED
  sleep 0.1
  retry
end

这将无限期地尝试每秒建立连接 10 次,直到它工作为止。更强大的解决方案可能是使用计数器或计时器最终放弃,以防万一出现严重错误。

完整的测试代码如下所示:

require 'socket'
require 'minitest/autorun'

PORT = 7331
CMD = File.expand_path("../../bin/rb3jay",__FILE__)

class TestServer < MiniTest::Unit::TestCase
  def setup
    @pid = Process.spawn "#{CMD} -D --port #{PORT}"
    begin
      @socket = TCPSocket.open('localhost', PORT)
    rescue Errno::ECONNREFUSED
      sleep 0.1
      retry
    end
  end

  def teardown
    if @socket && !@socket.closed?
      @socket.puts("quit") # try for a nice shutdown
      @socket.close
    end
    Process.kill("HUP",@pid)
  end

  def test_aaa
    # my test code
  end

  def test_bbb
    # more test code
  end
end

【讨论】:

    【解决方案2】:

    问题是从主线程你无法知道Thread.new 代码块何时实际执行。通过使用sleep,您只需给它足够的时间以便执行它。

    在这种情况下,我更喜欢使用Queue,其中Thread.new 块在完成它应该做的事情后执行push(通常是nil),而线程曾经sleep,从它做一个poppop 一直等到Queue 中有可用数据。

    require 'socket'
    PORT = 7331
    CMD = File.expand_path("../../bin/rb3jay",__FILE__)
    q = Queue.new
    @thread = Thread.new do
      Process.spawn "#{CMD} -D --port #{PORT}"
      q.push(nil)
    end
    q.pop
    @socket = TCPSocket.open('localhost', PORT)
    

    但是,您可能会遇到问题,因为生成命令并不意味着服务器实际上已准备好(侦听新连接)。所以,我会尝试一种方法,让我可以更好地控制服务器的生命周期。

    require 'socket'
    PORT = 7331
    q = Queue.new
    @thread = Thread.new do
      server = TCPServer.new PORT
      q.push(nil)
    
      loop do
        client = server.accept
        client.puts "Hello !"
        client.puts "Time is #{Time.now}"
        client.close
      end
    end
    q.pop
    @socket = TCPSocket.open('localhost', PORT)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-03
      • 1970-01-01
      • 2019-01-01
      • 2012-03-21
      • 2011-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多