2016-02-03 15 views
0

Я столкнулся с путаницей, соединяя тестовый двойник и обрезая его. Мой вопрос: какой самый подходящий способ проверить методы confirm_purchase_order и create_order в class PurchaseOrder?Прерывание возвращаемого значения в RSpec - правильное использование удвоений и заглушек

Я включил соответствующий код следующий код:

class PurchaseOrder 
    attr_reader :customer, :products 

    def initialize(customer) 
    @products = {} 
    @customer = customer 
    end 

    ....some other methods 

    def add_product(product, quantity = 1) 
    @products[product] = (@products[product] ? @products[product] + quantity : quantity) 
    puts "You haved added #{quantity} #{product.title}'s to your purchase order" 
    end 

    def confirm_purchase_order 
    purchase_order_total 
    raise "Your PO appears to be empty! Add some products and try again." unless self.total.to_f.round(2) > 0 

    create_order 
    create_invoice 

    return "We have generated an Invoice and created an order." 
    end 

    def create_order 
    order = Order.new(customer) 
    order.products = @products.clone 
    end 

    def create_invoice 
    invoice = Invoice.new(customer) 
    invoice.products = @products.clone 
    end 
end 

class Order 
    attr_reader :customer 
    attr_accessor :status, :total, :products 

    def initialize(customer) 
    @products = {} 
    @status = :pending 
    @customer = customer 
    end 

class Customer 
    attr_reader :name, :type 

    def initialize(name, type) 
    @name = name.to_s 
    @type = type.to_sym 
    end 

class Invoice 

    attr_reader :customer, :products 
    attr_accessor :total 

    def initialize(customer, products) 
    @products = {} 
    @customer = customer 
    @payment_recieved = false 
    end 
end 

Я хочу протестировать метод confirm_purchase_order, а также метод create_order в class PurchaseOrder. Мой подход до сих пор:

Мне нужно несколько объектов двойников и фактической PurchaseOrder object

describe PurchaseOrder do 
    let(:product) { double :product, title: "guitar", price: 5 } 
    let(:order) { instance_double(Order) } 
    let(:customer) { double :customer, name: "Bob", type: :company } 
    let(:products) { {:product => 1} } 

    let(:purchase_order) { PurchaseOrder.new(customer) } 

    describe "#create_order" do 

    it "returns an order" do 
     expect(Order).to receive(:new).with(customer).and_return(order) 
     allow(order).to receive(products).and_return(???products??!) 

     purchase_order.add_product(product, 1) 
     purchase_order.create_order 
     expect(order.products).to eq (products) 
    end 
    end 
end 

Я также посмотрел на использование:

# order.stub(:products).and_return(products_hash) 
# allow_any_instance_of(Order).to receive(:products) { products_hash } 
# order.should_receive(:products).and_return(products_hash) 

Для установки порядка дважды для возврата продукции хеш, когда вызывается order.products, но все они чувствуют, что они слишком «фальсифицируют» тест. Каков наиболее подходящий способ тестирования методов confirm_purchase_order и create_order в class PurchaseOrder?

ответ

1

Мне кажется, что, возможно, вы даете PurchaseOrder слишком много ответственности. Теперь он имеет глубокие знания о Order и Invoice.

Я бы, возможно, проверить текущую реализацию, как это:

it "returns an order with the same products" do 
    expect_any_instance_of(Order).to receive(:products=).with(products: 1) 

    purchase_order.add_product(product, 1) 
    expect(purchase_order.create_order).to be_a(Order) 
end 

Но, возможно, это может иметь смысл разъединять PurchaseOrder от Order и Invoice немного и сделать что-то вроде этого:

class Invoice 
    def self.from_purchase_order(purchase_order) 
    new(purchase_order.customer, purchase_order.products.clone) 
    end 
end 

class Order 
    def self.from_purchase_order(purchase_order) 
    new.tap(purchase_order.customer) do |invoice| 
     invoice.products = purchase_order.products.clone 
    end 
    end 
end 

class PurchaseOrder 
    # ... 
    def create_order 
    Order.from_purchase_order(self) 
    end 

    def create_invoice 
    Invoice.from_purchase_order(self) 
    end 
end 

describe PurchaseOrder do 
    let(:customer) { double('a customer')} 
    let(:purchase_order) { PurchaseOrder.new(customer) } 
    describe "#create_order" do 
    expect(Order).to receive(:from_purchase_order).with(purchase_order) 
    purchase_order.create_order 
    end 

    describe "#create_invoice" do 
    expect(Order).to receive(:from_purchase_order).with(purchase_order) 
    purchase_order.create_order 
    end 
end 

describe Order do 
    describe '.from_purchase_order' do 
    # test this 
    end 
end 

describe Order do 
    describe '.from_purchase_order' do 
    # test this 
    end 
end 

Этот способ позволяет классам Order и Invoice знать, как построить себя с PurchaseOrder. Вы можете протестировать эти методы класса отдельно. Тесты для create_order и create_invoice упрощаются.

Некоторые другие вещи, я думал:

Для products, попробуйте использовать Hash с прока по умолчанию:

@products = Hash.new { |hash, unknown_key| hash[unknown_key] = 0 } 

Таким образом, вы всегда можете смело делать @products[product] += 1.

+0

Спасибо Йесперу за такой всеобъемлющий ответ. Я работаю над рефакторингом.Я не сталкивался с этим «шаблоном проектирования» (я думаю, вы могли бы его назвать), чтобы гарантировать, что вы делегируете ответственность за создание объекта для этого класса с помощью метода класса таким образом, но полностью понимаете, почему это необходимо с точки зрения инкапсуляции , –