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

Test-Commit-Revert:เวิร์กโฟลว์ที่มีประโยชน์สำหรับการทดสอบรหัสดั้งเดิมใน Ruby

มันเกิดขึ้นกับพวกเราทุกคน เมื่อโปรเจ็กต์ซอฟต์แวร์เติบโตขึ้น บางส่วนของโค้ดเบสก็สิ้นสุดลงในการผลิตโดยไม่มีชุดทดสอบที่ครอบคลุม เมื่อคุณลองพิจารณาโค้ดเดิมอีกครั้งหลังจากผ่านไปสองสามเดือน มันอาจจะเข้าใจยาก ที่แย่ไปกว่านั้นคือ อาจมีข้อบกพร่อง และเราไม่รู้ว่าจะเริ่มต้นแก้ไขตรงไหน

การปรับเปลี่ยนโค้ดโดยไม่ทำการทดสอบถือเป็นความท้าทายที่สำคัญ เราไม่สามารถแน่ใจได้ว่าเราจะทำลายสิ่งใดในกระบวนการ และการตรวจสอบทุกอย่างด้วยตนเองอย่างดีที่สุดมีแนวโน้มที่จะเกิดข้อผิดพลาด ปกติมันเป็นไปไม่ได้

การจัดการกับโค้ดประเภทนี้เป็นงานทั่วไปอย่างหนึ่งที่เราทำในฐานะนักพัฒนา และเทคนิคมากมายที่เน้นไปที่ปัญหานี้ในช่วงหลายปีที่ผ่านมา เช่น การทดสอบการกำหนดลักษณะที่เราได้พูดคุยกันในบทความที่แล้ว

วันนี้ เราจะมาพูดถึงเทคนิคอื่นโดยอิงจากการทดสอบลักษณะเฉพาะและแนะนำโดย Kent Beck ผู้ซึ่งได้แนะนำ TDD ให้กับโลกของการเขียนโปรแกรมสมัยใหม่เมื่อหลายปีก่อน

TCR คืออะไร

TCR ย่อมาจาก "test, commit, revert" แต่เรียกมันว่า "test &&commit || revert" ได้แม่นยำกว่า มาดูกันว่าทำไม

เทคนิคนี้อธิบายเวิร์กโฟลว์เพื่อทดสอบโค้ดเดิม เราจะใช้สคริปต์ที่จะทำการทดสอบทุกครั้งที่เราบันทึกไฟล์โครงการของเรา ขั้นตอนมีดังนี้:

  • ขั้นแรก เราสร้างการทดสอบหน่วยว่างสำหรับส่วนของรหัสเดิมที่เราต้องการทดสอบ
  • จากนั้นเราจะเพิ่มการยืนยันเพียงครั้งเดียวและบันทึกการทดสอบ
  • เนื่องจากเราได้ตั้งค่าสคริปต์แล้ว การทดสอบจึงทำงานโดยอัตโนมัติ หากทำได้สำเร็จ การเปลี่ยนแปลงจะเกิดขึ้น หากไม่สำเร็จ การเปลี่ยนแปลงจะถูกลบ (เปลี่ยนกลับ) และเราจำเป็นต้องลองอีกครั้ง

เมื่อการทดสอบผ่าน เราก็สามารถเพิ่มกรณีทดสอบใหม่ได้

โดยพื้นฐานแล้ว TCR นั้นเกี่ยวกับการรักษาโค้ดของคุณให้อยู่ในสถานะ "สีเขียว" แทนที่จะเขียนการทดสอบที่ล้มเหลวก่อน (สีแดง) แล้วจึงทำให้มันผ่าน (สีเขียว) เช่นเดียวกับที่เราทำกับการพัฒนาที่ขับเคลื่อนด้วยการทดสอบ หากเราเขียนการทดสอบที่ล้มเหลว การทดสอบนั้นจะหายไป และเราจะถูกนำกลับสู่สถานะ "สีเขียว" อีกครั้ง

วัตถุประสงค์

เป้าหมายหลักของเทคนิคนี้คือทำความเข้าใจโค้ดให้ดีขึ้นเล็กน้อยทุกครั้งที่คุณเพิ่มกรณีทดสอบ การทำเช่นนี้จะเพิ่มความครอบคลุมในการทดสอบโดยธรรมชาติและเลิกบล็อกการปรับโครงสร้างใหม่จำนวนมากที่ไม่เช่นนั้นจะไม่สามารถทำได้

ข้อดีอย่างหนึ่งของ TCR คือมีประโยชน์ในหลายสถานการณ์ เราสามารถใช้กับรหัสที่ไม่มีการทดสอบเลยหรือกับรหัสที่ทดสอบแล้วบางส่วน หากการทดสอบไม่ผ่าน เราก็แค่ยกเลิกการเปลี่ยนแปลงแล้วลองอีกครั้ง

เราจะใช้งานได้อย่างไร

Kent Beck แสดงให้เห็นในบทความและวิดีโอต่างๆ (ลิงก์ในตอนท้าย) ว่าแนวทางที่ดีคือการใช้สคริปต์ที่ทำงานหลังจากบันทึกไฟล์บางไฟล์ในโครงการแล้ว

สิ่งนี้จะขึ้นอยู่กับโครงการที่คุณพยายามทดสอบเป็นอย่างมาก บางสิ่งเช่นสคริปต์ต่อไปนี้ ซึ่งทำงานทุกครั้งที่เราบันทึกไฟล์ด้วยปลั๊กอินในตัวแก้ไข ถือเป็นการเริ่มต้นที่ดี:

(rspec && git commit -am "WIP") || git reset --hard

หากคุณกำลังใช้ Visual Studio Code ปลั๊กอินที่ดีในการดำเนินการในทุกการบันทึกคือ "runonsave" คุณสามารถรวมคำสั่งด้านบนหรือคำสั่งที่คล้ายกันสำหรับโครงการของคุณ ในกรณีนี้ ไฟล์ปรับแต่งทั้งหมดจะเป็น

{
  "folders": [{ "path": "." }],
  "settings": {
    "emeraldwalk.runonsave": {
      "commands": [
        {
          "match": "*.rb",
          "cmd": "cd ${workspaceRoot} && rspec && git commit -am WIP || git reset --hard"
        }
      ]
    }
  }
}

จำไว้ว่าในภายหลัง คุณสามารถสควอชคอมมิตด้วย Git ได้โดยตรงในบรรทัดคำสั่ง หรือเมื่อรวม PR หากคุณใช้ Github:

Test-Commit-Revert:เวิร์กโฟลว์ที่มีประโยชน์สำหรับการทดสอบรหัสดั้งเดิมใน Ruby .

ซึ่งหมายความว่าเราจะได้คอมมิชชันเดียวใน main branch สำหรับคอมมิชชันทั้งหมดที่เราทำบน branch ที่เรากำลังดำเนินการอยู่ แผนภาพนี้จาก Github อธิบายได้ดี:

Test-Commit-Revert:เวิร์กโฟลว์ที่มีประโยชน์สำหรับการทดสอบรหัสดั้งเดิมใน Ruby .

เขียนการทดสอบครั้งแรกของเราด้วย TCR

เราจะใช้ตัวอย่างง่ายๆ เพื่อแสดงเทคนิค เรามีคลาสที่รู้ว่าใช้งานได้ แต่เราต้องแก้ไข

เราสามารถทำการเปลี่ยนแปลงและปรับใช้การเปลี่ยนแปลงได้ อย่างไรก็ตาม เราต้องการให้แน่ใจว่าเราจะไม่ทำลายสิ่งใดในกระบวนการ ซึ่งเป็นความคิดที่ดีเสมอ

# worker.rb
class Worker
  def initialize(age, active_years, veteran)
    @age = age
    @active_years = active_years
    @veteran = veteran
  end

  def can_retire?
    return true if @age >= 67
    return true if @active_years >= 30
    return true if @age >= 60 && @active_years >= 25
    return true if @veteran && @active_years > 25

    false
  end
end

ขั้นตอนแรกคือการสร้างไฟล์ใหม่สำหรับการทดสอบ เพื่อให้เราสามารถเริ่มเพิ่มเข้าไปที่นั่นได้ เราได้เห็นบรรทัดแรกใน can_retire? วิธีการด้วย

  def can_retire?
    return true if @age >= 67
    ...
    ...
  end

ดังนั้น เราสามารถทดสอบกรณีนี้ก่อน:

# specs/worker_spec.rb
require_relative './../worker'

describe Worker do
  describe 'can_retire?' do
    it "should return true if age is higher than 67" do

    end
  end
end

คำแนะนำโดยย่อ:เมื่อคุณทำงานกับ TCR ทุกครั้งที่คุณบันทึก การเปลี่ยนแปลงล่าสุดจะหายไปหากการทดสอบไม่ผ่าน ดังนั้นเราจึงต้องการมีโค้ดให้มากที่สุดเพื่อ "ตั้งค่า" การทดสอบก่อนที่จะเขียนและบันทึกบรรทัดหรือบรรทัดด้วยการยืนยันจริง ๆ

หากเราบันทึกไฟล์ด้านบนแบบนั้น เราก็สามารถเพิ่มบรรทัดสำหรับการทดสอบได้

require_relative './../worker'

describe Worker do
  describe 'can_retire?' do
    it "should return true if age is higher than 67" do
      expect(Worker.new(70, 10, false).can_retire?).to be_true ## This line can disappear when we save now
    end
  end
end

เมื่อเราบันทึก หากบรรทัดใหม่ไม่หายไป แสดงว่าเราทำได้ดีแล้ว การทดสอบผ่านไป!

กำลังเพิ่มการทดสอบ

เมื่อเราได้การทดสอบครั้งแรกแล้ว เราก็สามารถเพิ่มกรณีต่างๆ ได้มากขึ้นโดยคำนึงถึงกรณีเท็จด้วย หลังเลิกงาน เราก็มีแบบนี้:

# frozen_string_literal: true

require_relative './../worker'

describe Worker do
  describe 'can_retire?' do
    it 'should return true if age is higher than 67' do
      expect(Worker.new(70, 10, false).can_retire?).to be true
    end

    it 'should return true if age is 67' do
      expect(Worker.new(67, 10, false).can_retire?).to be true
    end

    it 'should return true if age is less than 67' do
      expect(Worker.new(50, 10, false).can_retire?).to be false
    end

    it 'should return true if active years is higher than 30' do
      expect(Worker.new(60, 31, false).can_retire?).to be true
    end

    it 'should return true if active years is 30' do
      expect(Worker.new(60, 30, false).can_retire?).to be true
    end
  end
end

ในทุกกรณี เราเขียนบล็อก "it" ก่อน บันทึก จากนั้นเพิ่มการยืนยันด้วย expect(...) .

ตามปกติแล้ว เราสามารถเพิ่มการทดสอบได้มากเท่าที่เป็นไปได้ แต่ควรหลีกเลี่ยงการเพิ่มมากเกินไปเมื่อเราค่อนข้างแน่ใจว่าครอบคลุมทุกอย่างแล้ว

ยังมีบางกรณีที่ต้องกล่าวถึง ดังนั้นเราจึงควรเพิ่มเพื่อความสมบูรณ์

การทดสอบครั้งสุดท้าย

นี่คือไฟล์ข้อมูลจำเพาะในรูปแบบสุดท้าย อย่างที่คุณเห็น เรายังสามารถเพิ่มกรณีอื่นๆ ได้ แต่ฉันคิดว่านี่เพียงพอที่จะแสดงให้เห็นกระบวนการของ TCR

# frozen_string_literal: true

require_relative './../worker'

describe Worker do
  describe 'can_retire?' do
    it 'should return true if age is higher than 67' do
      expect(Worker.new(70, 10, false).can_retire?).to be true
    end

    it 'should return true if age is 67' do
      expect(Worker.new(67, 10, false).can_retire?).to be true
    end

    it 'should return true if age is less than 67' do
      expect(Worker.new(50, 10, false).can_retire?).to be false
    end

    it 'should return true if active years is higher than 30' do
      expect(Worker.new(60, 31, false).can_retire?).to be true
    end

    it 'should return true if active years is 30' do
      expect(Worker.new(20, 30, false).can_retire?).to be true
    end

    it 'should return true if age is higher than 60 and active years is higher than 25' do
      expect(Worker.new(60, 30, false).can_retire?).to be true
    end

    it 'should return true if age is higher than 60 and active years is higher than 25' do
      expect(Worker.new(61, 30, false).can_retire?).to be true
    end

    it 'should return true if age is 60 and active years is higher than 25' do
      expect(Worker.new(60, 30, false).can_retire?).to be true
    end

    it 'should return true if age is higher than 60 and active years is 25' do
      expect(Worker.new(61, 25, false).can_retire?).to be true
    end

    it 'should return true if age is 60 and active years is 25' do
      expect(Worker.new(60, 25, false).can_retire?).to be true
    end

    it 'should return true if is veteran and active years is higher than 25' do
      expect(Worker.new(60, 25, false).can_retire?).to be true
    end
  end
end

วิธีการปรับโครงสร้างใหม่

หากคุณได้อ่านมาถึงตอนนี้ อาจมีบางอย่างที่รู้สึกผิดกับโค้ด เรามี "ตัวเลขมหัศจรรย์" มากมายที่ควรแยกออกมาเป็นค่าคงที่ ทั้งในการทดสอบและในคลาสผู้ปฏิบัติงาน

เรายังสามารถสร้างวิธีการส่วนตัวสำหรับแต่ละกรณีใน can_retire หลักได้หรือไม่ วิธีสาธารณะ

ฉันจะปล่อยให้การ refactoring ที่เป็นไปได้ทั้งสองแบบเป็นแบบฝึกหัดสำหรับคุณ อย่างไรก็ตาม ขณะนี้เรามีการทดสอบ ดังนั้นหากเราทำผิดพลาดในขั้นตอนใดพวกเขาจะบอกเรา

บทสรุป

ฉันแนะนำให้คุณลองใช้ TCR กับโครงการของคุณ เป็นการทดลองที่ราคาถูกมากเพราะคุณไม่จำเป็นต้องมีการผสานรวมแบบต่อเนื่องในเซิร์ฟเวอร์ภายนอกหรือการพึ่งพาไลบรารีใหม่ สิ่งที่คุณต้องมีคือวิธีดำเนินการคำสั่งทุกครั้งที่คุณบันทึกไฟล์บางไฟล์ในคอมพิวเตอร์ของคุณ

นอกจากนี้ยังให้ประสบการณ์ "การเล่นเกม" แก่คุณเมื่อเพิ่มการทดสอบ ซึ่งสนุกและน่าสนใจอยู่เสมอ นอกจากนี้ ระเบียบวินัยในการลบการทดสอบที่ล้มเหลวออกจากตัวแก้ไขของคุณจะทำให้คุณมีเครือข่ายความปลอดภัยเพิ่มเติมโดยยืนยันว่าการทดสอบที่คุณส่งไปยังที่เก็บนั้นผ่าน

ฉันหวังว่าคุณจะพบว่าเทคนิคใหม่นี้มีประโยชน์เมื่อต้องรับมือกับโค้ดเดิม ฉันใช้หลายครั้งในช่วงสองสามเดือนที่ผ่านมา และมันก็เป็นความยินดีเสมอมา

ทรัพยากรเพิ่มเติม

  • วิดีโอแนะนำที่ดี
  • แนวคิดของ Kent Beck เกี่ยวกับวิธีใช้ TCR ใน VS Code
  • ปลั๊กอินของ VS Code เพื่อเรียกใช้สคริปต์เมื่อบันทึก