Anatomy of a Gem: Bane

36 %
64 %
Information about Anatomy of a Gem: Bane
Technology

Published on March 20, 2014

Author: etldan

Source: slideshare.net

Description

Inside the design decisions of Bane, a test harness for sockets. This talk discusses the key design decisions of Bane, presents some code, and looks at some of Bane's automated tests.

Anatomy of a Ruby Gem Bane !A test harness for server connections. March 19, 2014 ! Daniel Wellman @wellman dan@danielwellman.com

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.)

Systems Talk to Others Our Application FacebookGoogle Authentication Internal domain services Payment Processors

Sockets e.g. localhost:3000

Our Application Stock Quote Server GOOG Price: $465.87 Normal Response

Eventually Some System Will Behave Unexpectedly

Our Application Stock Quote Server GOOG Nobody Home

Our Application Stock Quote Server GOOG ... zzz ... No Response

Our Application Stock Quote Server GOOG !? Unexpected Response

So What? Our Application Stock Quote Server GOOG ... zzz ...

Photo by Ed Schipul

Our Application Bane GOOG not listening ... zzz ... Use Bane!

>  gem  install  bane Installation

Demo

Bane’s Goal: ! Have the common behaviors at your fingertips.

Design Strategy ! Don’t require any additional gems, so we can easily run anywhere

Behaviors

My Goal: ! I don’t want to write my own server to get this project started

GServer ! (class in the Ruby standard library)

Any kind of protocol, from HTTP to SMTP to something custom

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)
 end
 end
 
 server = TimeServer.new
 server.start

Great! I want to make some behaviors!

class FixedResponse < GServer
 
 def serve(io)
 io.write “Hello, World!”
 end
 
 end Subclass? class NeverRespond < GServer
 
 def serve(io)
 # ...
 end
 
 end class RandomResponse < GServer
 
 def serve(io)
 # ...
 end
 
 end

I’d prefer not

class FixedResponse < GServer
 
 def serve(io)
 io.write “Hello, World!”
 end
 
 end Testing?

Start a Server for Every Test? or Test a Private Method?

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

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
 end
 
 end • Coupled to implementation

Test through the object’s public interface

Delegate! BehaviorServer FixedResponse has a class BehaviorServer < GServer
 
 def initialize(port, behavior, host)
 super(port, host)
 @behavior = behavior
 # ...
 end
 
 def serve(io)
 @behavior.serve(io)
 end
 end class FixedResponse 
 def serve(io)
 io.write "Hello, world!"
 end
 end
 server = BehaviorServer.new(3000, FixedResponse.new, '127.0.0.1')

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
 end
 
 end

But how do we know the whole thing works?

TDD Loop From Freeman & Pryce,“Growing Object-Oriented Software: Guided by Tests”

Acceptance Tests class 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

Acceptance Test Helpers def run_server_with(port, behavior, &block)
 # ...
 end
 
 def with_response_from(port)
 begin
 connection = TCPSocket.new "localhost", port
 yield connection.read
 ensure
 connection.close if connection
 end
 end

Write Tests in the Language of the Problem Domain

This is almost the production code… class FixedResponse
 
 def serve(io)
 io.write “Hello, World!”
 end
 
 end

Programmatic Use require 'bane'
 
 include Bane
 behavior = Behaviors::FixedResponse.new( message: "Shall we play a game?”) 
 launcher = Launcher.new([ BehaviorServer.new(3000, behavior)])
 launcher.start

# 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]
 end
 end

More Acceptance Tests def test_serves_http_requests
 run_server_with(TEST_PORT, HttpRefuseAllCredentials) do
 assert_match /401/, status_returned_from( "http://localhost:#{TEST_PORT}/url")
 end
 end def status_returned_from(uri)
 begin
 open(uri).read
 rescue OpenURI::HTTPError => e
 return e.message
 end
 flunk "Should have refused access"
 end

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)
 end
 end


Close Immediately # Closes the connection immediately # after a connection is made.
 class CloseImmediately
 def serve(io)
 # do nothing
 end
 end


Echo Response class EchoResponse
 def serve(io)
 while(input = io.gets)
 io.write(input)
 end 
 io.close
 end
 end


NeverRespond class NeverRespond
 def serve(io)
 sleep
 end
 end

NeverRespond class NeverRespond
 def serve(io)
 while !io.closed?
 sleep 1
 end
 end
 end

Photo by Sean T. Allen

New Behavior: Server is Not Listening

Socket Lifecycle 1. create 2. bind 3. listen 4. accept 5. close

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

How do we fit this into our GServer-based code?

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)
 end
 end
 
 server = TimeServer.new
 server.start X It’s too late in the socket lifecycle!

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

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?

Two Groups to Name • NeverRespond • CloseImmediately • FixedResponse • EchoResponse • RandomResponse • … • NeverListen • FullListenQueue • BehaviorServer

http://github.com/danielwellman/bane Bane dan@danielwellman.com Twitter: @wellman

Add a comment

Related presentations

Presentación que realice en el Evento Nacional de Gobierno Abierto, realizado los ...

In this presentation we will describe our experience developing with a highly dyna...

Presentation to the LITA Forum 7th November 2014 Albuquerque, NM

Un recorrido por los cambios que nos generará el wearabletech en el futuro

Um paralelo entre as novidades & mercado em Wearable Computing e Tecnologias Assis...

Microsoft finally joins the smartwatch and fitness tracker game by introducing the...

Related pages

Anatomy of a Gem: Bane - Technology - documents.mx

1. Anatomy of a Ruby Gem Bane !A test harness for server connections. March 19, 2014 ! Daniel Wellman @wellman dan@ ...
Read more

Backbiting: Bane of Team Work in Schools - Technology

BACKBITING AND GOSSIPING BANE OF TEAMWORK AND SUCCESS IN SCHOOLS Payam Shoghi http://payamshoghi.com/ 2. ... Anatomy of a Gem: Bane.
Read more

Bane - Community - Diablo III

New to Diablo III? Try Free Now. Home; Game Guide; Rankings; ... 70 Bane. 70 Zajjil 70 DUDIDU. 40 ... Anatomy: 0% Gold Find; 1% ...
Read more

Poison | Definition of Poison by Merriam-Webster

bane, toxic, toxin, venom ... Poison and later potion both came into English from French. 2 poison play . verb poi·son . Definition of poison for Students
Read more

Batman: The Animated Series: Make 'Em Laugh - TV.com

... Make 'Em Laugh: ... Bane. Contributors Become a contributor. ... Grey's Anatomy The Room Where It Happens. NEW. ABC Rosewood ...
Read more

Crossguard lightsaber | Wookieepedia | Fandom powered by Wikia

The crossguard lightsaber was an ancient lightsaber design that dated back to the Great Scourge of ... Cad Bane; Lumiya; Good articles; Comprehensive articles;
Read more

S K Forex Marol - etrade girokonto bankleitzahl

s k forex marol Rekursive und Frau Pips ist Japanisch von Bestellungen, die mit binären Optionen Leben; Membaca Tanda. s k forex marol Vielen Dank für ...
Read more

Sockets - UORoleplay.com

For this example we have an Iron Platemail Tunic with 5 available sockets. 2) Our gem is a "Mythic Amethyst" witch will add a +6 Defense Change to any ...
Read more

Batman: The Animated Series: Almost Got 'Im - TV.com

Almost Got 'Im more. Contributors Become a contributor. Become a contributor; TV Listings Full Listings. 8:00 pm Kevin Can Wait The Fantastic ...
Read more