วิธีการเขียนการทดสอบหน่วย "ดี"?


61

ที่ถูกเรียกโดยกระทู้นี้ฉัน (อีกครั้ง) กำลังคิดเกี่ยวกับการใช้การทดสอบหน่วยในโครงการของฉันในที่สุด มีผู้โพสต์สองสามคนพูดว่า "การทดสอบนั้นยอดเยี่ยมถ้าพวกเขาทำการทดสอบที่ดี" คำถามของฉันตอนนี้: การทดสอบ "ดี" คืออะไร?

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


8
การทดสอบหน่วยที่ดีใด ๆ ควรทดสอบสิ่งหนึ่งเท่านั้น - ถ้ามันล้มเหลวคุณควรรู้ว่าอะไรผิด
gablin

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

2
@ แกบลิน "ถ้ามันล้มเหลวคุณควรรู้ว่าสิ่งที่ผิดพลาด" จะแนะนำว่าการทดสอบที่มีสาเหตุของความล้มเหลวหลายอย่างนั้นไม่เป็นไรตราบใดที่คุณสามารถหาสาเหตุจากผลลัพธ์ของการทดสอบ ... ?
user253751

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

คำตอบ:


52

ศิลปะการทดสอบหน่วยมีดังต่อไปนี้ที่จะพูดเกี่ยวกับการทดสอบหน่วย:

การทดสอบหน่วยควรมีคุณสมบัติดังต่อไปนี้:

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

จากนั้นต่อมาจึงเพิ่มว่าควรเป็นแบบอัตโนมัติเต็มรูปแบบเชื่อถือได้อ่านได้และบำรุงรักษาได้

ฉันขอแนะนำให้อ่านหนังสือเล่มนี้ถ้าคุณยังไม่ได้ทำ

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


1
+1 สำหรับรายการที่ครอบคลุมการกำหนดเป้าหมายที่หน่วยทดสอบ (ไม่ได้บูรณาการหรือการทดสอบการทำงาน)
แกรี่ Rowe

1
+1 สำหรับลิงก์ วัสดุที่น่าสนใจที่จะพบว่ามี
Joris Meys

1
"เรียกใช้อย่างรวดเร็ว" มีความหมายอย่างมาก นี่เป็นเหตุผลหนึ่งว่าทำไมการทดสอบหน่วยควรทำงานแยกออกจากแหล่งข้อมูลภายนอกเช่นฐานข้อมูลระบบไฟล์บริการเว็บเป็นต้นสิ่งนี้จะนำไปสู่ ​​mocks / stubs
Michael Easter

1
เมื่อมีการระบุว่าIt should run at the push of a buttonนั่นหมายความว่าการทดสอบหน่วยไม่ควรใช้คอนเทนเนอร์ (แอปเซิร์ฟเวอร์) กำลังทำงาน (สำหรับหน่วยที่กำลังทดสอบ) หรือการเชื่อมต่อทรัพยากร (เช่น DB, บริการเว็บภายนอก ฯลฯ )? ฉันสับสนว่าส่วนใดของแอปพลิเคชั่นที่ควรได้รับการทดสอบโดยหน่วยและไม่ควรใช้ ฉันได้รับแจ้งว่าการทดสอบหน่วยไม่ควรขึ้นอยู่กับการเชื่อมต่อฐานข้อมูลและการเรียกใช้คอนเทนเนอร์และอาจใช้ mockups แทน
สัตว์สะเทินน้ำสะเทินบก

42

การทดสอบหน่วยที่ดีไม่ได้สะท้อนฟังก์ชั่นการทดสอบ

เป็นตัวอย่างที่ง่ายมากพิจารณาว่าคุณมีฟังก์ชันที่คืนค่าเฉลี่ยของ int สองตัว การทดสอบที่ครอบคลุมมากที่สุดจะเรียกใช้ฟังก์ชั่นและตรวจสอบว่าผลลัพธ์นั้นเป็นค่าเฉลี่ยหรือไม่ สิ่งนี้ไม่สมเหตุสมผลเลย: คุณกำลังทำมิเรอร์ (จำลอง) ฟังก์ชันการทำงานที่คุณกำลังทดสอบ หากคุณทำผิดพลาดในฟังก์ชั่นหลักคุณจะทำผิดพลาดเหมือนกันในการทดสอบ

กล่าวอีกนัยหนึ่งถ้าคุณพบว่าตัวเองเลียนแบบฟังก์ชั่นหลักในการทดสอบหน่วยนั่นเป็นสัญญาณที่บ่งบอกว่าคุณเสียเวลา


21
+1 สิ่งที่คุณจะทำในกรณีนี้คือทดสอบกับอาร์กิวเมนต์ hardcoded และตรวจสอบกับคำตอบที่คุณรู้จัก
Michael K

ฉันเคยเห็นกลิ่นนั้นมาก่อน
Paul Butcher

คุณช่วยยกตัวอย่างการทดสอบหน่วยที่ดีสำหรับฟังก์ชั่นที่ส่งกลับค่าเฉลี่ยได้ไหม
VLAS

2
@VLAS ทดสอบค่าที่กำหนดไว้ล่วงหน้าเช่นตรวจสอบให้แน่ใจ avg (1, 3) == 2 และที่สำคัญกว่านั้นคือเช็คขอบเคสเช่น INT_MAX, เลขศูนย์, ค่าลบ, ฯลฯ หากพบข้อบกพร่องและแก้ไขในฟังก์ชันให้เพิ่มอีก ทดสอบเพื่อให้แน่ใจว่าข้อผิดพลาดนี้จะไม่แนะนำอีกครั้ง
mojuba

น่าสนใจ คุณเสนอให้รับคำตอบที่ถูกต้องสำหรับอินพุตการทดสอบเหล่านั้นอย่างไรและอาจไม่ทำผิดพลาดเหมือนกับรหัสที่อยู่ภายใต้การทดสอบ
Timo

10

การทดสอบหน่วยที่ดีนั้นเป็นข้อมูลจำเพาะในรูปแบบที่รันได้:

  1. อธิบายพฤติกรรมของรหัสที่สอดคล้องกับการใช้เคส
  2. ครอบคลุมกรณีมุมทางเทคนิค (สิ่งที่เกิดขึ้นหากผ่านเป็นโมฆะ) - หากไม่มีการทดสอบสำหรับกรณีมุมมุมพฤติกรรมจะไม่ได้กำหนด
  3. แตกถ้ารหัสทดสอบการเปลี่ยนแปลงออกไปจากสเปค

ฉันพบว่า Test-Driven-Development นั้นเหมาะสำหรับไลบรารีรูทีนเป็นอย่างมากในขณะที่คุณเขียน API ก่อนแล้วจึงนำไปใช้จริง


7

สำหรับ TDD ทดสอบการทดสอบ "ดี" ให้บริการที่ลูกค้าต้องการ ; ฟีเจอร์ไม่จำเป็นต้องสอดคล้องกับฟังก์ชั่นและนักพัฒนาไม่ควรสร้างสถานการณ์การทดสอบในสุญญากาศ

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

ตัวอย่างเรื่อง:

ในฐานะ [X-Wing Pilot] ฉันต้องการ [ไม่เกิน 0.0001% พอดีกับข้อผิดพลาด] เพื่อให้ [คอมพิวเตอร์เป้าหมายสามารถโจมตีพอร์ตไอเสียของ Death Star เมื่อเคลื่อนที่ด้วยความเร็วเต็มที่ผ่านหุบเขาลึก]

ดังนั้นคุณจะไปคุยกับนักบิน (และคอมพิวเตอร์เป้าหมายถ้ามีความรู้สึก) ก่อนอื่นคุณพูดถึงสิ่งที่ 'ปกติ' แล้วพูดถึงสิ่งผิดปกติ คุณพบว่าสิ่งที่สำคัญในสถานการณ์นี้สิ่งที่เป็นเรื่องธรรมดาสิ่งที่ไม่น่าเป็นไปได้และสิ่งที่เป็นไปได้

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

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

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


5

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


5

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

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

นอกจากนี้ฉันไม่เชื่อในการเขียนแบบทดสอบเพียงเพื่อพูดว่า "ใช่เราทดสอบสิ่งนั้น" ถ้าฉันใช้ห้องสมุดที่ถูกทิ้งไว้และจะยังคงไม่เปลี่ยนรูปฉันจะไม่เสียวันทดสอบเขียนเพื่อให้แน่ใจว่าอวัยวะภายในของ API ที่จะไม่เปลี่ยนงานตามที่คาดไว้แม้ว่าจะมีบางส่วนของคะแนน สูงในกราฟโทร การทดสอบที่ใช้ไลบรารี่กล่าวว่า


แต่จะเกิดอะไรขึ้นในภายหลังเมื่อไลบรารีมีเวอร์ชันที่ใหม่กว่าพร้อมด้วยการแก้ไขข้อบกพร่อง?

@ Thorbjørn Ravn Andersen - ขึ้นอยู่กับห้องสมุดสิ่งที่เปลี่ยนแปลงและกระบวนการทดสอบของพวกเขาเอง ฉันจะไม่เขียนการทดสอบสำหรับรหัสที่ฉันรู้ว่าทำงานได้เมื่อฉันวางมันลงในสถานที่และไม่เคยสัมผัส ดังนั้นหากใช้งานได้หลังจากอัปเดตอย่าลืม :) แน่นอนว่ามีข้อยกเว้นอยู่
ทิมโพสต์

ถ้าคุณขึ้นอยู่กับห้องสมุดของคุณอย่างน้อยคุณสามารถทำได้คือการเขียนการทดสอบแสดงให้เห็นว่าสิ่งที่คุณคาดหวังกล่าวว่าห้องสมุดที่จริงการทำ ,

... และหากมีการเปลี่ยนแปลงทดสอบในสิ่งที่ใช้ห้องสมุดกล่าวว่า ... tl; dr; ฉันไม่จำเป็นต้องทดสอบอวัยวะภายในของรหัสบุคคลที่สาม ตอบรับการปรับปรุงเพื่อความชัดเจนแม้ว่า
Tim Post

4

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


3

ฉันพยายามทดสอบทุกอย่างทดสอบเพียงอย่างเดียว ฉันพยายามตั้งชื่อแบบทดสอบ DoDoSomething () ฉันพยายามทดสอบพฤติกรรมไม่ใช่การนำไปใช้ ฉันทดสอบวิธีสาธารณะเท่านั้น

ฉันมักจะมีหนึ่งหรือสองสามการทดสอบเพื่อความสำเร็จและอาจจะเป็นแบบทดสอบความล้มเหลวต่อวิธีการสาธารณะ

ฉันใช้จำลองมาก กรอบการจำลองที่ดีอาจเป็นประโยชน์อย่างมากเช่น PowerMock แม้ว่าฉันจะยังไม่ได้ใช้ก็ตาม

ถ้าคลาส A ใช้คลาส B อื่นฉันจะเพิ่มอินเตอร์เฟส X เพื่อให้ A ไม่ได้ใช้ B โดยตรง จากนั้นฉันก็จะสร้าง XMockup จำลองและใช้แทน B ในการทดสอบของฉัน มันช่วยเร่งการดำเนินการทดสอบลดความซับซ้อนของการทดสอบและลดจำนวนการทดสอบที่ฉันเขียนสำหรับ A เนื่องจากฉันไม่ต้องรับมือกับลักษณะเฉพาะของ B. ฉันสามารถยกตัวอย่างการทดสอบที่ A เรียก X.someMethod () แทนผลข้างเคียงของการโทร B.someMethod ()

ให้คุณทดสอบโค้ดที่สะอาดด้วยเช่นกัน

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


2

ฉันเห็นว่า Andry Lowry ได้โพสต์การวัดหน่วยทดสอบของ Roy Osherove แล้ว แต่ดูเหมือนว่าไม่มีใครเสนอชุด (ฟรี) ที่ลุงบ็อบมอบให้ในรหัสสะอาด (132-133) เขาใช้ตัวย่อ FIRST (นี่กับสรุปของฉัน):

  • เร็ว (พวกเขาควรวิ่งเร็วดังนั้นผู้คนจะไม่สนใจที่จะวิ่งหนี)
  • เป็นอิสระ (การทดสอบไม่ควรทำการเซ็ตอัพหรือฉีกขาดให้ซึ่งกันและกัน)
  • ทำซ้ำได้ (ควรทำงานในทุกสภาพแวดล้อม / แพลตฟอร์ม)
  • การตรวจสอบตนเอง (อัตโนมัติเต็มรูปแบบผลลัพธ์ควรเป็น "ผ่าน" หรือ "ล้มเหลว" ไม่ใช่ไฟล์บันทึก)
  • ทันเวลา (เมื่อจะเขียนพวกเขา - ก่อนที่จะเขียนรหัสการผลิตที่จะทดสอบ)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.