การทดสอบหน่วยใน Java คืออะไร
ก่อนที่เราจะเรียนรู้เกี่ยวกับ JUnit ใน Java เรามาทำความเข้าใจคร่าวๆ กันก่อนว่า Unit Testing คืออะไร และเหตุใดจึงเป็นที่นิยม (หากคุณรู้เรื่องนี้อยู่แล้ว ให้ข้ามไปที่ 'ฉันจะเขียน JUnit Test ใน Java ได้อย่างไร') การทดสอบหน่วยใน Java ทำให้การพัฒนาซอฟต์แวร์ขนาดใหญ่มีประสิทธิภาพและง่ายดายมากขึ้น สามารถช่วยทั้งบุคคลและทีมลดเวลานับไม่ถ้วนในการดีบักและปรับปรุงกระบวนการทำงานร่วมกันอย่างมาก
https://junit.org/junit4/
แนวคิดที่สำคัญของการทดสอบหน่วยคือ: เขียนการทดสอบอะตอมของคุณลักษณะแต่ละรายการ (เรียกว่าการทดสอบหน่วย) และค่อยๆ เพิ่มคุณลักษณะเพิ่มเติมหลังจากการทดสอบและตรวจสอบให้แน่ใจว่าการทดสอบก่อนหน้านี้ทำงานได้ เป็นแนวคิดที่เรียบง่ายแต่ทรงพลังอย่างยิ่ง เพื่อเป็นตัวอย่างว่ากระบวนการนี้จะมีลักษณะอย่างไร ลองจินตนาการว่าคุณกำลังสร้างเครื่องคำนวณทางวิทยาศาสตร์เสมือนจริง นอกเหนือจากตัวดำเนินการทางคณิตศาสตร์ที่ชัดเจน (
+
,
-
,
x
,
%
) เครื่องคิดเลขนี้จะมีคุณสมบัติขั้นสูงที่ต้องใช้คุณสมบัติย่อยอื่นๆ ในการทำงาน ในการคำนวณเลขยกกำลัง เครื่องคิดเลขของคุณจะต้องสามารถคูณได้อย่างถูกต้อง ดังนั้นแนวทางการทดสอบหน่วยในการสร้างและทดสอบเครื่องคิดเลขนี้จะเป็น:
- เขียนฟังก์ชันการบวก ทดสอบอย่างระมัดระวัง เปลี่ยน ทำซ้ำจนกว่าจะได้ผล
- ทำเช่นเดียวกันกับฟังก์ชันการลบ การคูณ การหาร
- ใช้ตัวดำเนินการฐานเหล่านี้เพื่อเขียนฟังก์ชันตัวดำเนินการขั้นสูง เช่น เลขยกกำลัง จากนั้นทดสอบฟังก์ชันเหล่านั้นด้วย
สิ่งนี้ทำให้มั่นใจได้ว่าคุณสมบัติที่สร้างจากคุณสมบัติย่อยที่เล็กกว่าอื่นๆ ไม่เพียงแต่ทำงานได้อย่างถูกต้องตามสิทธิของตนเองเท่านั้น แต่ไม่มีคุณสมบัติย่อยที่ผิดพลาดอยู่ภายใน ตัวอย่างเช่น ถ้าฉันกำลังทดสอบฟังก์ชันเลขชี้กำลังและมีบางอย่างผิดพลาด ฉันรู้ว่าข้อผิดพลาดนั้นไม่ได้อยู่ในคุณลักษณะย่อยของการคูณ เนื่องจากฟังก์ชันการคูณได้รับการทดสอบอย่างกว้างขวางแล้ว สิ่งนี้ช่วยลดจำนวนโค้ดทั้งหมดที่ฉันต้องใช้ในการย้อนรอยและตรวจสอบเพื่อหาจุดบกพร่องได้อย่างมาก หวังว่าตัวอย่างเล็กๆ น้อยๆ นี้จะทำให้ชัดเจนว่ากระบวนการคิดเกี่ยวกับการทดสอบหน่วยมีโครงสร้างอย่างไร แต่การทดสอบหน่วยโต้ตอบกับกระบวนการพัฒนาซอฟต์แวร์ที่เหลืออย่างไร จะเกิดอะไรขึ้นถ้าคุณมีฟีเจอร์ที่ซับซ้อนมากขึ้น ซึ่งจำเป็นต้องทำงานและสื่อสารร่วมกันได้ การทดสอบหน่วยไม่เพียงพอเพื่อให้แน่ใจว่าคุณสมบัติที่ซับซ้อนดังกล่าวสามารถทำงานร่วมกันได้อย่างเหมาะสม ที่จริงแล้วเป็นเพียงขั้นตอนแรกของการทดสอบซอฟต์แวร์สี่ระดับ (ฉันใช้ตัวพิมพ์ใหญ่เพราะฉันอ้างถึงมาตรฐานอุตสาหกรรมหรือแนวทางทั่วไปในการทดสอบซอฟต์แวร์) สามขั้นตอนสุดท้ายคือ
การทดสอบการรวมระบบการ
ทดสอบระบบและ
การทดสอบการยอมรับ. ทั้งหมดนี้อาจหมายถึงสิ่งที่คุณคิดอย่างแน่นอน แต่ให้ฉันชี้แจง: การทดสอบการรวมเป็นสิ่งที่เราจะทำเพื่อให้มั่นใจว่า "คุณลักษณะที่ซับซ้อน" ที่กล่าวมาข้างต้นมีปฏิสัมพันธ์ซึ่งกันและกันอย่างเหมาะสม (เช่น ตรวจสอบให้แน่ใจว่าเครื่องคิดเลขสามารถรองรับ “3 + 7 * 4 - 2” ได้) การทดสอบระบบคือการทดสอบการออกแบบโดยรวมของระบบใดระบบหนึ่ง มักจะมีระบบหลายระบบที่มีฟีเจอร์ที่ซับซ้อนทำงานร่วมกันในผลิตภัณฑ์ ดังนั้นคุณจึงจัดกลุ่มเหล่านี้เป็นระบบต่างๆ และทดสอบทีละรายการ (เช่น หากคุณกำลังสร้างเครื่องคำนวณกราฟ ก่อนอื่นคุณต้องสร้าง 'ระบบ' เลขคณิตเพื่อจัดการกับตัวเลข ทดสอบจนกว่าจะทำงานได้ตามที่ตั้งใจ จากนั้นคุณจะต้องสร้างและทดสอบ 'ระบบ' กราฟเพื่อจัดการการถอน เช่น มันจะต่อยอดมาจากระบบเลขคณิต) การทดสอบการยอมรับคือการทดสอบระดับผู้ใช้ กำลังดูว่าระบบทั้งหมดสามารถทำงานประสานกันเพื่อสร้างผลิตภัณฑ์สำเร็จรูปที่พร้อมให้ผู้ใช้ยอมรับหรือไม่ (เช่น ผู้ใช้ทดสอบเครื่องคิดเลข) บางครั้งนักพัฒนาซอฟต์แวร์อาจเพิกเฉยต่อขั้นตอนสุดท้ายของกระบวนการนี้ เนื่องจากบริษัทต่างๆ มักจะมีพนักงานคนอื่นๆ ทำการทดสอบผู้ใช้ (เบต้า) แยกต่างหาก
ฉันจะเขียนการทดสอบ JUnit ใน Java ได้อย่างไร
ตอนนี้คุณมีความคิดที่ชัดเจนขึ้นเกี่ยวกับประโยชน์และข้อจำกัดของการทดสอบหน่วยแล้ว มาดูโค้ดบางส่วนกันดีกว่า! เราจะใช้เฟรมเวิร์กการทดสอบ Java ยอดนิยมที่เรียกว่า JUnit (อีกอันยอดนิยมคือ TestNG ซึ่งคุณสามารถใช้ได้หากต้องการ พวกมันคล้ายกันมาก ในรูปแบบวากยสัมพันธ์ TestNG ได้รับแรงบันดาลใจจาก JUnit) คุณสามารถดาวน์โหลดและติดตั้ง JUnit ได้ที่
นี่ สำหรับโค้ดตัวอย่างนี้ เราจะดำเนินการต่อจากตัวอย่าง 'เครื่องคิดเลขวิทยาศาสตร์' ที่ฉันพูดถึงก่อนหน้านี้ มันค่อนข้างง่ายที่จะคาดคะเน และโค้ดทดสอบก็ง่ายสุดๆ แนวทางปฏิบัติทั่วไปคือการเขียนชั้นเรียนแบบทดสอบแยกต่างหากสำหรับแต่ละชั้นเรียนของคุณ ดังนั้นนั่นคือสิ่งที่เราจะทำ สมมติว่า ณ จุดนี้ เรามี
Math.java
ไฟล์ที่มีฟังก์ชันทางคณิตศาสตร์ทั้งหมดอยู่ในนั้น (รวมถึง
Math.add
) และเรากำลังเขียน
MathTests.java
ไฟล์ในแพ็คเกจเดียวกัน ตอนนี้มาตั้งค่าคำสั่งการนำเข้าและเนื้อหาของคลาส: (คำถามสัมภาษณ์ JUnit ที่เป็นไปได้: คุณอาจถูกถามว่าจะวางการทดสอบ JUnit ไว้ที่ใด และจำเป็นต้องนำเข้าไฟล์ต้นฉบับหรือไม่ หากคุณกำลังเขียนคลาสทดสอบในแพ็คเกจเดียวกันกับ คลาสหลักของคุณ คุณไม่จำเป็นต้องมีคำสั่งนำเข้าสำหรับไฟล์ต้นฉบับของคุณในคลาสทดสอบ มิฉะนั้น ตรวจสอบให้แน่ใจว่าคุณกำลังนำเข้าไฟล์ต้นฉบับของคุณ!)
import org.junit.jupiter.Test; //gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; //less typing :)
public class MathTests {
//...
}
คำสั่งการนำเข้าแรกให้
@Test
ส่วนหัว แก่เรา เราเขียน '
@Test
' ไว้บนสุดของนิยามฟังก์ชันการทดสอบทั้งหมดโดยตรง เพื่อให้ JUnit รู้ว่านี่เป็นการทดสอบหน่วยเอกพจน์ที่สามารถรันแยกกันได้ ในภายหลัง ฉันจะแสดงให้คุณเห็นว่าคุณสามารถเรียกใช้การทดสอบเฉพาะหน่วยโดยใช้ส่วนหัวนี้ได้อย่างไร คำสั่งนำเข้าที่สองช่วยให้เราไม่ต้องพิมพ์เล็กน้อย ฟังก์ชัน JUnit หลักที่เราใช้ในการทดสอบฟังก์ชันของเราคือ to
Assert.assertEquals()
ซึ่งใช้พารามิเตอร์สองตัว (ค่าจริงและค่าที่คาดไว้) และตรวจสอบให้แน่ใจว่ามีค่าเท่ากัน การมีคำสั่งนำเข้าที่สองนี้ช่วยให้เราสามารถพิมพ์ "
assertEquals(...
" แทนที่จะต้องระบุทุกครั้งว่าเป็นส่วนหนึ่งของแพ็คเกจใด ทีนี้ลองเขียนกรณีทดสอบง่ายๆ เพื่อยืนยันว่า 2 + 2 เป็น 4 จริง ๆ !
import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :)
public class MathTests {
@Test
public void add_twoPlusTwo_returnsFour(){
final int expected = 4;
final int actual = Math.add(2, 2);
assertEquals(“2+2 is 4”, actual, expected);
}
}
มาดูห้าบรรทัดของฟังก์ชันทดสอบแต่ละบรรทัดและสิ่งที่พวกเขาทำกัน: บรรทัดที่ 5:
@Test
ส่วนหัวนี้ระบุว่าคำจำกัดความของฟังก์ชันด้านล่าง
add_twoPlusTwo_returnsFour()
เป็นฟังก์ชันทดสอบที่ JUnit สามารถเรียกใช้แยกกันได้ บรรทัดที่ 6: นี่คือลายเซ็นของฟังก์ชันสำหรับกรณีทดสอบของเรา กรณีทดสอบมักจะเป็นเอกพจน์เสมอ พวกเขาทดสอบเพียงหนึ่งตัวอย่างเฉพาะ เช่น 2+2=4 เป็นเรื่องปกติที่จะตั้งชื่อกรณีทดสอบของคุณในรูปแบบ "
[function]_[params]_returns[expected]()
," โดยที่
[function]
ชื่อของฟังก์ชันที่คุณกำลังทดสอบ
[params]
คือพารามิเตอร์ตัวอย่างเฉพาะที่คุณกำลังทดสอบ และ
[expected]
เป็นค่าส่งคืนที่คาดไว้ของฟังก์ชัน ฟังก์ชันการทดสอบมักจะมีประเภทการส่งคืนเป็น ''
void
' เสมอ เนื่องจากประเด็นหลักของฟังก์ชันทั้งหมดคือการเรียกใช้
assertEquals
ซึ่งจะส่งออกไปยังคอนโซลไม่ว่าการทดสอบของคุณจะผ่านหรือไม่ก็ตาม คุณไม่จำเป็นต้องส่งคืนข้อมูลอื่นใดที่ใดก็ได้ บรรทัดที่ 7: เราประกาศ
final
ตัวแปร ' ' ของประเภทการส่งคืนเป็น
Math.add (int)
และตั้งชื่อว่า 'คาดหวัง' ตามแบบแผน ค่าของมันคือคำตอบที่เราคาดหวัง (4) บรรทัดที่ 8: เราประกาศตัวแปร '
final
' ของประเภทการส่งคืนเป็น
Math.add (int)
และตั้งชื่อว่า 'จริง' ตามแบบแผน ค่าของมันเป็นผลมา
Math.add(2, 2)
จาก บรรทัดที่ 9: เส้นสีทอง นี่คือบรรทัดที่เปรียบเทียบค่าจริงและค่าที่คาดไว้ และบอกเราว่าเราผ่านการทดสอบก็ต่อเมื่อมีค่าเท่ากัน พารามิเตอร์แรกที่ส่งผ่าน “2+2 is 4” คือคำอธิบายของฟังก์ชันการทดสอบ
จะเกิดอะไรขึ้นถ้าฟังก์ชันของฉันควรมีข้อยกเว้น
หากตัวอย่างการทดสอบเฉพาะของคุณควรแสดงข้อยกเว้นแทนที่จะยืนยันว่าค่าจริงและค่าที่คาดไว้เท่ากัน JUnit มีวิธีอธิบายสิ่งนี้ในส่วน
@Test
หัว ลองดูตัวอย่างด้านล่าง สมมติว่าเรามีฟังก์ชันใน
Math.java
call
Math.divide
เราต้องการให้แน่ใจว่าอินพุตไม่สามารถหารด้วย 0 ได้ แต่การพยายามเรียก
Math.divide(a, 0)
ใช้ค่า 'a' ใดๆ ควรมีข้อยกเว้น (
ArithmeticException.class
) เราระบุไว้ในส่วนหัวดังนี้:
import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :)
public class MathTests {
@Test (expectedExceptions = ArithmeticException.class)
public void divide_byZero_throwsException() throws ArithmeticException{
Math.divide(1, 0);
}
}
คุณสามารถมีข้อยกเว้นได้มากกว่าหนึ่งรายการ
expectedExceptions
เพียงใช้วงเล็บและเครื่องหมายจุลภาคเพื่อแสดงรายการคลาสข้อยกเว้นของคุณ เช่น:
expectedException = {FirstException.class, SecondException.class, … }
ฉันจะรันการทดสอบ JUnit ใน Java ได้อย่างไร
วิธีเพิ่ม JUnit ไปที่ IntelliJ:
https://stackoverflow.com/questions/19330832/setting-up-junit-with-intellij-idea คุณสามารถรันโปรเจกต์ของคุณได้ตามปกติในการทดสอบ การเรียกใช้การทดสอบทั้งหมดในคลาสทดสอบจะดำเนินการตามลำดับตัวอักษร ใน JUnit 5 คุณสามารถเพิ่มลำดับความสำคัญให้กับการทดสอบได้โดยการเพิ่ม
@Order
แท็ก ตัวอย่าง:
@TestMethodOrder(OrderAnnotation.class)
public class Tests {
…
@Test
@Order(2)
public void a_test() { … }
@Test
@Order (1)
public void b_test() { … }
…
}
แม้ว่า
a_test()
มาก่อน
b_test()
ตามตัวอักษรและในโค้ด
b_test()
จะรันก่อน
a_test()
ที่นี่ เพราะ 1 มาก่อน 2 ตามลำดับ นั่นคือทั้งหมดสำหรับพื้นฐานของ JUnit ตอนนี้ เรามาจัดการกับคำถามสัมภาษณ์ JUnit ทั่วไปสองสามข้อ และเรียนรู้เพิ่มเติมเกี่ยวกับ JUnit ไปพร้อมกัน!
คำถามสัมภาษณ์ JUnit (ข้อมูลเพิ่มเติม)
ฉันได้รวบรวมคำถามสัมภาษณ์ JUnit ยอดนิยมไว้ที่นี่แล้ว หากคุณมีสิ่งที่จะเพิ่ม - อย่าลังเลที่จะทำเช่นนี้ในความคิดเห็นด้านล่าง
ถาม: คุณสามารถเรียกใช้วิธีการทดสอบแบบใดเพื่อให้การทดสอบไม่ผ่านโดยอัตโนมัติ A: ล้มเหลว (“คำอธิบายข้อผิดพลาดที่นี่!”); ถาม: คุณกำลังทดสอบคลาสสุนัข ในการทดสอบวัตถุ Dog คุณต้องสร้างอินสแตนซ์ก่อนจึงจะสามารถทำการทดสอบได้ คุณจึงเขียนฟังก์ชัน setUp() เพื่อสร้างตัวอย่าง Dog คุณต้องการเรียกใช้ฟังก์ชันนี้เพียงครั้งเดียวในระหว่างการทดสอบทั้งหมด คุณต้องใส่อะไรเหนือลายเซ็นของฟังก์ชัน setUp() เพื่อให้ JUnit รู้วิธีเรียกใช้ setUp() ก่อนที่จะรันการทดสอบ A: @BeforeClass (@BeforeAll ใน JUnit 5) Q:ลายเซ็นฟังก์ชันของฟังก์ชัน setUp() ที่อธิบายไว้ข้างต้นต้องเป็นอย่างไร A: โมฆะคงที่สาธารณะ ฟังก์ชันใดๆ ที่มี @BeforeClass (@BeforeAll ใน JUnit 5) หรือ @AfterClass (@AfterAll ใน JUnit 5) จะต้องเป็นแบบคงที่ ถาม: คุณทดสอบคลาส Dog เสร็จแล้ว คุณเขียนฟังก์ชัน void tearDown() ซึ่งจะล้างข้อมูลและพิมพ์ข้อมูลไปยังคอนโซลหลังการทดสอบแต่ละครั้ง คุณต้องการให้ฟังก์ชันนี้ทำงานหลังจากการทดสอบทุกครั้ง คุณต้องใส่อะไรเหนือลายเซ็นของฟังก์ชัน tearDown() เพื่อให้ JUnit รู้ว่าต้องรัน tearDown() หลังจากรันการทดสอบแต่ละครั้ง A: @After (@AfterEach ใน JUnit 5)
GO TO FULL VERSION