ใหม่ต่อการทดสอบหน่วยวิธีการเขียนการทดสอบที่ยอดเยี่ยม? [ปิด]


267

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

นี่เป็นงานที่ยิ่งใหญ่ส่วนใหญ่เป็นเพราะจำนวนชั้นเรียนที่จะทดสอบ แต่ก็เป็นเพราะการทดสอบการเขียนนั้นเป็นเรื่องใหม่สำหรับฉัน

ฉันเขียนข้อสอบไปแล้วหลายชั้น แต่ตอนนี้ฉันสงสัยว่าฉันทำถูกไหม

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

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

แก้ไข: ฉันชอบที่จะขอบคุณ Stack Overflow ฉันมีอินพุตที่ยอดเยี่ยมในเวลาน้อยกว่า 15 นาทีที่ตอบเวลาอ่านออนไลน์มากขึ้นที่ฉันเพิ่งทำ


1
นี่คือหนังสือที่ดีที่สุดสำหรับการทดสอบหน่วย: manning.com/osheroveมันอธิบายวิธีปฏิบัติที่ดีที่สุดทั้งหมดสิ่งที่ควรทำและสิ่งที่ไม่ควรทำสำหรับการทดสอบหน่วย
Ervi B

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

คำตอบ:


187

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

ฉันคิดว่าคุณทำผิด

การทดสอบหน่วยควร:

  • ทดสอบวิธีหนึ่ง
  • ให้ข้อโต้แย้งบางอย่างกับวิธีการนั้น
  • ทดสอบว่าผลลัพธ์เป็นไปตามที่คาดไว้

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

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

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

testAdd()
{
    int x = 5;
    int y = -2;
    int expectedResult = 3;
    Calculator calculator = new Calculator();
    int actualResult = calculator.Add(x, y);
    Assert.AreEqual(expectedResult, actualResult);
}

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


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

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

1
"การทดสอบหน่วยควรทดสอบวิธีหนึ่ง" จริง ๆ แล้วฉันไม่เห็นด้วย การทดสอบหน่วยควรทดสอบแนวคิดตรรกะหนึ่งข้อ แม้ว่าวิธีนี้มักจะถูกนำเสนอเป็นวิธีหนึ่ง แต่ก็ไม่ได้เป็นเช่นนั้นเสมอไป
robertmain

35

สำหรับการทดสอบหน่วยฉันพบทั้ง Test Driven (การทดสอบครั้งแรกรหัสที่สอง) และรหัสแรกการทดสอบที่สองนั้นมีประโยชน์อย่างยิ่ง

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

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

ตัวอย่างง่ายๆเช่นทั้งหมดfunction square(number)นั้นยอดเยี่ยมและทั้งหมดและอาจเป็นตัวเลือกที่ไม่ดีในการใช้เวลาทดสอบจำนวนมาก คนที่ทำตรรกะทางธุรกิจที่สำคัญนั่นคือที่การทดสอบมีความสำคัญ ทดสอบข้อกำหนด อย่าทดสอบท่อประปา หากข้อกำหนดมีการเปลี่ยนแปลงให้คาดเดาว่าจะต้องทำการทดสอบอะไรเช่นกัน

การทดสอบไม่ควรเป็นการทดสอบอย่างแท้จริงว่าฟังก์ชั่น foo เรียกใช้ฟังก์ชันบาร์ 3 ครั้ง ว่าเป็นสิ่งที่ผิด. ตรวจสอบว่าผลลัพธ์และผลข้างเคียงถูกต้องไม่ใช่กลไกภายใน


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

2
ตัวอย่างล่าสุดที่สมบูรณ์แบบ ฉันมีฟังก์ชั่นที่ง่ายมาก ผ่านมันจริงมันทำสิ่งหนึ่งผิดมันทำอย่างอื่น ง่ายมาก. มีการทดสอบ 4 รายการที่ตรวจสอบเพื่อให้แน่ใจว่าฟังก์ชั่นทำในสิ่งที่ตั้งใจจะทำ ฉันเปลี่ยนพฤติกรรมเล็กน้อย เรียกใช้การทดสอบ POW เป็นปัญหา สิ่งที่ตลกคือเมื่อใช้แอปพลิเคชันปัญหาจะไม่ปรากฏเฉพาะในกรณีที่ซับซ้อนเท่านั้น กรณีทดสอบพบมันและฉันช่วยตัวเองชั่วโมงปวดหัว
Dmitriy Likhten

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

18

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

การย้ายรหัสที่มีอยู่เป็นการทดสอบการพัฒนาแบบขับเคลื่อน

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


3
หากคุณเขียนการทดสอบครั้งแรกหรือครั้งที่สองมันเป็นเรื่องปกติ แต่เมื่อคุณเขียนการทดสอบคุณต้องแน่ใจว่าโค้ดของคุณนั้นสามารถทดสอบได้เพื่อที่คุณจะได้สามารถทำการทดสอบได้ คุณเลิกคิดว่า "ฉันจะทดสอบสิ่งนี้ได้อย่างไร" บ่อยครั้งที่ตัวของมันเองทำให้โค้ดที่เขียนดีกว่า การเพิ่มการทดสอบเป็นเรื่องที่ไม่สำคัญเสมอไป ยากมาก. ไม่ใช่ปัญหาเรื่องเวลาปัญหาเรื่องปริมาณและความสามารถในการทดสอบ ฉันไม่สามารถหาเจ้านายของฉันได้ในตอนนี้และบอกว่าฉันต้องการเขียนกรณีทดสอบให้กับตารางและการใช้งานของเรามากกว่าหนึ่งพันโต๊ะตอนนี้มันมากเกินไปจะใช้เวลาหนึ่งปีและตรรกะ / การตัดสินใจบางอย่างถูกลืม ดังนั้นอย่าวางไว้นานเกินไป: P
Dmitriy Likhten

2
สันนิษฐานว่าคำตอบที่ยอมรับมีการเปลี่ยนแปลง มีคำตอบจาก Linx ที่แนะนำศิลปะของการทดสอบหน่วยโดยรอย Osherove ของmanning.com/osherove
เธเลม

15

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

ทำให้การทดสอบของคุณมีขนาดเล็ก: หนึ่งการทดสอบต่อความต้องการ

ต่อมาเมื่อคุณต้องการทำการเปลี่ยนแปลง (หรือเขียนรหัสใหม่) ให้ลองเขียนการทดสอบหนึ่งครั้งก่อน แค่หนึ่ง. จากนั้นคุณจะเข้าสู่ขั้นตอนแรกในการพัฒนาที่ขับเคลื่อนด้วยการทดสอบ


ขอขอบคุณมันสมเหตุสมผลแล้วที่จะมีการทดสอบขนาดเล็กสำหรับความต้องการขนาดเล็กครั้งละหนึ่งรายการ บทเรียน.
pixelastic

13

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


ขอบคุณมาก! ฉันรู้สึกว่าฉันทำผิด แต่การมีคนบอกฉันจริงนั้นดีกว่า
pixelastic

8

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

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

คุณควรทำการทดสอบผลลัพธ์ของวิธีการเสมอไม่ใช่วิธีที่ได้รับผลลัพธ์เหล่านั้น


ใช่ฉันชอบที่จะสามารถทำเช่นนั้นได้ยกเว้นวิธีการที่เขียนไว้แล้ว ฉันแค่ต้องการทดสอบพวกเขา ฉันจะเขียนแบบทดสอบก่อนวิธีการในอนาคตท่อ
pixelastic

2
@pixelastic ทำเป็นว่าวิธีการ havent ถูกเขียน?
committedandroider

4

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

When I'm writing tests for a method, I have the feeling of rewriting a second time what I          
already wrote in the method itself.
My tests just seems so tightly bound to the method (testing all codepath, expecting some    
inner methods to be called a number of times, with certain arguments), that it seems that
if I ever refactor the method, the tests will fail even if the final behavior of the   
method did not change.

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


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

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

ยังเฉพาะเจาะจงกับความคาดหวังของคุณ อย่ายืนยันว่าการทดสอบคืนวัตถุให้ทดสอบว่าวัตถุนั้นมีค่าต่าง ๆ ทดสอบว่าเมื่อค่าควรเป็นศูนย์นั่นคือ นอกจากนี้คุณยังสามารถแยกย่อยได้โดยทำ refactoring ตามที่คุณต้องการหลังจากเพิ่มการทดสอบ
hvgotcodes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.