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

เชี่ยวชาญ RSpec Testing ใน Ruby: บทเรียนครบวงจรพร้อมตัวอย่างปฏิบัติ

อยากเรียนรู้เขียน tests สำหรับ Ruby applications ด้วย RSpec?

แล้ว คุณอยู่ที่ถูกต้อง!

ใน tutorial นี้ ผมจะ show วิธีทำ

สารบัญ

  • 1 ทำไมควรเขียน Tests?
      • 1.0.1 สร้าง safety net ต่อ errors (มีประโยชน์มากสำหรับ refactoring)
      • 1.0.2 ช่วย document โค้ด
      • 1.0.3 ให้ feedback loop
      • 1.0.4 ช่วย ensure โค้ดผลลัพธ์ที่คาดหวัง
      • 1.0.5 ช่วยได้ Ruby job
  • 2 Getting Started With RSpec
  • 3 RSpec Testing Example
  • 4 RSpec Let Method
  • 5 วิธีใช้ Subject Method
  • 6 วิธี Run Code ก่อนทุก Tests
  • 7 วิธีสร้าง Testing Subgroups
  • 8 วิธี Disable Test ชั่วคราว
  • 9 รัน Examples ด้วย Name
  • 10 RSpec Expectations & Matchers
  • 11 RSpec Formatters
  • 12 วิธีหา Slow Tests
  • 13 RSpec Video Tutorial
  • 14 สรุป
    • 14.1 Related

ทำไมควรเขียน Tests?

นี่คือเหตุผล:

สร้าง safety net ต่อ errors (มีประโยชน์มากสำหรับ refactoring)

หากไม่มี test suite คุณไม่อยากแตะโค้ด เพราะกลัวพัง…

…มี tests เพิ่ม confidence!

ช่วย document โค้ด

Tests ของคุณ describe ว่า application ควรทำอะไร

ให้ feedback loop

ตอนทำ TDD คุณได้ feedback loop บอกว่าต้อง focus อะไรต่อ useful หาก distract ง่าย

ช่วย ensure โค้ดผลลัพธ์ที่คาดหวัง

นี่สำคัญ!

หากเขียน complex logic อยากแน่ใจว่าทำงานกับหลาย inputs ไม่ใช่แค่ตัวอย่างเดียว

Tests ช่วย uncover corner cases และ document พวกมัน

ช่วยได้ Ruby job

Job applications ส่วนใหญ่ appreciate testing skills เพิ่มโอกาสได้งาน

Getting Started With RSpec

เข้าใจ RSpec ทำงานอย่างไร มาไป step-by-step

เราจะเขียน application หา factorial numbers

ขั้นแรก:

require 'rspec/autorun'
describe Factorial do
  # ...
end

นี่คือ initial code สำหรับเขียน RSpec test แรก

ต้อง require rspec gem

จากนั้นสร้าง describe block เพื่อ group tests ทั้งหมด และบอก RSpec ว่าทดสอบคลาสไหน

ต่อไปคือ it block:

describe Factorial do
  it "does something" do
    # ...
  end
end

นี่คือ test name บวกวิธี group components ของ test

Components:

  • Setup
  • Exercise
  • Verify

Setup คือที่ สร้าง objects ที่ต้องการ

เป็น preparation phase

จากนั้นเรียกเมธอดที่อยาก exercise เพื่อได้ return value

สุดท้าย verify ผลลัพธ์ด้วย expectation (RSpec) หรือ assertion (Minitest)

RSpec Testing Example

ตอนนี้หากอยากเขียน factorial method ต้องหา valid values ออนไลน์หรือคำนวณมือ

ใช้กับ tests

แบบนี้:

describe Factorial do
  it "finds the factorial of 5" do
    calculator = Factorial.new
    expect(calculator.factorial_of(5)).to eq(120)
  end
end

เวลารันโค้ด (เหมือน Ruby program ปกติ) ได้:

uninitialized constant Factorial (NameError)

ปกติเพราะยังไม่มีคลาส Factorial

มาสร้าง:

class Factorial
end

Error ถัดไป:

undefined method 'factorial_of'

แก้โดยสร้างเมธอด factorial_of:

class Factorial
  def factorial_of
  end
end

รันอีกครั้ง:

wrong number of arguments (given 1, expected 0)

Error อีกครั้ง! แตคุณรู้ไหม?

นั่นดีมาก 🙂

Errors ไม่ใช่สิ่ง frustrating

มันคือ feedback

ตอนนี้:

เพิ่ม method argument ให้ factorial_of:

class Factorial
  def factorial_of(n)
  end
end

ตอนนี้ได้ test failure:

expected: 120
got: nil

นี่คือจุดที่คุณอยากอยู่!

งานต่อไป implement method:

class Factorial
  def factorial_of(n)
    (1..n).inject(:*)
  end
end

และได้ first passing test:

.
Finished in 0.00315 seconds (files took 0.09083 seconds to load)
1 example, 0 failures

นี่คือ test-driven development (TDD)

เขียน tests ก่อน ให้ tests guide ว่าต้องทำอะไรต่อ

RSpec Let Method

หากอยากเขียนหลาย tests และ reuse objects เดียวกัน นิยาม objects ด้วย let statements

长这样:

describe Factorial do
  let(:calculator) { Factorial.new }
  it "finds the factorial of 5" do
    expect(calculator.factorial_of(5)).to eq(120)
  end
end

ตอนนี้ reuse calculator ในทุก tests ภายใต้ describe block เดียวกัน

สิ่งหนึ่งเกี่ยว let คือมัน "lazy"

หมายถึงอะไร?

Object ไม่สร้างจนกว่าจะใช้ครั้งแรก

มีผลหากการสร้าง object มี side-effects เช่นสร้าง database entries หรือเขียนไฟล์

ดีที่สุดหลีกเลี่ยง side-effects แตหากไม่ได้ ใช้ let!

ตัวอย่าง:

let!(:user) { User.create("rspec@rubyguides.com") }

let! non-lazy object สร้างก่อนรัน tests

วิธีใช้ Subject Method

Version อื่นของ let คือ subject

ความแตกต่างคือมี subject ได้หนึ่งตัว และ meant to be instance ของ main object ที่ทดสอบ

RSpec สร้าง default subject แบบนี้:

subject { Factorial.new }

เรียกว่า "implicit subject"

ใช้ได้แบบนี้:

describe Factorial do
  it "finds the factorial of 5" do
    expect(subject.factorial_of(5)).to eq(120)
  end
end

ตั้งชื่อ subject ได้:

subject(:calculator) { Factorial.new }

พฤติกรรมเหมือน let แต enable one-line expectations:

it { should be_empty }

วิธี Run Code ก่อนทุก Tests

RSpec มี execution hooks ใช้ run something ก่อนและหลังทุก test หรือ group

ตัวอย่าง:

describe Shop do
  before(:all) { Shop.prepare_database }
  after (:all) { Shop.cleanup_database }
end

หากอยากรัน per example (example = test ใน RSpec) ใช้ :each แทน :all

วิธีสร้าง Testing Subgroups

หากทดสอบ scenarios ต่างๆ ใน app อาจช่วย group related tests

ทำได้ด้วย context block ใน RSpec

ตัวอย่าง:

describe Course do
  context "when user is logged in" do
    it "displays the course lessons" do end
    it "displays the course description" do end
  end
  context "when user it NOT logged in" do
    it "redirects to login page" do end
    it "it shows a message" do end
  end
end

วิธี Disable Test ชั่วคราว

Disable test ได้สำหรับ debugging

แค่เปลี่ยน it เป็น xit สำหรับ tests ที่อยาก disable

ตัวอย่าง:

xit "eats lots of bacon" do end

อย่าลืมลบ x ตอนเสร็จ!

รัน Examples ด้วย Name

แทน disable tests คุณ filter tests ที่อยากรันด้วย flag -e

ตัวอย่าง:

> ruby person.rb -e bacon

Filtering based on test name ดังนั้น above matches tests ที่มีคำ "bacon"

RSpec Expectations & Matchers

คุณจำตัวอย่างที่ใช้อยู่?

expect(calculator.factorial_of(5)).to eq(120)

แต eq(120) ส่วนนี้คืออะไร?

120 คือค่าที่คาดหวัง…

และ eq คือ matcher

Matchers คือวิธี RSpec compare output ของเมธอด กับ expected value

กรณี eq RSpec ใช้ operator == (อ่านเพิ่มเกี่ยวกับ Ruby operators)

แต มี matchers อื่นๆ ใช้ได้

เช่น be_something matcher:

expect(nil).to be_nil

ที่ something คือ predicate method (เช่น empty?) เรียกบน test results

Matchers useful อื่นๆ:

  • include (สำหรับ arrays และ hashes)
  • start_with
  • end_with
  • match (สำหรับ regex matching)
  • be_between
  • have_key / have_value (สำหรับ hashes)
  • be_instance_of
  • respond_to
  • have_attributes (สำหรับทดสอบ instance variables)

Matcher ที่ต้อง ระวังพิเศษ คือ raise_error

เหตุผลคือต้อง wrap expectation ใน block

แบบนี้:

expect{ :x.count }.to raise_error(NoMethodError)

change matcher ทำงานแบบเดียวกัน:

expect{ stock.increment }.to change(stock, :value).by(100)

RSpec Formatters

Default RSpec output เป็น format "progress"

Format นี้เห็น dots (.) represent 1 passing test, F สำหรับ failed test, E สำหรับ error

แตมี alternative formatting options

รายการ:

  • progress
  • documentation
  • json
  • html

Enable ด้วย flag -f:

> ruby factorial.rb -f d
Person
  eats lots of healthy food
  writes many articles
Finished in 0.00154 seconds (files took 0.09898 seconds to load)
2 examples, 0 failures

Documentation format ใช้ test descriptions generate output

วิธีหา Slow Tests

RSpec มี option handy profile tests

แค่ส่ง flag --profile จะเห็นแต่ละ test ใช้เวลานานแค่ไหน และ fix slow ones

ตัวอย่าง:

> ruby factorial.rb --profile
Factorial
  finds the factorial of 5
  0.00043 seconds

RSpec Video Tutorial

สรุป

คุณเรียนรู้เขียน tests ด้วย RSpec testing framework แล้ว!

ตอนถึงคุณเริ่มเขียน test ของตัวเอง!