- การทดสอบการรวมฐานข้อมูลโดยใช้ MariaDB เพื่อแทนที่ MySql
- การดำเนินการแอปพลิเคชันหลายภาษา
- การบันทึกไฟล์ลงในแอปพลิเคชันและข้อมูลเกี่ยวกับไฟล์เหล่านั้นลงในฐานข้อมูล
ประเภทของการทดสอบ
การทดสอบคืออะไร? ดังที่ Wiki กล่าวไว้: “ การทดสอบหรือการทดสอบเป็นวิธีการศึกษากระบวนการพื้นฐานของระบบโดยการวางระบบในสถานการณ์ที่แตกต่างกันและติดตามการเปลี่ยนแปลงที่สังเกตได้” กล่าวอีกนัยหนึ่ง นี่คือการทดสอบการทำงานที่ถูกต้องของระบบของเราในบางสถานการณ์ มาดูกันว่ามีการทดสอบประเภทใดบ้าง:-
การทดสอบหน่วยคือการทดสอบที่มีหน้าที่ทดสอบแต่ละโมดูลของระบบแยกกัน เป็นที่พึงประสงค์ว่าสิ่งเหล่านี้เป็นส่วนที่แบ่งได้น้อยที่สุดของระบบ เช่น โมดูล
-
การทดสอบระบบเป็นการทดสอบระดับสูงเพื่อทดสอบการทำงานของแอปพลิเคชันชิ้นใหญ่หรือระบบโดยรวม
-
การทดสอบการถดถอยคือการทดสอบที่ใช้ในการตรวจสอบว่าคุณลักษณะใหม่หรือการแก้ไขข้อบกพร่องส่งผลต่อฟังก์ชันการทำงานที่มีอยู่ของแอปพลิเคชันหรือไม่ และข้อบกพร่องเก่าปรากฏขึ้นอีกครั้งหรือไม่
-
การทดสอบฟังก์ชันคือการตรวจสอบความสอดคล้องของส่วนหนึ่งของแอปพลิเคชันตามข้อกำหนดที่ระบุไว้ในข้อมูลจำเพาะ เรื่องราวของผู้ใช้ ฯลฯ
ประเภทของการทดสอบการทำงาน:
- การทดสอบ "กล่องสีขาว"สำหรับการปฏิบัติตามส่วนหนึ่งของแอปพลิเคชันด้วยข้อกำหนดที่มีความรู้เกี่ยวกับการใช้งานระบบภายใน
- การทดสอบ "กล่องดำ"สำหรับการปฏิบัติตามส่วนหนึ่งของแอปพลิเคชันโดยปราศจากความรู้เกี่ยวกับการใช้งานระบบภายใน
- การทดสอบประสิทธิภาพคือการทดสอบประเภทหนึ่งที่เขียนขึ้นเพื่อกำหนดความเร็วที่ระบบหรือส่วนหนึ่งของระบบทำงานภายใต้ภาระงานที่กำหนด
- การทดสอบโหลด - การทดสอบที่ออกแบบมาเพื่อตรวจสอบความเสถียรของระบบภายใต้โหลดมาตรฐาน และเพื่อค้นหาจุดสูงสุดที่เป็นไปได้ที่แอปพลิเคชันทำงานอย่างถูกต้อง
- การทดสอบภาวะวิกฤตเป็นการทดสอบประเภทหนึ่งที่ออกแบบมาเพื่อตรวจสอบการทำงานของแอปพลิเคชันภายใต้โหลดที่ไม่ได้มาตรฐาน และเพื่อกำหนดจุดสูงสุดที่เป็นไปได้ที่ระบบจะไม่ขัดข้อง
- การทดสอบ ความปลอดภัย - การทดสอบที่ใช้เพื่อตรวจสอบความปลอดภัยของระบบ (จากการโจมตีของแฮกเกอร์ ไวรัส การเข้าถึงข้อมูลที่เป็นความลับโดยไม่ได้รับอนุญาต และความสุขอื่น ๆ ของชีวิต)
- การทดสอบการแปลคือการทดสอบการแปลสำหรับแอปพลิเคชัน
- การทดสอบการใช้งานคือการทดสอบประเภทหนึ่งที่มุ่งตรวจสอบการใช้งาน ความเข้าใจ ความน่าดึงดูดใจ และความสามารถในการเรียนรู้ของผู้ใช้ ทั้งหมดนี้ฟังดูดี แต่ในทางปฏิบัติมันทำงานอย่างไร? ง่ายมาก: ใช้ปิรามิดทดสอบของ Mike Cohn: นี่คือปิรามิดเวอร์ชันที่เรียบง่าย: ตอนนี้มันถูกแบ่งออกเป็นส่วนย่อย ๆ แต่วันนี้เราจะไม่บิดเบือนและพิจารณาตัวเลือกที่ง่ายที่สุด
-
หน่วย - การทดสอบหน่วยที่ใช้ในเลเยอร์ต่างๆ ของแอปพลิเคชัน ทดสอบตรรกะที่หารน้อยที่สุดของแอปพลิเคชัน เช่น คลาส แต่ส่วนใหญ่มักจะเป็นวิธีการ การทดสอบเหล่านี้มักจะพยายามแยกออกจากตรรกะภายนอกให้มากที่สุดเท่าที่จะเป็นไปได้ นั่นคือเพื่อสร้างภาพลวงตาว่าแอปพลิเคชันที่เหลือทำงานในโหมดมาตรฐาน
ควรมีการทดสอบเหล่านี้เป็นจำนวนมาก (มากกว่าประเภทอื่นๆ) เนื่องจากเป็นการทดสอบชิ้นเล็กๆ และมีน้ำหนักเบามาก ไม่ใช้ทรัพยากรมากนัก (โดยทรัพยากร ฉันหมายถึง RAM และเวลา)
-
บูรณาการ - การทดสอบบูรณาการ จะตรวจสอบชิ้นส่วนที่ใหญ่กว่าของระบบ กล่าวคือ เป็นการรวมกันของตรรกะหลายส่วน (วิธีการหรือคลาสหลายวิธี) หรือความถูกต้องของการทำงานกับส่วนประกอบภายนอก โดยปกติแล้วจะมีการทดสอบเหล่านี้น้อยกว่าการทดสอบหน่วยเนื่องจากมีน้ำหนักมากกว่า
เป็นตัวอย่างการทดสอบการรวม คุณสามารถพิจารณาเชื่อมต่อกับฐานข้อมูลและตรวจสอบการดำเนินการที่ถูกต้องของวิธีการทำงานกับฐานข้อมูล
-
UI - การทดสอบที่ตรวจสอบการทำงานของส่วนต่อประสานผู้ใช้ สิ่งเหล่านี้ส่งผลต่อตรรกะในทุกระดับของแอปพลิเคชัน ซึ่งเป็นสาเหตุที่เรียกว่า end-to-end ตามกฎแล้วมีจำนวนน้อยกว่ามาก ดังนั้นจึงเป็นเส้นทางที่หนักที่สุดและต้องตรวจสอบเส้นทางที่จำเป็น (ใช้) มากที่สุด
ในรูปด้านบน เราจะเห็นอัตราส่วนของพื้นที่ของส่วนต่างๆ ของรูปสามเหลี่ยม: สัดส่วนที่เท่ากันโดยประมาณจะคงอยู่ในจำนวนการทดสอบเหล่านี้ในการทำงานจริง
วันนี้เราจะมาดูรายละเอียดการทดสอบที่ใช้มากที่สุด - การทดสอบหน่วย เนื่องจากนักพัฒนา Java ที่เคารพตนเองทุกคนควรจะสามารถใช้งานได้ในระดับพื้นฐาน
- เนื้อหาเกี่ยวกับ Code Coverage บนJavaRushและHabré ;
- ทฤษฎีการทดสอบพื้นฐาน
- เรากำลังเขียนแบบทดสอบของเรา
- เราทำการทดสอบไม่ว่าจะผ่านหรือไม่ (เราเห็นว่าทุกอย่างเป็นสีแดง - อย่าเพิ่งตกใจ: ควรจะเป็นแบบนี้)
- เราเพิ่มโค้ดที่ควรเป็นไปตามการทดสอบนี้ (รันการทดสอบ)
- เราปรับโครงสร้างโค้ดใหม่
- การระบุข้อมูลที่จะทดสอบ (ฟิกซ์เจอร์)
- การใช้โค้ดภายใต้การทดสอบ (การเรียกวิธีการภายใต้การทดสอบ)
- ตรวจสอบผลลัพธ์และเปรียบเทียบกับผลลัพธ์ที่คาดหวัง
assertEquals(Object expecteds, Object actuals)
— ตรวจสอบว่าวัตถุที่ส่งเท่ากันหรือไม่assertTrue(boolean flag)
- ตรวจสอบว่าค่าที่ส่งคืนค่าจริงหรือไม่assertFalse(boolean flag)
- ตรวจสอบว่าค่าที่ส่งคืนค่าเท็จหรือไม่assertNull(Object object)
– ตรวจสอบว่าวัตถุนั้นเป็นโมฆะหรือไม่assertSame(Object firstObject, Object secondObject)
— ตรวจสอบว่าค่าที่ส่งผ่านอ้างถึงวัตถุเดียวกันหรือไม่assertThat(T t, Matcher<T> matcher)
— ตรวจสอบว่าเป็นไปตามเงื่อนไขที่ระบุในตัวจับคู่หรือไม่
แนวคิดหลักของการทดสอบหน่วย
ความครอบคลุมการทดสอบ (Code Coverage) เป็นหนึ่งในการประเมินคุณภาพการทดสอบแอปพลิเคชันหลัก นี่คือเปอร์เซ็นต์ของโค้ดที่ครอบคลุมโดยการทดสอบ (0-100%) ในทางปฏิบัติ หลายๆ คนไล่ตามเปอร์เซ็นต์นี้ ซึ่งฉันไม่เห็นด้วย เนื่องจากพวกเขาเริ่มเพิ่มการทดสอบในส่วนที่ไม่จำเป็น ตัวอย่างเช่น บริการของเรามีการดำเนินการ CRUD มาตรฐาน (สร้าง/รับ/อัปเดต/ลบ) โดยไม่มีตรรกะเพิ่มเติม วิธีการเหล่านี้เป็นเพียงตัวกลางที่มอบหมายงานให้กับเลเยอร์ที่ทำงานกับพื้นที่เก็บข้อมูลเท่านั้น ในสถานการณ์นี้ เราไม่มีอะไรต้องทดสอบ: บางทีวิธีนี้จะเรียกวิธีการจากเต่าหรือไม่ แต่ก็ไม่ร้ายแรง เพื่อประเมินความครอบคลุมของการทดสอบ มักจะใช้เครื่องมือเพิ่มเติม: JaCoCo, Cobertura, Clover, Emma เป็นต้น หากต้องการศึกษาปัญหานี้อย่างละเอียด โปรดเก็บบทความที่เหมาะสมไว้ 2-3 บทความ:ขั้นตอนการทดสอบ
การทดสอบประกอบด้วยสามขั้นตอน:สภาพแวดล้อมการทดสอบ
ตอนนี้เรามาลงมือทำธุรกิจกันดีกว่า มีสภาพแวดล้อมการทดสอบ (เฟรมเวิร์ก) มากมายสำหรับ Java ที่ได้รับความนิยมมากที่สุดคือ JUnit และ TestNG สำหรับการทบทวนของเรา เราใช้: การทดสอบ JUnit เป็นวิธีการที่มีอยู่ในคลาสที่ใช้สำหรับการทดสอบเท่านั้น โดยทั่วไปแล้วคลาสจะมีชื่อเหมือนกับคลาสที่กำลังทดสอบโดยมี +Test ในตอนท้าย ตัวอย่างเช่น CarService→ CarServiceTest ระบบ Maven build จะรวมคลาสดังกล่าวไว้ในพื้นที่ทดสอบโดยอัตโนมัติ จริงๆ แล้ว คลาสนี้เรียกว่าคลาสทดสอบ มาดูคำอธิบายประกอบพื้นฐานกันสักหน่อย: @Test - คำจำกัดความของวิธีนี้เป็นวิธีการทดสอบ (อันที่จริง วิธีการที่ทำเครื่องหมายด้วยคำอธิบายประกอบนี้คือการทดสอบหน่วย) @Before - ทำเครื่องหมายวิธีการที่จะดำเนินการก่อนการทดสอบแต่ละครั้ง ตัวอย่างเช่น การกรอกข้อมูลการทดสอบคลาส การอ่านข้อมูลอินพุต ฯลฯ @After - วางไว้เหนือเมธอดที่จะถูกเรียกหลังการทดสอบแต่ละครั้ง (การล้างข้อมูล การเรียกคืนค่าเริ่มต้น) @BeforeClass - วางไว้เหนือวิธีการ - คล้ายกับ @Before แต่วิธีนี้จะถูกเรียกเพียงครั้งเดียวก่อนการทดสอบทั้งหมดสำหรับคลาสที่กำหนด ดังนั้นจึงต้องเป็นแบบคงที่ ใช้เพื่อดำเนินการงานหนักมากขึ้น เช่น การยกฐานข้อมูลทดสอบ @AfterClassตรงกันข้ามกับ @BeforeClass: ดำเนินการหนึ่งครั้งสำหรับคลาสที่กำหนด แต่ดำเนินการหลังจากการทดสอบทั้งหมด ใช้เพื่อล้างทรัพยากรถาวรหรือตัดการเชื่อมต่อจากฐานข้อมูล @Ignore - โปรดทราบว่าวิธีการด้านล่างถูกปิดใช้งานและจะถูกละเว้นเมื่อทำการทดสอบโดยรวม มันถูกใช้ในกรณีที่แตกต่างกัน เช่น หากวิธีการพื้นฐานมีการเปลี่ยนแปลงและไม่มีเวลาที่จะทำการทดสอบซ้ำ ในกรณีเช่นนี้ ขอแนะนำให้เพิ่มคำอธิบาย - @Ignore("Some description") @Test (expected = Exception.class) - ใช้สำหรับการทดสอบเชิงลบ สิ่งเหล่านี้คือการทดสอบที่ตรวจสอบว่าวิธีการทำงานอย่างไรในกรณีที่เกิดข้อผิดพลาด กล่าวคือ การทดสอบคาดว่าวิธีการดังกล่าวจะสร้างข้อยกเว้นบางประการ วิธีการดังกล่าวแสดงโดยคำอธิบายประกอบ @Test แต่มีข้อผิดพลาดในการตรวจพบ @Test(timeout=100) - ตรวจสอบว่าเมธอดดำเนินการภายในเวลาไม่เกิน 100 มิลลิวินาที @Mock - คลาสถูกใช้บนฟิลด์เพื่อตั้งค่าวัตถุที่กำหนดให้เป็นแบบจำลอง (นี่ไม่ใช่จากไลบรารี Junit แต่มาจาก Mockito) และหากเราต้องการมัน เราจะตั้งค่าพฤติกรรมของการจำลองในสถานการณ์เฉพาะ โดยตรงในวิธีการทดสอบ @RunWith(MockitoJUnitRunner.class) - วิธีการถูกวางไว้เหนือชั้นเรียน นี่คือปุ่มสำหรับเรียกใช้การทดสอบในนั้น Runner อาจแตกต่างกันได้ เช่น MockitoJUnitRunner, JUnitPlatform, SpringRunner เป็นต้น) ใน JUnit 5 คำอธิบายประกอบ @RunWith ถูกแทนที่ด้วยคำอธิบายประกอบ @ExtendWith ที่ทรงพลังกว่า มาดูวิธีการบางอย่างในการเปรียบเทียบผลลัพธ์กัน:assertThat(firstObject).isEqualTo(secondObject)
ที่นี่ฉันได้พูดถึงวิธีการพื้นฐานแล้ว เนื่องจากส่วนที่เหลือเป็นรูปแบบที่แตกต่างกันไปจากที่กล่าวมาข้างต้น
การทดสอบการปฏิบัติ
ตอนนี้เรามาดูเนื้อหาข้างต้นโดยใช้ตัวอย่างที่เฉพาะเจาะจง เราจะทดสอบวิธีการใช้บริการ - อัปเดต เราจะไม่พิจารณาชั้น dao เนื่องจากเป็นค่าเริ่มต้นของเรา มาเพิ่มตัวเริ่มต้นสำหรับการทดสอบ:<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.2.RELEASE</version>
<scope>test</scope>
</dependency>
ดังนั้นคลาสบริการ:
@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
private final RobotDAO robotDAO;
@Override
public Robot update(Long id, Robot robot) {
Robot found = robotDAO.findById(id);
return robotDAO.update(Robot.builder()
.id(id)
.name(robot.getName() != null ? robot.getName() : found.getName())
.cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
.producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
.build());
}
}
8 - ดึงวัตถุที่อัปเดตจากฐานข้อมูล 9-14 - สร้างวัตถุผ่านตัวสร้างหากวัตถุที่เข้ามามีฟิลด์ - ตั้งค่าถ้าไม่ใช่ - ปล่อยสิ่งที่อยู่ในฐานข้อมูล และดูการทดสอบของเรา:
@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
@Mock
private RobotDAO robotDAO;
private RobotServiceImpl robotService;
private static Robot testRobot;
@BeforeClass
public static void prepareTestData() {
testRobot = Robot
.builder()
.id(123L)
.name("testRobotMolly")
.cpu("Intel Core i7-9700K")
.producer("China")
.build();
}
@Before
public void init() {
robotService = new RobotServiceImpl(robotDAO);
}
1 — Runner 4 ของเรา — แยกบริการออกจากเลเยอร์ dao โดยการแทนที่การจำลอง 11 — ตั้งค่าเอนทิตีการทดสอบสำหรับคลาส (อันที่เราจะใช้เป็นหนูแฮมสเตอร์ทดสอบ) 22 — ตั้งค่าวัตถุบริการที่เราจะทดสอบ
@Test
public void updateTest() {
when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
Robot robotForUpdate = Robot
.builder()
.name("Vally")
.cpu("AMD Ryzen 7 2700X")
.build();
Robot resultRobot = robotService.update(123L, robotForUpdate);
assertNotNull(resultRobot);
assertSame(resultRobot.getId(),testRobot.getId());
assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
ที่นี่เราเห็นการแบ่งการทดสอบออกเป็นสามส่วนอย่างชัดเจน: 3-9 - การตั้งค่าการติดตั้ง 11 - การดำเนินการทดสอบส่วนที่ 13-17 - การตรวจสอบผลลัพธ์ รายละเอียดเพิ่มเติม: 3-4 - การตั้งค่าพฤติกรรมสำหรับโมก้าดาว 5 - การตั้งค่า อินสแตนซ์ที่เราจะอัปเดตเพิ่มเติมจากมาตรฐานของเรา 11 - ใช้วิธีการและรับอินสแตนซ์ผลลัพธ์ 13 - ตรวจสอบว่าไม่ใช่ศูนย์ 14 - ตรวจสอบรหัสผลลัพธ์และอาร์กิวเมนต์ของวิธีการที่ระบุ 15 - ตรวจสอบว่าชื่อได้รับการอัปเดตหรือไม่ 16 - ดูผลลัพธ์ด้วย cpu 17 - เนื่องจากเราไม่ได้ตั้งค่านี้ในฟิลด์อินสแตนซ์การอัปเดต จึงควรคงเหมือนเดิม มาตรวจสอบกัน เริ่มกันเลย: การทดสอบเป็นสีเขียว คุณสามารถหายใจออกได้)) สรุป:การทดสอบช่วยปรับปรุงคุณภาพของโค้ดและทำให้กระบวนการพัฒนามีความยืดหยุ่นและเชื่อถือได้มากขึ้น ลองนึกภาพว่าเราต้องใช้ความพยายามมากเพียงใดในการออกแบบซอฟต์แวร์ใหม่ด้วยไฟล์คลาสหลายร้อยไฟล์ เมื่อเราเขียน Unit Test สำหรับคลาสเหล่านี้ทั้งหมดแล้ว เราก็สามารถปรับเปลี่ยนโครงสร้างใหม่ได้อย่างมั่นใจ และที่สำคัญช่วยให้เราค้นหาข้อผิดพลาดระหว่างการพัฒนาได้ง่าย พวกนั่นคือทั้งหมดสำหรับฉันในวันนี้: กดไลค์เขียนความคิดเห็น)))
GO TO FULL VERSION