问题
I've written a really simple date server:
require 'socket'
s = TCPServer.new 3939
while (conn = s.accept)
Thread.new(conn) do |c|
c.print "Enter your name: "
name = c.gets.chomp
c.puts "Hi #{name}, the date is..."
c.print `date`
c.close
end
end
A user connects, a thread is spawned, they enter their name, the date is returned. Simple.
I'm wondering about how I would go abouts testing something like this in rspec. Some ideas that I've had: 1.) Use VCR to record a server connection and Timecop to freeze and return a date. 2.) Connect to the actual server in a before block. I'm not entirely sure how to do this as when I run rspec, I think it actually runs the server...or something happens that the terminal just kind of freezes and waits for something... example test code:
before
@server = TCPSever.new 3939
end
it "does something.."
conn = @server.accept
# etc
end
after
@server.close
end
3.) Stub the connection. 4.) Don't even try to test that threads are created, and just put the name request and date response into a method and test this.
I looked at puma's tests to see how they test threads: https://github.com/puma/puma/blob/master/test/test_puma_server.rb#L27
I'd really appreciate some help with this. It's just a simple exercise where I'd like to see how best to implement some tests for this kind of server. Like, simulating a user connecting and entering their name. Thank you in advance.
回答1:
I would consider organizing the server as a class with this signature:
class DateServer
# Initialize the server. If port is 0, find
# an unused port and use that.
def initialize(port = 0)
def start
def stop
def port # return the bound port
end
Your test then sets up and tears down the server:
before do
@server = Server.new
@server.start
end
after do
@server.stop
end
Since this server has no side-effects, your test will connect to it, send some stuff, and get some stuff back:
it "prints the date" do
@socket = TCPSocket.new("localhost", @server.port)
@socket.puts "Fred"
@socket.close_write
output = @socket.read
expect(output).to match /Hi Fred/
end
To check that the output contains the date, either use a regular expression. Here's an untested and probably incorrect example:
expect(output).to match /\w+ \w+ +\d+ \d+:\d+:\d+ \w+ \d+/
Or use, e.g., the timecop gem so that the test can force the time, and then check for an exact match on the date.
This server has no side-effects, but if it did, I would put the side-effects in their own objects. Let's say that the server can reboot the box when the user's name is "Barney". Instead of having the server do that directly, let it call a "rebooter" object. In your test, create a fake rebooter and give that to the server:
before do
@rebooter = double(Rebooter)
@server = Server.new(rebooter: @rebooter)
@server.start
end
and then:
"it should reboot when Barney connects" do
@rebooter.should_receive(:reboot)
@socket = TCPSocket.new("localhost", @server.port)
@socket.puts "Barney"
@socket.close_write
output = @socket.read
end
来源:https://stackoverflow.com/questions/27368345/how-to-test-a-multi-threaded-tcpserver-with-rspec