Anatomy of a Gem: Bane
-
Upload
daniel-wellman -
Category
Technology
-
view
264 -
download
0
description
Transcript of Anatomy of a Gem: Bane
![Page 1: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/1.jpg)
Anatomy of a Ruby Gem
Bane !A test harness for server connections.
March 19, 2014 !
Daniel Wellman @wellman
![Page 2: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/2.jpg)
About Me
• Extreme Programming and Test-Driven Development since 2000
• Ruby since 2005, Rails 1.x in 2006
• Helping teams deliver working software safely and reliably for eight years by pairing and coaching (TDD, refactoring, agile development practices, etc.)
![Page 3: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/3.jpg)
Systems Talk to Others
Our Application
FacebookGoogle Authentication
Internal domain servicesPayment
Processors
![Page 4: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/4.jpg)
Sockets
e.g. localhost:3000
![Page 5: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/5.jpg)
Our Application
Stock Quote Server
GOOG
Price: $465.87
Normal Response
![Page 6: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/6.jpg)
Eventually Some System Will Behave
Unexpectedly
![Page 7: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/7.jpg)
Our Application
Stock Quote Server
GOOG
Nobody Home
![Page 8: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/8.jpg)
Our Application
Stock Quote Server
GOOG
... zzz ...
No Response
![Page 9: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/9.jpg)
Our Application
Stock Quote Server
GOOG
!?
Unexpected Response
![Page 10: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/10.jpg)
So What?
Our Application
Stock Quote Server
GOOG
... zzz ...
![Page 11: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/11.jpg)
![Page 12: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/12.jpg)
Photo by Ed Schipul
![Page 13: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/13.jpg)
Our Application
Bane
GOOG
not listening
... zzz ...
Use Bane!
![Page 14: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/14.jpg)
> gem install bane
Installation
![Page 15: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/15.jpg)
Demo
![Page 16: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/16.jpg)
Bane’s Goal: !
Have the common behaviors at your fingertips.
![Page 17: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/17.jpg)
Design Strategy !
Don’t require any additional gems, so we can easily run
anywhere
![Page 18: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/18.jpg)
Behaviors
![Page 19: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/19.jpg)
![Page 20: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/20.jpg)
My Goal: !
I don’t want to write my own server to get this project started
![Page 21: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/21.jpg)
GServer !
(class in the Ruby standard library)
![Page 22: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/22.jpg)
Any kind of protocol, from HTTP to SMTP to
something custom
![Page 23: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/23.jpg)
require 'gserver'# # A server that returns the time in # seconds since 1970.# class TimeServer < GServer def initialize(port=10001, *args) super(port, *args) end def serve(io) io.puts(Time.now.to_s) endendserver = TimeServer.newserver.start
![Page 24: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/24.jpg)
Great! I want to make some behaviors!
![Page 25: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/25.jpg)
class FixedResponse < GServer def serve(io) io.write “Hello, World!” endend
Subclass?
class NeverRespond < GServer def serve(io) # ... endend
class RandomResponse < GServer def serve(io) # ... endend
![Page 26: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/26.jpg)
I’d prefer not
![Page 27: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/27.jpg)
class FixedResponse < GServer def serve(io) io.write “Hello, World!” endend
Testing?
![Page 28: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/28.jpg)
Start a Server for Every Test?
or Test a Private Method?
![Page 29: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/29.jpg)
class FixeResponseTest < Test::Unit::TestCase def test_sends_the_same_message_every_time server = FixedResponse.new(3000) server.start response = # connect to port 3000 and query assert_equal "Hello, World!”, response server.stop end end
Start a Server for Every Test?
• Uses real I/O • Testing GServer
Over and Over
![Page 30: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/30.jpg)
Test a Private Method?
class FixedResponseTest < Test::Unit::TestCase def test_sends_the_same_message_every_time server = FixedResponse.new(3000) # call the serve() method directly server.serve(fake_connection) assert_equal "Hello, World!”, fake_connection.string endend
• Coupled to implementation
![Page 31: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/31.jpg)
Test through the object’s public interface
![Page 32: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/32.jpg)
Delegate!
BehaviorServer FixedResponsehas a
class BehaviorServer < GServer def initialize(port, behavior, host) super(port, host) @behavior = behavior # ... end def serve(io) @behavior.serve(io) endend
class FixedResponse def serve(io) io.write "Hello, world!" end
end
server = BehaviorServer.new(3000, FixedResponse.new, '127.0.0.1')
![Page 33: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/33.jpg)
Test Behavior in Isolation
class FixedResponseTest < Test::Unit::TestCase def test_sends_the_same_message_every_time behavior = FixedResponse.new behavior.serve(fake_connection) assert_equal "Hello, World!", fake_connection.string endend
![Page 34: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/34.jpg)
But how do we know the whole thing works?
![Page 35: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/35.jpg)
TDD Loop
From Freeman & Pryce, “Growing Object-Oriented Software: Guided by Tests”
![Page 36: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/36.jpg)
Acceptance Testsclass BaneAcceptanceTest < Test::Unit::TestCase TEST_PORT = 4000 def test_uses_specified_port_and_server run_server_with(TEST_PORT, FixedResponse) do with_response_from TEST_PORT do |response| assert !response.empty? end end end
# … !end
![Page 37: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/37.jpg)
Acceptance Test Helpers
def run_server_with(port, behavior, &block) # ...enddef with_response_from(port) begin connection = TCPSocket.new "localhost", port yield connection.read ensure connection.close if connection endend
![Page 38: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/38.jpg)
Write Tests in the Language of the Problem Domain
![Page 39: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/39.jpg)
This is almost the production code…
class FixedResponse def serve(io) io.write “Hello, World!” endend
![Page 40: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/40.jpg)
Programmatic Userequire 'bane'include Bane
behavior = Behaviors::FixedResponse.new( message: "Shall we play a game?”) launcher = Launcher.new([ BehaviorServer.new(3000, behavior)])launcher.start
![Page 41: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/41.jpg)
# Sends a static response.# # Options:# - message: The response message to send. Default: "Hello, world!"class FixedResponse def initialize(options = {}) @options = {message: "Hello, world!”} .merge(options) end def serve(io) io.write @options[:message] endend
![Page 42: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/42.jpg)
More Acceptance Testsdef test_serves_http_requests run_server_with(TEST_PORT, HttpRefuseAllCredentials) do assert_match /401/, status_returned_from( "http://localhost:#{TEST_PORT}/url") endend
def status_returned_from(uri) begin open(uri).read rescue OpenURI::HTTPError => e return e.message end flunk "Should have refused access"end
![Page 43: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/43.jpg)
class HttpRefuseAllCredentials UNAUTHORIZED_RESPONSE_BODY = <<EOF<!DOCTYPE html><html>… </html>EOF def serve(io) io.gets response = NaiveHttpResponse.new( 401, "Unauthorized", “text/html", UNAUTHORIZED_RESPONSE_BODY) io.write(response.to_s) endend
![Page 44: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/44.jpg)
Close Immediately
# Closes the connection immediately # after a connection is made.class CloseImmediately def serve(io) # do nothing endend
![Page 45: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/45.jpg)
Echo Response
class EchoResponse def serve(io) while(input = io.gets) io.write(input) end io.close endend
![Page 46: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/46.jpg)
NeverRespond
class NeverRespond def serve(io) sleep endend
![Page 47: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/47.jpg)
NeverRespond
class NeverRespond def serve(io) while !io.closed? sleep 1 end endend
![Page 48: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/48.jpg)
Photo by Sean T. Allen
![Page 49: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/49.jpg)
New Behavior: Server is Not Listening
![Page 50: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/50.jpg)
Socket Lifecycle
1. create
2. bind
3. listen
4. accept
5. close
![Page 51: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/51.jpg)
Never Listen
@server = Socket.new(:INET, :STREAM) address = Socket.sockaddr_in(port, host)@server.bind(address) # Note that we never call listen
Clients that try to connect get an ECONNREFUSED error
![Page 52: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/52.jpg)
How do we fit this into our GServer-based
code?
![Page 53: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/53.jpg)
require 'gserver'# # A server that returns the time in # seconds since 1970.# class TimeServer < GServer def initialize(port=10001, *args) super(port, *args) end def serve(io) io.puts(Time.now.to_s) endendserver = TimeServer.newserver.start
X It’s too late in the socket lifecycle!
![Page 54: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/54.jpg)
class NeverListen def initialize(port, host = Services::LOCALHOST) @port = port @host = host end def start @server = Socket.new(:INET, :STREAM) address = Socket.sockaddr_in(port, host) @server.bind(address) log 'started' end def stop @server.close log 'stopped' end ! # … end
![Page 55: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/55.jpg)
Now We’re Two…
• Small server-independent behaviors that require a GServer (or something) to manage their lifecycle
• Behaviors that use low-level sockets and manage their own lifecycle
…. called what?
Services?
Behaviors?
![Page 56: Anatomy of a Gem: Bane](https://reader033.fdocuments.us/reader033/viewer/2022042606/54899716b47959ec0c8b59e6/html5/thumbnails/56.jpg)
Two Groups to Name• NeverRespond
• CloseImmediately
• FixedResponse
• EchoResponse
• RandomResponse
• …
• NeverListen
• FullListenQueue
• BehaviorServer