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

SOLID Principles through tests

SOLID Principles through tests

A practical approach on how to integrating the SOLID principles to the way we write code will pay off in the long the term.

Presented at: RubyConf Brazil 2014

Sebastian Sogamoso

August 28, 2014
Tweet

More Decks by Sebastian Sogamoso

Other Decks in Programming

Transcript

  1. Cats follow the 6 harmonies to practice Kungfu We should

    follow principles when writing code RECAP
  2. E-commerce system • Initial status: supports a few shipping methods

    • Requirement: painlessly add new shipping methods as needed
  3. class Order ... def ship case @store.shipping_method when Shipping::RegularMethod @store.shipping_method.pack(@items,

    @buyer, @seller).ship when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" @store.shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send end end ... end
  4. def ship case @store.shipping_method when Shipping::RegularMethod @store.shipping_method.pack(@items, @buyer, @seller).ship when

    Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" @store.shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send when Shipping::NewMethod ... end end
  5. class Order ... def ship(shipping_method) case shipping_method when Shipping::RegularMethod shipping_method.pack(@items,

    @buyer, @seller).ship when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send else shipping_method.ship_order(self) end end ... end
  6. class Order ... def ship(shipping_method) case shipping_method when Shipping::RegularMethod shipping_method.ship_order(self)

    when Shipping::OvernightMethod shipping_method.ship_order(self) when Shipping::SameDayMethod shipping_method.ship_order(self) else shipping_method.ship_order(self) end end ... end
  7. describe Order do describe "#ship" do ... subject { Order.new

    } context "when shipping method is a Shipping::RegularMethod" do it "uses Shipping::RegularMethod to ship the order" do allow(shipping_method).to receive(class: Shipping::RegularMethod) subject.ship expect(shipping_method). to have_received_message_chain(:pack, :ship). with(items, seller, buyer) end end context "when shipping method is a Shipping::OvernightMethod" do it "uses Shipping::OvernightMethod to ship the order" do allow(shipping_method).to receive(class: Shipping::OvernightMethod) subject.ship expect(shipping_method). to have_received(:ship_order) end end context "when shipping method is a Shipping::SameDayMethod" do it "uses Shipping::SameDayMethod to ship the order" do allow(shipping_method).to receive(class: Shipping::SameDayMethod) subject.ship expect(shipping_method). to have_received_message_chain(:pack, :send). end end end end
  8. context "when shipping method is a Shipping::OvernightMethod" do it "uses

    Shipping::OvernightMethod to ship the order" do allow(shipping_method).to receive(class: Shipping::OvernightMethod) subject.ship expect(shipping_method). to have_received(:ship_order) end end
  9. “TDD doesn’t drive good design. TDD gives you immediate feedback

    about what’s likely to be bad design” Kent Beck
  10. class Order ... def ship(shipping_method) case shipping_method when Shipping::RegularMethod shipping_method.pack(@items,

    @buyer, @seller).ship when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send else shipping_method.ship_order(self) end end ... end
  11. module Shipping class RegularMethod < BaseMethod ... def self.ship_order(order) new.

    pack(order.items, order.origin, order.destination) .ship end ... end end
  12. class Order ... def ship(shipping_method) case shipping_method when Shipping::RegularMethod shipping_method.ship_order(self)

    when Shipping::OvernightMethod origin = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send else shipping_method.ship_order(self) end end ... end
  13. class Order ... def ship(shipping_method) case shipping_method when Shipping::OvernightMethod origin

    = @seller.address destination = “#{@buyer.address_line1}, #{@buyer.zip}" shipping_method.ship_order(origin, destination, @items) when Shipping::SameDayMethod addresses = [@seller.address, @buyer.complete_address] @store.shipping_method.pack(@items, addresses).send else shipping_method.ship_order(self) end end ... end
  14. describe Order do describe "#ship" do ... subject { Order.new

    } context "when shipping method is a Shipping::OvernightMethod" do it “ships the order using the given shipping method" do order = Order.new allow(shipping_method).to receive(class: Shipping::OvernightMethod) allow(shipping_method).to receive(:ship_order) { ‘ordered shipped’ } expect(order.ship(shipping_method).to eq ‘ordered shipped’ end end context "when shipping method is a Shipping::SameDayMethod" do it “ships the order using the given shipping method" do order = Order.new allow(shipping_method).to receive(class: Shipping::SameDayMethod) allow(shipping_method).to receive(:ship_order) { ‘ordered shipped’ } expect(order.ship(shipping_method).to eq ‘ordered shipped’ end end context "when shipping method is a different type" do it "uses Shipping::SameDayMethod to ship the order" do allow(shipping_method).to receive(class: nil) subject.ship expect(shipping_method). to have_received_message_chain(:pack, :send). end end end end
  15. describe Order do ... describe "#ship" do let(:shipping_method) { double(:shipping_method)

    } subject { Order.new } it “ships the order using the given shipping method" do order = Order.new.pack allow(shipping_method).to receive(:ship_order) { ‘ordered shipped’ } expect(order.ship(shipping_method).to eq ‘ordered shipped’ end ... end
  16. class Order ... def ship(shipping_method) case shipping_method.class when Shipping::RegularMethod shipping_method.ship_order(self)

    when Shipping::OvernightMethod shipping_method.ship_order(self) when Shipping::SameDayMethod shipping_method.ship_order(self) end end ... end
  17. shared_examples "a shipping method" do it do should respond_to(:ship_order). with(1).argument

    end it “can be used to ship an order” do order = Order.new shipping_method = subject expect(order).to_not be_shipped order.ship(shipping_method) expect(order).to be_shipped end end
  18. module Shipping class WikedCrazyMethod < BaseMethod def self.ship_order(order) items =

    order.items * Math::PI origin = order.seller.name.reverse new.fold(items, origin, order.buyer).fly end ... end end
  19. Shipping cost calculation Formula that considers: • The amount of

    items (except for digital items). • A value that represents the distance between the origin and the destination . • A value that represents a fixed rate. items_quantity * distance * fixed_rate
  20. • Initial status: a change on any formula items, requires

    changing the class • Requirement: if only one formula item is changed the rest of the items shouldn’t be affected Shipping cost calculation
  21. describe Shipping::Cost do describe "#calculate" do ... subject { Shipping::StandardMethod.new(order,

    origin, destination) } context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when more than one item in the order are not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when origin city doesn't ship to the destination" do ... end end ... end
  22. module Shipping class Cost def initialize(order, origin, destination) ... end

    def calculate items_quantity * rate * distance end ... private def items_quantity @order.items.reject(:digital?).lenght end def rate if items_quantity > 1 9.99 else 5.99 end end def distance return 0 if @origin.does_not_ship_to? @destination return 1 if @origin.same_state? @destination return 2 if @origin.same_country? @destination 3 end end end
  23. describe Shipping::Cost do ... describe "#calculate" do subject { Shipping::StandardMethod.new(order,

    origin, destination) } context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when more than one item in the order are not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when the origin city doesn't ship to the destination" do ... end end ... end
  24. describe Shipping::Cost do ... describe "#calculate" do subject { Shipping::StandardMethod.new(order,

    origin, destination) } context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when more than one item in the order are not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when the origin city doesn't ship to the destination" do ... end end ... end
  25. • "when all items in the order are digital” •

    "when exactly one item in the order is not digital" • "when more than one item in the order are not digital"
  26. describe Shipping::Items do describe "#quantity" do context "when all items

    in the order are digital" do ... end context "when exactly one item in the order is not digital" do ... end context "when more than one item in the order are not digital" do ... end end end
  27. describe Shipping::Cost do ... describe "#calculate" do subject { Shipping::StandardMethod.new(order,

    origin, destination) } context "when all items in the order are digital" do ... end context "when exactly one item in the order is not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when more than one item in the order are not digital" do context "when the origin and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end end context "when the origin city doesn't ship to the destination" do ... end end ... end
  28. • "when the origin and destination are in the same

    state” • "when the origin and destination are in the same country” • "when the origin and destination are in different countries” • "when the origin city doesn't ship to the destination"
  29. describe Shipping::Distance do describe "#calculate" do context "when the origin

    and destination are in the same state" do ... end context "when the origin and destination are in the same country" do ... end context "when the origin and destination are in different countries" do ... end context "when the origin city doesn't ship to the destination" do ... end end end
  30. module Shipping class Cost def initialize(order, origin, destination) ... end

    def calculate items_quantity * rate * distance end private def items_quantity Shipping::Items.new(@order).quantity end def rate if items_quantity > 1 9.99 else 5.99 end end def distance Shipping::Distance.new(origin, destination).calculate end end end
  31. module Shipping class Cost def initialize(order, origin, destination) ... end

    def calculate items_quantity * rate * distance end private def items_quantity Shipping::Items.new(@order).quantity end def rate Shipping::Rate.new(items_quantity).calculate end def distance Shipping::Distance.new(origin, destination).calculate end end end
  32. Credits • Me at RubyConf Uruguay: https://secure.flickr.com/photos/94260666@N05/14251620082/ • James: https://www.flickr.com/photos/125635394@N08/14894541332/in/photolist-oGbqdL-9neeBd-9neeD3-o8YsWp-osB7nF-oqTkmd-osef7e-9nfoAb-oDxDMY-

    o9mymh-ooPmHN-o9mAa9-o9mwaf-o9mBVn-o9mBf4-o9nDYM-o9mEds-o9nEaZ-o9mD9L-oqPJSm-ooPvhG-o9nCTk-o9nKtV-ooPnL9-oqz7uD-ooPvxw-o9mAu6- oqz4o8-o9mCpZ-ooPzR1-oqPGfY-oqD4hS-oqz4TM-osB7Kz-ooPv5s-osBfmB-o9mzY5-osBdax-oqz8ar-o9mJj4-o9mH3f-o9mAoy-oqReWV-o9mwVU-o9mEAJ-oqz2Tz- o9mv9N-o9mBia-o9nESR-oqD2xE • Golden Gate Bridge: https://www.flickr.com/photos/salim/402620871/in/photolist-gxzCVN-3gHR9W-5Wkky-7L2Rdu-7uys1-5R11B1- e7CfhP-6DZhWn-38B3gv-4tN2Xq-kAVAG-dUSUQy-6zLofj-Bzxaa-4jmZiD-Bxufq-bfCFgT-5V84Y-n3nkmB-Bzxus-Bzwuu-bVnpiR-n8Vxzv-axiND-ekFJyQ-h881MJ- bdn9iB-dX8ZeH-ba8H-dZcoQ1-5eikhM-cdFJjh-BzwPa-iW215d-5cTsTq-aPkLxV-6u7c4x-eD5M4-hFTq1W-78nwcX-sXd8e-fCMz9o-813icF-39Wstq-ev17J3-nPu9UG- c6bssC-6EDRSs-dQjTmN-axiXV • Gatto Mimmo: https://www.flickr.com/photos/gattomimmo/2125052913/in/photolist-3eqRoo-bRHqDi-akDPPi-9znES-482wnh-57PniE-7k88aj-2eEDEV-4eMsck- NEZ6A-eAR9nm-6c4a3f-c6p92h-2Y3Xp-78ATbn-9cdK3N-48ERpQ-6bANod-guvqG4-NEZ7f-DV4uM-47Xq5i-95D52m-7GfyjG-3E8qR-2vnfir-6qkkAk-d6XrpW-Ey2hB- NEZ6L-8mNVog-fEgakj-7oGZng-7YpL32-6ScqrQ-qPBk1-4Pvgs-93vS3Y-h1Gipq-47XHfB-PsFae-7YsZMh-8mPMr2-h1G7cr-gux5xc-2xcrmY-owS2Tg-8Jt8ro- ddKAb-332pLa • Shaolin Kungfu 1: https://www.flickr.com/photos/larique/3235902917/in/ photolist-5VWRcB-5KpggW-5KpioE-5VWDwg-5Kk2Qp-5Gf7j-4Jntb-4Jnio-4JoKG-4JoeM-4MZ6M-4JnKm-4JoTZ-4Johi-4JoEA-3xWQA- gsor-7t8exA-5VWPb4-5W24MC-gvWkgi-yKgw6-5W226u-5W1U5w-oUZif-8Mo9FE-8Mk5bg-8Modbf-5Kk3Wa-gwdV3T-fAcBQo-xTuHS-fFtqEg-5W1M8y-5W1KgG- oUZii-oV14w-crzuKN-9Hpikx-9HpdF6-9Hsgeh-9Hsa5Q-9HpiUD-9HsaBC-9HpkEg-9HskyJ-9Hs5jC-9Hpgiv-9HpvH2-9Hpd9k • Shaolin Kungfu 2: https://www.flickr.com/photos/larique/3236693046/in/photolist-5W1U5w-oUZif-8Mo9FE-8Mk5bg-8Modbf-5Kk3Wa-gwdV3T-fAcBQo-xTuHS- fFtqEg-5W1M8y-5W1KgG-oUZii-oV14w- crzuKN-9Hpikx-9HpdF6-9Hsgeh-9Hsa5Q-9HpiUD-9HsaBC-9HpkEg-9HskyJ-9Hs5jC-9Hpgiv-9HpvH2-9Hpd9k-9HsjxW-9Hse2b-9HpucM-9Hs7rj-9Hsigj-9Hpnki-9Hpfn K-9HpbAX-9HppSF-9Hs8xd-9HsiEQ-9HscSS-9Hscgw-9HpuJc-9Hps8M-9Hptd4-9Hprfp-9Hso4o-9HpmM8-9HpoSp-9HppnX-9Hs6X9-9Hs3Lm
  33. • Shaolin Kungfu 3: https://www.flickr.com/photos/larique/3235896099/in/photolist-5VWPb4-5W24MC-gvWkgi-yKgw6-5W226u-5W1U5w- oUZif-8Mo9FE-8Mk5bg-8Modbf-5Kk3Wa-gwdV3T-fAcBQo-xTuHS-fFtqEg-5W1M8y-5W1KgG-oUZii-oV14w- crzuKN-9Hpikx-9HpdF6-9Hsgeh-9Hsa5Q-9HpiUD-9HsaBC-9HpkEg-9HskyJ-9Hs5jC-9Hpgiv-9HpvH2-9Hpd9k-9HsjxW-9Hse2b-9HpucM-9Hs7rj-9Hsigj-9Hpnki-9Hpfn K-9HpbAX-9HppSF-9Hs8xd-9HsiEQ-9HscSS-9Hscgw-9HpuJc-9Hps8M-9Hptd4-9Hprfp-9Hso4o • Uncle

    Bob: https://www.flickr.com/photos/moofbong/4206618799/in/ photolist-7pJ2dk-7pMCyY-7pMWo5-7pHWfe-7pMJ7s-7pMSoJ-7pJ16V-7pHSMt-7pHXpr-7pMNVw-7pHL6Z-7pHHPt-7pMGow-7pHMqv-seGMM-seGNW-7j2wCX- seHCx-seHbn-seHx4-seHmT-seHod-seGDJ-seHFq-seHDM-seGEB-seGL1-seHK6-seGHD-seGYT-seHBi-seGFC-seHHY-seH4e-seHiT-seGJp-seGK7-seHsD-seHkn- seHH7-seHxv-seH5f-seH6s-seGGF-seH3p-seHy8-seH9M-seHzx-b1hoEp-Gj1Wx/ • Kent Beck: https://www.flickr.com/photos/skrb/238102518/in/photolist-Hfu8-n3kzQ-n3kWh-n3kkG-7Jnjvu-6foWR2-6foWRa-djMXPd-cWkWKS-oF4y3- oCY4q-7Tx3yN-3p6hoi-4SrHSK-3Uu3Z3-8QyWVd-3paQF1-3p6hcT-3o4296-3o423c-3p62PD-3p62FM-3p62ze-3p6aQz-3p6h26-3paA55-3payDY-3p5VdK-3p5TaV-3p5S x6-3p5Wnc-3p5YdM-3pavp9-3p5Y4B-3pawg1-3pauEh-3pay1L-3patuL-3p5UiB-3p61zD-3paqDq-3parJE-3p5XKn-3paujq-3pavM9-3p5ZFR-3p5SEk-3pavdN-3p5YMZ-3 pauxq • Superheroes: https://www.flickr.com/photos/charlot17/4648822207 • Beware: https://www.flickr.com/photos/atoach/3922401551/in/photolist-AmPFE-2xwi8T-64GFek-6kWVRg-9vAqpD-jiG1mG-4tj1U-cvyg1f-4eHMx9-65XzLZ- czjMeb-6Y6JC8-5Hja23-6YBkjX-7tRTC3-64VB6C-mmd6zP-9LaWU3-7T61c5-kDDFs3-gmYDTg-ekvzx5-9mrc-7JphLV-6x1xw1-atzvpP-5Nz7gs-5y9TPm-nxj3e1- hHNuw9-bpnnzS-7B43Lf-9BMD7M-bvZDbg-jwss4F-bUhfe1-8ype7F-51LBV-n9rWsV-KugGj-dCPvvp-cECRaC-7GjgEU-jMaCY-h66jGg-jGjbF-9jF4P-diSx2r-nsVjAr- ceUeoG • Burroteca: http://www.jackmag.com.co/articles/burroteca/ Credits