Lessons learned in Software Development (2) – Abstraction

Long ago during our weekly tech meeting @ Therap, we had this intriguing discussion, “What is the most crucial skill for a software developer?”. It was quite interesting to see how different people at different skill level had different opinion about the topic. I remember my answer was “Algorithm” (Back then I was into solving ACM style problem). Other inputs were “Language”, “Communication”, “Optimization”, “Clean Code” etc. However, we all agreed finally that it was “Design skills” that matters most.

My view on this topic also changed from time to time as I grew older and played different roles. I tried to master java (with two java certification under my belt) when I thought language was the most important, I memorized all design patterns from GOF books and tried to apply them when I thought design was the most important one.

Well now a days, I feel like I got the bigger picture. I think it is the power of “Abstraction” that can turn you from a good Software engineer to a Great one. You can find the importance of abstraction everywhere. When we were tought object oriented design at our schools, we first relate the word “Abstraction” as one of the three attributes of Object Oriented Programming. Well, it has much bigger meaning than that. Abstraction is not only part of  detailed design or Object design, Its the most important part when you want to architect big systems.

When I started my career, I used to get overwhelmed by big problems, big tasks, big projects. I tried to address them all together and made a mess of things. Now a days, I try to apply abstraction in such scenarios. Whenever I get big problems, I try to divide them into smaller pieces. “Divide and conquer”–Thats the trick. If you can abstract away the smaller problems and focus on the big picture, at the end of the day, you are going to achieve the big goal. If you are working in a team, you can put your team members to work with each of the abstractions.

I am not going to talk about the Abstraction @ code level. There are enough materials on them. But here is the key, If you can abstract away the components/responsibility/code, you will always be safe from big disaster and achieve the bigger goal.

Automate SIP Flow testing with Sipper

If you are into VOIP development and want to automate your sip call flows for testing, you might be interested in this project SIPr.

I have been involved in a project for a few months now which provides an advanced callcenter service leveraging the Broadworks Platform. Like any callcenter, the customer calls the CallCenter , Callcenter Queues the Call and Selects an agent and delivers the Call to the Agent. The agent is a registered user of Broadworks.

I have been looking for the right tool to automate the SIP Call Flows. After playing a while with sippr, I end up writing these test cases:

The CUSTOMER controller:
It justs sends the INVITE to a broadworks number which is the QUEUE. After the call gets received, it waits and sends BYE to terminate the call.

require 'sip_test_driver_controller'

class CustomerController < SIP::SipTestDriverController

  transaction_usage :use_transactions=>false
  start_on_load false

  def initialize
    logd('Controller created')
  end

  def on_provisional_res(session)
  end

  def start
    session = create_udp_session(SipperConfigurator[:DefaultRIP], SipperConfigurator[:DefaultRP])
    session.request_with('INVITE', 'sip:sajidqueue1@bwas.broadworkslab.com')
    puts 'Sending INVITE Dest: ',  'sip:sajidqueue1@bwas.broadworkslab.com';
 end

 def on_invite(session)
 puts 'customer got re-invite : '
 session.respond_with(200)
 session.schedule_timer_for("send 200!", 15000)
 end

 def on_timer(session , task)
 puts 'sending bye'
 session.request_with('BYE')
 session.invalidate(true)
 end

 def on_success_res(session)
 puts 'customer ---> on_success_res'
 session.request_with('ACK')
 end
end

Here is the Agent Controller:
It registers with Broadworks and waits for calls from the queue and receives it after 10 sec Ring and then waits for the BYE from the customer.

require 'sip_test_driver_controller'

class AgentController < SIP::SipTestDriverController

  transaction_usage :use_transactions=>false

  # change the directive below to true to start after loading.
  start_on_load false

  def specified_transport
    [SipperConfigurator[:LocalSipperIP],SipperConfigurator[:LocalSipperPort][1]]
  end   

  def initialize
    logd('Controller created')
  end

  def on_invite(session)
    
 
    if !session['invite']
      puts 'Agent got call from Queue '
      session.respond_with(100)
      session.respond_with(180)
      session['invite']=1
      session.schedule_timer_for("send 200!", 5000)
    else
      puts 'agent got re-invite'
      session.respond_with(200)
    end
    
  end
  
  def on_timer(session , task) 
    puts 'Agent is going to accept the call from customer!', task
    session.respond_with(200)
  end

  def on_bye(session)
    puts 'Agent received BYE from Customer!'
    session.respond_with(200)
    session.invalidate(true)
    session.flow_completed_for("CustomerAgentTest")
  end

  def on_provisional_res(session)
  end

  def start
    contact = SipperConfigurator[:LocalSipperIP]+":"
    r = Request.create_initial("REGISTER", "sip:" + "bwas.broadworkslab.com'",
         :from=>"sip:2401120075@bwas.dhaka.vantage.com", :to=>"sip:2401120075@bwas.broadworkslab.com'",
         :contact=>"sip:sajidagent1@"+ SipperConfigurator[:LocalSipperIP]+":"+SipperConfigurator[:LocalSipperPort][1].to_s(),
         :expires=>"300",
         :cseq=>"1 REGISTER",
         :p_session_record=>"msg-info")
    r.contact.q="0.9"       
    session = create_udp_session(SipperConfigurator[:DefaultRIP], SipperConfigurator[:DefaultRP])
    #session.create_register_request('sip:bwas.dhaka.vantage.com', '<sip:sajidagent1@bwas.broadworkslab.com'>')
    session.send(r)    
    #print "Sent a new REGISTER from "+name+"\n"
  end
  
  def on_success_res_for_register(s)
    s.invalidate(true);
  end

  def on_success_res(session)
    puts 'on_success_res->'
  end

  def on_ack(session)
  end

  def on_failure_res(session)
    if session.iresponse.code == 401
     r = session.create_request_with_response_to_challenge(session.iresponse.www_authenticate, false,"a", "b")     
     session.send r
     session[:auth] = r.authorization
    end
  end
end

And Here is the Test Case that runs both controller and performs Validation/Assertion for the call flow.

$:.unshift File.join(ENV['SIPPER_HOME'],'sipper_test')
require 'driven_sip_test_case'

class CustomerAgentTest < DrivenSipTestCase
  def setup
    super
    SipperConfigurator&#91;:SessionRecord&#93;='msg-info'
    puts '------------------------------------'
  end
  
  def test_case1
    start_named_controller_non_blocking("AgentController")
    sleep 1
    start_named_controller_non_blocking("CustomerController")    

    #it waits for Agent & Customer flow complete. The agent controller triggers the completion with session.flow_completed_for invocation. 
    wait_for_signaling()
            
    self.expected_flow = &#91;'< INVITE', '> 100', '> 180', '> 200', '< ACK ' , '< INVITE', '> 200', '< ACK',  '< INVITE', '> 200', '< ACK' , '< BYE', '> 200']
    verify_call_flow(:in,0)

    self.expected_flow = ['> INVITE', '< 100 {0,}', '< 200', '> ACK', '< INVITE', '> 200', '< ACK', '< INVITE', '> 200', '< ACK', '> BYE'  ]
    verify_call_flow(:out, 1)   
    puts "done tests"
  end
end

Ain’t this cool! Just a few lines of Code and you get a test case with Both UAC & UAS simulation!. I wish the Sipper Guys posted some more examples. We are planning to integrate this testcases with Watir & FunFX to automate the Full VOICE + WEB Mash up.