Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ลดความซับซ้อนการทดสอบ Ruby Network Services: กลยุทธ์และเครื่องมือที่พิสูจน์แล้ว

เริ่มโปรเจกต์ใหม่ เวลาโค้ด depend on third-party service อาจเป็น ElasticSearch, Resque, billing provider หรือ arbitrary HTTP API คุณ good developer อยากโค้ดนี้ well tested แตทดสอบโค้ดที่ยิง requests ไป service ที่ควบคุมไม่ได้ ยังไง?

ข้ามทดสอบได้ แตเร็วๆ จะ pile โค้ดมากบน foundation รั่ว Untested code ดึง complex code มา และรู้สึกว่าโค้ด dangerous to refactor เพราะไม่มี test coverage รู้สึกปลอดภัย อยากสร้าง stable foundation แตจบกับ unmaintainable mess

หลีกเลี่ยงสถานการณ์นี้ง่ายกว่าคิด! ด้วย tools ใหม่และ up-front effort นิดหน่อย คุณ decouple tests จาก services ที่โค้ด depend เขียนโค้ดง่าย และมีมั่นใจปรับปรุงโค้ดโดยไม่ introduce bugs แทน procrastinate เพราะไม่รู้เขียนทดสอบต่อ ให้ดู interaction ระหว่างโค้ดและ outside world รู้ทันทีว่าจะ jump เข้าไปกลางยังไง

Mocha: quick-and-dirty approach

Mocha วิธีง่ายที่สุดได้ระหว่างโค้ดและ outside world

ตัวอย่าง มี Cart object trigger credit card charge ตอน checkout อยากแน่ใจว่า cart มี error message ถ้า charge fail

ไม่อยาก tests hit billing system ทุกครั้ง รัน tests แตก็ยาก force service return failure นี่ Mocha:

def test_error_message_set_on_charge_failure
  cart = Cart.new(items)
  cart.stubs(:charge!).returns(false) # mocha in action
  cart.checkout!
  assert_equal "The credit card could not be charged", cart.credit_card_error
end

Mocha ยัง fail tests ถ้า methods ไม่ถูกเรียกแบบที่คาดหวัง:

def test_only_bill_once_per_cart
  cart = Cart.new(items)
  cart.expects(:charge!).once # Don't double-bill, no matter how many times we check out
  cart.checkout!
  cart.checkout!
end

Mocha 簡單แต handy ต้องระวัง mock แค่ behavior ไม่อยากเกิดง่าย mock มากซ่อน real bugs อย่าจะ overboard: tests เต็ม expects และ stubs อ่านคิดยาก

Test fakes: วิธีโปรดของผม

ถ้า mock/stub methods เดียวกันบน objects เดียวกันตลอด promote mocks เป็น full-fledged objects (เรียกว่า test fakes) แบบนี้:

def test_billed_full_amount_minus_discount
  test_payment_provider = TestPaymentProvider.new # A fake payment provider
  cart = Cart.new(items, discount: 30, provider: test_payment_provider)
  cart.checkout!
  assert_equal items.sum(:&price) * 0.7, test_payment_provider.total_charges
end

Fakes ดี:

  • Fake track internal state ของตัวเอง

    Fake มี custom assertion messages และ helper functions ทำทดสอบง่าย เช่น total_charges method ตัวอย่าง

  • เป็น full-fledged object ได้ editor และ language support เพิ่ม

    editor support autocomplete, inline docs, และสิ่งที่ไม่ได้จาก stubbing methods กับ Mocha validations, exception handling ดีกว่า

  • 用 fake ใน development mode ไม่ต้อง connection real service

    เขียน app บนรถบัส ไม่ต้อง forest services รันกินแบตลaptop และ setup fakes คืน data สำหรับ edge cases โดยไม่ setup มาก

  • Objects นี้用นอกทดสอบได้

    นี่ favorite ส่วนของ fakes logging client log ไป 3rd party service และ fake backed by in-memory array dump contents array ใน admin view บนเว็บ verify ว่ากำลัง log สิ่งที่คิดง่ายขึ้น

ทำได้แบบนี้:

fake_backend = FakeBackend.new
LoggingService.backends = [RealBackend.new, fake_backend]
LoggingService.debug("TEST MESSAGE PLEASE IGNORE")
fake_backend.messages.first # => [:debug, "TEST MESSAGE PLEASE IGNORE"]

เขียน fake uses effort มากกว่า stub methods แตฝึกแล้วไม่เกิน 1-2 ชั่วโมงได้ fake helpful ถ้าสร้างอันมีประโยชน์คนอื่น share it! ผมสร้าง resque-unit นานแล้ว คนเยอะยัง用

inject objects นี้ยังไง?

ต้องทำให้ objects under test คุยกับ fakes โชคดี Ruby abuse ง่าย inject fakes มักไม่ยาก

ควบคุม API ของ object under test เพิ่ม default parameter, attribute, หรือ constructor option ตั้ง fake:

class Card
  attr_reader :provider
  def initialize(items, options={})
    @provider = options.fetch(:provider) { RealProvider.new }
  end
end

สะอาดตอนคุย real service และมี hook เพิ่ม flexibility ภายหลัง

ไมेकวบคุม object หรือไม่อยากเพิ่ม parameter monkey patch ได้:

# if in test mode
Card.class_eval do
  def provider
    @provider ||= TestProvider.new
  end
end

醜ใน test แตสะอาดใน environments ไม่ใช้ fake

เริ่มสร้าง fake ของตัวเองตอนนี้

สร้าง fakes ง่ายขึ้นตอนฝึก ลองตอนนี้:

  • หาทดสอบคุย external service Tests ที่ fail ถ้า disconnect internet candidates ดี
  • หา object ที่ communicate จริง และ calls ที่โค้ดทำไป object นั้น
  • สร้าง duplicate ว่างเกือบของ class นั้น log calls ไป array
  • เพิ่ม method คืน list of calls made
  • Swap real object ด้วย fake ใหม่ เขียน assertions ต่อ calls ที่โค้ดทำ

ลองดู บอกผมผล!

ด้วยเทคนิคเหล่านี้ ไม่เกินนานคุณจะ tame craziest interactions ระหว่าง app และ outside world Stub 簡單ที่ถูกที่ จะ ship well-tested code ด้วยมั่นใจ