0
Sunday, December 16, 12
Lessons Learned Using               and Testing with Twilio               Tips, Tricks, and Best PracticesSunday, December...
How Test Driven               Design started the               Robot Apocalypse!               ... And how its not my faul...
Customer Call SystemSunday, December 16, 12
Wanted New Efficiencies               1. Connecting customers to the same agent;                  developing a personal rel...
Current Provider...               1. Offered little to no real-time integration.               2. Was unable or unwilling ...
Sunday, December 16, 12
Twilio...               1. A REST-ful API to make and manipulate calls                  and their associated data.        ...
[censored client]Sunday, December 16, 12
Sunday, December 16, 12
Call Flows               1. A user can click-to-call a target; the users                  phone is called first and is then...
DemoSunday, December 16, 12
Excellent Documentation               https://twilio.com/docsSunday, December 16, 12
Productizing Twilio               http://kalzumeus.com/2011/12/19/productizing-twilio-applications/Sunday, December 16, 12
Treat TwiML as Your               View               We use Builder to generate XMLSunday, December 16, 12
TWiML Builder View               xml.instruct!               xml.Response do                 xml.Say Hello. Are you       ...
Port-Forward Callbacks               To Development               Set it up yourself or use:               localtunnel htt...
Model Calls AND               Conversations               We made heavy use of state_machine gem               https://git...
Wheres the       Robopocalypse?Sunday, December 16, 12
TestingSunday, December 16, 12
Deprecated SandboxSunday, December 16, 12
Test Account               Like payment providers, it allows you to make               dummy calls, with specific phone num...
Record responses with               VCR               Speeds up test suite.               https://github.com/myronmarston/...
Conversations make for               Messy State MachinesSunday, December 16, 12
Sunday, December 16, 12
Sunday, December 16, 12
On top of all that, we               were still figuring it out!Sunday, December 16, 12
Changes would blow                          away functionality!Sunday, December 16, 12
Testing became               Manual Labor               Our poor PM constantly clicking through               scenarios.Su...
Phonio               Made use of Twilio JS Client to provide multiple               numbers backed by another Twilio accou...
How could we automate               this?               As part of the build and continuous integration.Sunday, December 1...
Sunday, December 16, 12
I didnt knowSunday, December 16, 12
Gaming Bots               Scripts that would act as other players to in               networked games.Sunday, December 16,...
Capybara               RSpec and Cucumber features use it to script a               user going through your application.Su...
Remote Host               Capybara.run_server = false               Capybara.app_host = http://staging.cyberdyne.com      ...
Supports Multi-"Users"               using_session :ahnold do                 visit /signin                 click Terminat...
How to do the same for               phones?Sunday, December 16, 12
Sunday, December 16, 12
Were are NOT testing               Twilio.Sunday, December 16, 12
We are testing WITH               Twilio.               An important distinction!Sunday, December 16, 12
The Pieces to a Solution               were lying around.Sunday, December 16, 12
What does it look like?                                                         Twilio Account                          Ca...
What does it look like?                                                         Twilio Account                          Ca...
What does it look like?                                                         Twilio Account                          Ca...
What does it look like?                                                         Twilio Account                          Ca...
What does it look like?                                                                 Twilio Account                    ...
What does it look like?                                                                 Twilio Account                    ...
What does it look like?                                                                 Twilio Account                    ...
What does it look like?                                                                       Twilio Account              ...
WOPR         http://github.com/ZestFinance/wopr         http://github.com/carbonfive/woprSunday, December 16, 12
A @wopr of a Feature               @javascript @wopr               Feature: Outbound Call                 A user can enter...
Setup in Cucumber               require wopr/cucumber               require File.join(File.dirname(__FILE__), .., .., stag...
Creat identified Bots               Wopr::Bot.create(:ahnold,                          email: rudy+ahnold@carbonfive.com,  ...
The Goal: Simpler Code               Then /^the users phone is called$/ do                 bot(:ahnold).should be_on_a_cal...
How does it work?Sunday, December 16, 12
Stole a LOT from               Capybara               Particularly threading code not to block running               specs...
Sinatra App               module Wopr                 class TwilioCallbackServer < Sinatra::Base                   VERIFIC...
Mount on Rack               def run_server(port)                 require rack/handler/thin                 Thin::Logging.s...
Launch in a thread               def boot(port=Wopr.twilio_server_port)                 @port = port                 unles...
Server manages Calls               post /calls do                 if(call = Call.find_by_sid(params[:CallSid]))           ...
<Say /> Keep-alive               xml.instruct!               xml.Response do                 xml.Say(loop: 0) do          ...
Bots can examine Calls               module Wopr                 class Bot                          # ...                 ...
Handle Asynchronicity                          def eventually(seconds=Wopr.default_wait_time)                            s...
Bot makes calls w/ Call               module Wopr                 class Bot                          # ...                ...
TwilioClientService               require twilio-ruby               module Wopr                 class TwilioService       ...
Do we KNOW theyre               TALKING to each other?               Its possible the system made two phone calls, but    ...
A Solution: Play and               Detect Dial Tones!               One bot starts to <Gather> digits, the other          ...
Call Gather & Play               module Wopr                 class Call                          # ...                    ...
Redirect Calls to TWiML          module Wopr            class TwilioService                  # ...                  def pl...
Prepare to <Gather />                 module Wopr                   class TwilioCallbackServer < Sinatra::Base            ...
<Play digits="..." />               module Wopr                 class TwilioCallbackServer < Sinatra::Base                ...
Gathered Digits Posted               module Wopr                 class TwilioCallbackServer < Sinatra::Base               ...
Again Asynchronous!               module Wopr                 class Bot                          # ...                    ...
Dial-Tone not a Perfect               Solution.               What if tones are used to trigger actions? And              ...
<Record> and SOX               Retrieve the recording, digest with SOX audio               library.Sunday, December 16, 12
More Capybara Tie-ins?               Given /^a user is logged in$/ do                 bot(:ahnold).log_in               en...
How far can we take it?Sunday, December 16, 12
[pic]Sunday, December 16, 12
Sample Use of wopr               http://github.com/carbonfive/cyberdyne-systemsSunday, December 16, 12
Questions?               rudy@carbonfive.com               @rudy on TwitterSunday, December 16, 12
Hasta la vista, baby!Sunday, December 16, 12
Upcoming SlideShare
Loading in...5
×

How Test Driven Development started the Robot Apocalypse; Lessons learned using Twilio for Telephony

6,576

Published on

When a client approached us to build a call-center using the Twilio API we didn't realize how far we would push our "test-driven" philosophy. Join us as we explain how easy it was to go from simply using a library, to regularly running bots to actually dial our app to ensure its integrity.

Published in: Technology
1 Comment
1 Like
Statistics
Notes
No Downloads
Views
Total Views
6,576
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
9
Comments
1
Likes
1
Embeds 0
No embeds

No notes for slide

Transcript of "How Test Driven Development started the Robot Apocalypse; Lessons learned using Twilio for Telephony"

  1. 1. Sunday, December 16, 12
  2. 2. Lessons Learned Using and Testing with Twilio Tips, Tricks, and Best PracticesSunday, December 16, 12
  3. 3. How Test Driven Design started the Robot Apocalypse! ... And how its not my fault!Sunday, December 16, 12
  4. 4. Customer Call SystemSunday, December 16, 12
  5. 5. Wanted New Efficiencies 1. Connecting customers to the same agent; developing a personal relationship. 2. Automatically popping up the customer record on the agents browser. 3. Collect call metrics and tie into other datapoints.Sunday, December 16, 12
  6. 6. Current Provider... 1. Offered little to no real-time integration. 2. Was unable or unwilling to customize solution. 3. Was expensive.Sunday, December 16, 12
  7. 7. Sunday, December 16, 12
  8. 8. Twilio... 1. A REST-ful API to make and manipulate calls and their associated data. 2. Makes real-time callbacks over HTTP to your application about incoming and ongoing calls. 3. Inexpensive: $1 per number, $0.01 per call legSunday, December 16, 12
  9. 9. [censored client]Sunday, December 16, 12
  10. 10. Sunday, December 16, 12
  11. 11. Call Flows 1. A user can click-to-call a target; the users phone is called first and is then connected to the target. 2. A user can enter any number and call it; the users phone is called first and is then connected to the number. 3. If a target calls the mainline, they are immediately connected to a user. 4. Unknown callers to the mainline are placed on a hold queue; any user can handle them.Sunday, December 16, 12
  12. 12. DemoSunday, December 16, 12
  13. 13. Excellent Documentation https://twilio.com/docsSunday, December 16, 12
  14. 14. Productizing Twilio http://kalzumeus.com/2011/12/19/productizing-twilio-applications/Sunday, December 16, 12
  15. 15. Treat TwiML as Your View We use Builder to generate XMLSunday, December 16, 12
  16. 16. TWiML Builder View xml.instruct! xml.Response do xml.Say Hello. Are you xml.Play https://s3.amazonaws.com/CarbonFive/placeholder.wav xml.Say If not, please hold. xml.Play https://s3.amazonaws.com/CarbonFive/sign_off.wav xml.Enqueue(action: goodbye_twilio_call_path(@call), waitUrl: hold_twilio_call_path(@call)) do xml.text! hold end endSunday, December 16, 12
  17. 17. Port-Forward Callbacks To Development Set it up yourself or use: localtunnel http://localtunnel.com forward http://forwardhq.comSunday, December 16, 12
  18. 18. Model Calls AND Conversations We made heavy use of state_machine gem https://github.com/pluginaweek/state_machineSunday, December 16, 12
  19. 19. Wheres the Robopocalypse?Sunday, December 16, 12
  20. 20. TestingSunday, December 16, 12
  21. 21. Deprecated SandboxSunday, December 16, 12
  22. 22. Test Account Like payment providers, it allows you to make dummy calls, with specific phone numbers resulting in specific responses.Sunday, December 16, 12
  23. 23. Record responses with VCR Speeds up test suite. https://github.com/myronmarston/vcrSunday, December 16, 12
  24. 24. Conversations make for Messy State MachinesSunday, December 16, 12
  25. 25. Sunday, December 16, 12
  26. 26. Sunday, December 16, 12
  27. 27. On top of all that, we were still figuring it out!Sunday, December 16, 12
  28. 28. Changes would blow away functionality!Sunday, December 16, 12
  29. 29. Testing became Manual Labor Our poor PM constantly clicking through scenarios.Sunday, December 16, 12
  30. 30. Phonio Made use of Twilio JS Client to provide multiple numbers backed by another Twilio account.Sunday, December 16, 12
  31. 31. How could we automate this? As part of the build and continuous integration.Sunday, December 16, 12
  32. 32. Sunday, December 16, 12
  33. 33. I didnt knowSunday, December 16, 12
  34. 34. Gaming Bots Scripts that would act as other players to in networked games.Sunday, December 16, 12
  35. 35. Capybara RSpec and Cucumber features use it to script a user going through your application.Sunday, December 16, 12
  36. 36. Remote Host Capybara.run_server = false Capybara.app_host = http://staging.cyberdyne.com Alternatively require capybara/cucumber require capybara/spec/test_app Capybara.app = TestApp Capybara.app_host = http://staging.cyberdyne.comSunday, December 16, 12
  37. 37. Supports Multi-"Users" using_session :ahnold do visit /signin click Terminate, within: #sarah_connor end using_session :robert do visit /sightings/new check have_you_seen_this_boy click Submit endSunday, December 16, 12
  38. 38. How to do the same for phones?Sunday, December 16, 12
  39. 39. Sunday, December 16, 12
  40. 40. Were are NOT testing Twilio.Sunday, December 16, 12
  41. 41. We are testing WITH Twilio. An important distinction!Sunday, December 16, 12
  42. 42. The Pieces to a Solution were lying around.Sunday, December 16, 12
  43. 43. What does it look like? Twilio Account Capybara The App Browser of the App Rspec/ CucumberSunday, December 16, 12
  44. 44. What does it look like? Twilio Account Capybara The App Browser of the App Rspec/ Cucumber Twilio Account of the BotsSunday, December 16, 12
  45. 45. What does it look like? Twilio Account Capybara The App Browser of the App Rspec/ Cucumber Sinatra Twilio Account of the BotsSunday, December 16, 12
  46. 46. What does it look like? Twilio Account Capybara The App Browser of the App Rspec/ Cucumber Sinatra Twilio Account of the BotsSunday, December 16, 12
  47. 47. What does it look like? Twilio Account Capybara The App Browser of the App Rspec/ Cucumber Sinatra Twilio Account of the Bots CallsSunday, December 16, 12
  48. 48. What does it look like? Twilio Account Capybara The App Browser of the App Rspec/ Cucumber Sinatra Twilio Account of the Bots Bots CallsSunday, December 16, 12
  49. 49. What does it look like? Twilio Account Capybara The App Browser of the App Rspec/ Cucumber Sinatra Twilio Account of the Bots Bots CallsSunday, December 16, 12
  50. 50. What does it look like? Twilio Account Capybara The App Browser of the App Rspec/ Cucumber Sinatra Twilio Account of the Bots Bots Calls Twilio ClientSunday, December 16, 12
  51. 51. WOPR http://github.com/ZestFinance/wopr http://github.com/carbonfive/woprSunday, December 16, 12
  52. 52. A @wopr of a Feature @javascript @wopr Feature: Outbound Call A user can enter phone number so that they can call it Scenario: Simple Session Given a user is logged in And the user enters a phone number And the user clicks the Call button Then the users phone is called And the phone number is called And they are speaking to each otherSunday, December 16, 12
  53. 53. Setup in Cucumber require wopr/cucumber require File.join(File.dirname(__FILE__), .., .., staging) Wopr.configure do |config| config.twilio_server_port = 4000 config.twilio_callback_host = http://rudyjahchan.fwd.wf config.twilio_account_sid = TWILIO_ACCOUNT_SID config.twilio_auth_token = TWILIO_AUTH_TOKEN end require File.join(File.dirname(__FILE__), .., .., bots) Wopr::TwilioService.new.update_callbacks([ Wopr::Bot[:ahnold].phone_number, Wopr::Bot[:kyle].phone_number]) Wopr::TwilioCallbackServer.bootSunday, December 16, 12
  54. 54. Creat identified Bots Wopr::Bot.create(:ahnold, email: rudy+ahnold@carbonfive.com, password: n07@r3@1p@$$w0rd, phone_number: 5558675309) Wopr::Bot.create(:kyle, phone_number: 5557779311) Wopr::Bot.create(:sarah, phone_number: 9006492568)Sunday, December 16, 12
  55. 55. The Goal: Simpler Code Then /^the users phone is called$/ do bot(:ahnold).should be_on_a_call end Then /^the users phone is not called$/ do bot(:ahnold).should_not be_on_a_call end Then /^the phone number is called$/ do bot(:kyle).should be_on_a_call end Then /^they are speaking to each other$/ do bot(:kyle).should be_on_a_call_with(bot(:ahnold)) end Given /^an unknown caller dials the main line$/ do bot(:kyle).make_a_call_to(CYBERDYNE_STAGING_PHONE_NUMBER) endSunday, December 16, 12
  56. 56. How does it work?Sunday, December 16, 12
  57. 57. Stole a LOT from Capybara Particularly threading code not to block running specs.Sunday, December 16, 12
  58. 58. Sinatra App module Wopr class TwilioCallbackServer < Sinatra::Base VERIFICATION_PHRASE = SHALL WE PLAY A GAME? set :views, File.join(File.dirname(__FILE__), templates) get /__identify__ do [200, {}, VERIFICATION_PHRASE] end post /calls do # ... end # ... end endSunday, December 16, 12
  59. 59. Mount on Rack def run_server(port) require rack/handler/thin Thin::Logging.silent = true Rack::Handler::Thin.run(self, Port: port) rescue LoadError require rack/handler/webrick Rack::Handler::WEBrick.run(self, Port: port, AccessLog: [], Logger: WEBrick::Log::new(nil, 0)) endSunday, December 16, 12
  60. 60. Launch in a thread def boot(port=Wopr.twilio_server_port) @port = port unless responsive? @server_thread = Thread.new { run_server(@port) } end Timeout.timeout(60) { @server_thread.join(0.1) until responsive? } end def responsive? return false if @server_thread && @server_thread.join(0) res = Net::HTTP.start(127.0.0.1, @port) do |http| http.get(/__identify__) end if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection) return res.body == VERIFICATION_PHRASE end rescue Errno::ECONNREFUSED, Errno::EBADF return false endSunday, December 16, 12
  61. 61. Server manages Calls post /calls do if(call = Call.find_by_sid(params[:CallSid])) call.update params else Call.create(params) end builder :default endSunday, December 16, 12
  62. 62. <Say /> Keep-alive xml.instruct! xml.Response do xml.Say(loop: 0) do xml.text! <<GIBBERISH Yorn desh born, der ritt de gitt der gue, Orn desh, dee born desh, de umn bork! bork! bork! GIBBERISH end endSunday, December 16, 12
  63. 63. Bots can examine Calls module Wopr class Bot # ... def current_call Call.find_all_by_number(phone_number).select{|call| call.status != completed}.last end def on_a_call? wait_until do current_call end end # ... end endSunday, December 16, 12
  64. 64. Handle Asynchronicity def eventually(seconds=Wopr.default_wait_time) start_time = Time.now begin yield rescue => e raise e if (Time.now - start_time) >= seconds sleep 1 retry end end def wait_until(seconds=Wopr.default_wait_time) eventually(seconds) do result = yield return result if result raise ConditionNotMetError end rescue ConditionNotMetError return false endSunday, December 16, 12
  65. 65. Bot makes calls w/ Call module Wopr class Bot # ... def make_a_call_to(phone_number) Call.make(from: self.phone_number, to: phone_number) end # ... end end module Wopr class Call class << self def make(options) TwilioService.new.make(options) end # ...Sunday, December 16, 12
  66. 66. TwilioClientService require twilio-ruby module Wopr class TwilioService def initialize @twilio_client = Twilio::REST::Client.new( Wopr.twilio_account_sid, Wopr.twilio_auth_token ) end def make(options) calls.create(options.merge( url: "#{Wopr.twilio_callback_host}/calls" )) end def hangup(sid) call(sid).hangup end # ...Sunday, December 16, 12
  67. 67. Do we KNOW theyre TALKING to each other? Its possible the system made two phone calls, but they’re not with each other.Sunday, December 16, 12
  68. 68. A Solution: Play and Detect Dial Tones! One bot starts to <Gather> digits, the other <Plays> them, and we confirm they receive it.Sunday, December 16, 12
  69. 69. Call Gather & Play module Wopr class Call # ... def play(digits) TwilioService.new.play sid, digits end def gather TwilioService.new.gather sid end # ... end endSunday, December 16, 12
  70. 70. Redirect Calls to TWiML module Wopr class TwilioService # ... def play(sid, digits) call(sid).redirect_to( "#{Wopr.twilio_callback_host}/calls/#{sid}/play?digits=#{digits}" ) end def gather(sid) call(sid).redirect_to( "#{Wopr.twilio_callback_host}/calls/#{sid}/gather" ) end # ... end endSunday, December 16, 12
  71. 71. Prepare to <Gather /> module Wopr class TwilioCallbackServer < Sinatra::Base # ... post /calls/:sid/gather do builder :gather, locals: { sid: params[:sid] } end # ... end end gather.builder xml.instruct! xml.Response do xml.Gather( timeout: "60", action: "#{Wopr.twilio_callback_host}/calls/#{sid}/gathered", numDigits: "4") endSunday, December 16, 12
  72. 72. <Play digits="..." /> module Wopr class TwilioCallbackServer < Sinatra::Base # ... post /calls/:sid/play do builder :play, locals: { sid: params[:sid], digits: params[:digits] } end # ... end end play.builder xml.instruct! xml.Response do xml.Play(digits: digits) xml.Pause(length: 10) endSunday, December 16, 12
  73. 73. Gathered Digits Posted module Wopr class TwilioCallbackServer < Sinatra::Base # ... post /calls/:sid/gathered do if(call = Call.find_by_sid(params[:sid])) call.gathered params[:Digits] end builder :default end # ... end endSunday, December 16, 12
  74. 74. Again Asynchronous! module Wopr class Bot # ... def on_a_call_with?(another_bot) current_call.gather sleep 1 another_bot.current_call.play 6661 wait_until do current_call.gathered_digits.last == 6661 end end # ... end endSunday, December 16, 12
  75. 75. Dial-Tone not a Perfect Solution. What if tones are used to trigger actions? And how do we confirm audio FILE playback?Sunday, December 16, 12
  76. 76. <Record> and SOX Retrieve the recording, digest with SOX audio library.Sunday, December 16, 12
  77. 77. More Capybara Tie-ins? Given /^a user is logged in$/ do bot(:ahnold).log_in end Given /^the user enters a phone number$/ do bot(:ahnold).within(div#call) do fill_in number, with: bot(:kyle).phone_number end endSunday, December 16, 12
  78. 78. How far can we take it?Sunday, December 16, 12
  79. 79. [pic]Sunday, December 16, 12
  80. 80. Sample Use of wopr http://github.com/carbonfive/cyberdyne-systemsSunday, December 16, 12
  81. 81. Questions? rudy@carbonfive.com @rudy on TwitterSunday, December 16, 12
  82. 82. Hasta la vista, baby!Sunday, December 16, 12
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×