Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SOLID and TDD, Sitting in a Tree

PromptWorks
September 26, 2013

SOLID and TDD, Sitting in a Tree

Watch it online: http://confreaks.com/videos/2653-rockymountainruby2013-solid-and-tdd-sitting-in-a-tree

You’ve heard the claims or know from experience that test-driven development (TDD) produces better code. Perhaps you’ve also heard that to write better code you should be following the SOLID principles. Is there a connection between practicing TDD and writing SOLID code?

In this talk, I’ll use examples gleaned from real life to demonstrate how TDD prompts us to produce code that conforms to the SOLID principles, and how the SOLID principles are where we should turn when our tests are causing us pain. In doing so, we’ll learn what each principle really means and why it’s valuable.

Mike Nicholaides has been a Rails consultant since 2006 and has always obsessed about writing clean, clear, and concise code. He organizes the Code Retreat in Philadelphia where the focus is on learning TDD, communicating with code, and of course, having fun.

Presented by Mike Nicholaides at Rocky Mountain Ruby Conf. https://www.promptworks.com

PromptWorks

September 26, 2013
Tweet

More Decks by PromptWorks

Other Decks in Programming

Transcript

  1. describe Notifier, "#notify" do let(:message_body) do "Hi @#{notifiable_user1.name} @#{notifiable_user2.name}\ @#{

    unnotifiable_user.name }" end ! let(:message) { create(:message, body: message_body) } ! it "FB's the message's mentioned users who prefer to be FB'd" do notifiable_user1 = create(:facebook_connected_user, fb_me_on_mention: true) notifiable_user2 = create(:facebook_connected_user, fb_me_on_mention: true) unnotifiable_user = create(:facebook_connected_user, fb_me_on_mention: false) ! notifiable_user1.facebook_connection.stub(put_connections: true) notifiable_user2.facebook_connection.stub(put_connections: true) unnotifiable_user.facebook_connection.stub(put_connections: true) ! Notifier.notify(message) ! expected_message = "#{message.author.name} said #{message_body}" ! expect(notifiable_user1.facebook_connection).to have_received(:put_connections).with("me", "feed", message: expected_message) expect(notifiable_user2.facebook_connection).to have_received(:put_connections).with("me", "feed", message: expected_message) expect(unnotifiable_user.facebook_connection).not_to have_received(:put_connections) end ! it "emails the message's mentioned users who prefer to be emailed" do notifiable_user1 = create(:user, email_me_on_mention: true) notifiable_user2 = create(:user, email_me_on_mention: true) unnotifiable_user = create(:user, email_me_on_mention: false) ! MessageMailer.stub(:deliver_message) ! Notifier.notify(message) ! expected(MessageMailer).to have_received(:deliver_message).with(message, [notifiable_user1, notifiable_user2]) end ! it "SMS's the message's mentioned users who prefer to be SMS'd" do notifiable_user1 = create(:sms_user, sms_me_on_mention: true) notifiable_user2 = create(:sms_user, sms_me_on_mention: true) unnotifiable_user = create(:sms_user, sms_me_on_mention: false) ! SMSGateway.stub(:deliver) ! Notifier.notify(message) ! expected_message = "#{message.author.name} said #{message_body}" ! expect(SMSGateway).to have_received(:deliver).with(expected_message, notifiable_user1.sms_number) expect(SMSGateway).to have_received(:deliver).with(expected_message, notifiable_user2.sms_number) end end
  2. class Notifier def self.notify(message) message.mentioned_users. select(&:fb_me_on_mention?). select(&:facebook_connection). each do |user|

    user.facebook_connection.put_connections( "me", "feed", message: "#{message.author.name} said: #{message.body}" ) end ! email_users = message.mentioned_users. select(&:email_me_on_mention?). select{|u| u.email.present? } MessageMailer.deliver_message(message, email_users) ! message.mentioned_users. select(&:sms_me_on_mention?). select{|u| u.sms_number.present? }. each { |user| SMSGateway.deliver(message, user.sms_number) } end end
  3. Interface Segregation Principle “many client-specific interfaces are better than one

    general-purpose interface.” ! Robert C. Martin Principles of Object Oriented Design
  4. Dependency Inversion Principle “Depend upon abstractions. Do not depend upon

    concretions.” ! ! Robert C. Martin, Design Principles and Design Patterns
  5. Depending on a Concretion def save_my_game if game_changed? File.open('game.txt', 'w')

    do |file| file.print @game_data end end end ! save_my_game
  6. Depending on an Abstraction def save_my_game persistent_store = FileStore.new persistent_store.save(@game_data)

    if game_changed? end ! save_my_game class FileStore def save(game_data) File.open('game.txt', 'w') do |file| file.print game_data end end end
  7. Open/Closed Principle “Software entities … should be open for extension,

    but closed for modification” ! Bertrand Meyer, Object Oriented Software Construction
  8. Single Responsibility Principle “A class should have one, and only

    one, reason to change.” ! Robert C. Martin Principles of Object Oriented Design
  9. Single Responsibility Principle • classes should be small • classes

    should do one thing • we should have lots of classes
  10. SOLID • hide concretions behind abstractions • make entities extendible

    • use duck typing • classes should do one thing
  11. message.author # => User @JesseKatsopolis ! message.body # => "@DannyTanner,

    @JoeyGladstone, Watch the hair!" ! message.mentioned_users # => [ User @DannyTanner, User @JoeyGladstone ]
  12. describe Notifier, ".notify" do ! it "emails the message's mentioned

    users who prefer to be emailed" it "SMS's the message's mentioned users who prefer to be SMS'd" it "FB's the message's mentioned users who prefer to be FB'd" ! end
  13. Responsibilities • email users • SMS users • FB users

    ! Concretions • the message • the mentioned users • how to determine if someone wants to be emailed • how to determine if someone wants to be SMS'd • how to determine if someone wants to be FB'd

  14. Responsibilities Described it "emails the message's mentioned users who prefer

    to be emailed" it "SMS's the message's mentioned users who prefer to be SMS'd" it "FB's the message's mentioned users who prefer to be FB'd"
  15. it "FB's the message's mentioned users who prefer to be

    FB'd" do ! notifiable_user1 = create(:facebook_connected_user, fb_me_on_mention: true) notifiable_user2 = create(:facebook_connected_user, fb_me_on_mention: true) unnotifiable_user = create(:facebook_connected_user, fb_me_on_mention: false) ! message_body = "Hi @#{notifiable_user1.name} @#{notifiable_user2.name}\ @#{ unnotifiable_user.name }" ! message = create(:message, body: message_body) ! notifiable_user1.facebook_connection.stub(put_connections: true) notifiable_user2.facebook_connection.stub(put_connections: true) unnotifiable_user.facebook_connection.stub(put_connections: true) ! Notifier.notify(message) ! expected_message = "#{message.author.name} said #{message_body}" ! expect(notifiable_user1.facebook_connection). to have_received(:put_connections). with("me", "feed", message: expected_message) ! expect(notifiable_user2.facebook_connection). to have_received(:put_connections). with("me", "feed", message: expected_message) ! expect(unnotifiable_user.facebook_connection). not_to have_received(:put_connections) ! end
  16. describe Notifier, "#notify" do let(:message_body) do "Hi @#{notifiable_user1.name} @#{notifiable_user2.name}\ @#{

    unnotifiable_user.name }" end ! let(:message) { create(:message, body: message_body) } ! it "FB's the message's mentioned users who prefer to be FB'd" do notifiable_user1 = create(:facebook_connected_user, fb_me_on_mention: true) notifiable_user2 = create(:facebook_connected_user, fb_me_on_mention: true) unnotifiable_user = create(:facebook_connected_user, fb_me_on_mention: false) ! notifiable_user1.facebook_connection.stub(put_connections: true) notifiable_user2.facebook_connection.stub(put_connections: true) unnotifiable_user.facebook_connection.stub(put_connections: true) ! Notifier.notify(message) ! expected_message = "#{message.author.name} said #{message_body}" ! expect(notifiable_user1.facebook_connection).to have_received(:put_connections).with("me", "feed", message: expected_message) expect(notifiable_user2.facebook_connection).to have_received(:put_connections).with("me", "feed", message: expected_message) expect(unnotifiable_user.facebook_connection).not_to have_received(:put_connections) end ! it "emails the message's mentioned users who prefer to be emailed" do notifiable_user1 = create(:user, email_me_on_mention: true) notifiable_user2 = create(:user, email_me_on_mention: true) unnotifiable_user = create(:user, email_me_on_mention: false) ! MessageMailer.stub(:deliver_message) ! Notifier.notify(message) ! expected(MessageMailer).to have_received(:deliver_message).with(message, [notifiable_user1, notifiable_user2]) end ! it "SMS's the message's mentioned users who prefer to be SMS'd" do notifiable_user1 = create(:sms_user, sms_me_on_mention: true) notifiable_user2 = create(:sms_user, sms_me_on_mention: true) unnotifiable_user = create(:sms_user, sms_me_on_mention: false) ! SMSGateway.stub(:deliver) ! Notifier.notify(message) ! expected_message = "#{message.author.name} said #{message_body}" ! expect(SMSGateway).to have_received(:deliver).with(expected_message, notifiable_user1.sms_number) expect(SMSGateway).to have_received(:deliver).with(expected_message, notifiable_user2.sms_number) end end
  17. class Notifier def self.notify(message) message.mentioned_users. select(&:fb_me_on_mention?). select(&:facebook_connection). each do |user|

    user.facebook_connection.put_connections( "me", "feed", message: "#{message.author.name} said: #{message.body}" ) end ! email_users = message.mentioned_users. select(&:email_me_on_mention?). select{|u| u.email.present? } MessageMailer.deliver_message(message, email_users) ! message.mentioned_users. select(&:sms_me_on_mention?). select{|u| u.sms_number.present? }. each { |user| SMSGateway.deliver(message, user.sms_number) } end end
  18. it "emails the message's mentioned users who prefer to be

    emailed" it "SMS's the message's mentioned users who prefer to be SMS'd" it "FB's the message's mentioned users who prefer to be FB'd"
  19. it "______ the message's mentioned users who prefer to be

    ______" it "______ the message's mentioned users who prefer to be ______" it "______ the message's mentioned users who prefer to be ______"
  20. it "notifies via SMS/FB/Email the message's mentioned users who prefer

    to be notified via SMS/FB/Email, respectively"
  21. it "notifies mentioned users via notification channels" do ! message

    = double('message') channels = [ double(notify_about_message: nil), double(notify_about_message: nil) ] notifier = Notifier.new(channels) ! notifier.notify(message) ! channel[0].should have_received(:notify_about_message).with message channel[1].should have_received(:notify_about_message).with message ! end
  22. class Notifier ! def initialize(channels) @channels = channels end !

    def notify(message) @channels.each do |channel| channel.notify_about_message(message) end end ! end ! notifier = Notifier.new(channels) notifier.notify(message)
  23. channels = case Rails.env when 'production' [ EmailChannel.new, SMSChannel.new, NSAChannel.new

    ] when 'staging' [ TestEmailChannel.new, TestSMSChannel.new ] else [ LoggingChannel.new ] end ! config.notifier = Notifier.new( channels )
  24. it "emails the message's mentioned users who prefer email" do

    ! message = build(:message, mentioned_users: [ user_who_likes_email1 = build(:user, email_on_mention: true), user_who_likes_email2 = build(:user, email_on_mention: true), user_who_dislikes_email = build(:user, email_on_mention: false) ]) MessageMailer.stub(:deliver_message) ! EmailChannel.new.notify(message) ! MessageMailer.should have_received(:deliver_message) .with(message, [user_who_likes_email1, user_who_likes_email2]) ! end
  25. - notify via channels Notifier - how to notify by

    email - who prefers to be emailed Email Channel - how to notify by SMS - who prefers to be SMS'd SMS Channel - who prefers to be Facebooked - how to notify by Facebook Facebook Channel - which channels < Configuration > - which users are mentioned Message - email address - Facebook Credentials - SMS number User - notify by email - notify by SMS - notify by Facebook - how to notify by email - who prefers to be emailed - how to notify by SMS - who prefers to be SMS'd - who prefers to be Facebooked - how to notify by Facebook Notifier - which users are mentioned Message - email address - Facebook Credentials - SMS number User