CodeGym /בלוג Java /Random-HE /הכל על בדיקת יחידות: טכניקות, מושגים, תרגול
John Squirrels
רָמָה
San Francisco

הכל על בדיקת יחידות: טכניקות, מושגים, תרגול

פורסם בקבוצה
היום לא תמצאו אפליקציה שאינה עטופה במבחנים, כך שהנושא הזה יהיה רלוונטי מתמיד למפתחים מתחילים: אי אפשר להצליח בלי מבחנים. הבה נבחן באילו סוגי בדיקות משתמשים באופן עקרוני, ולאחר מכן נלמד בפירוט את כל מה שצריך לדעת על בדיקת יחידות. הכל על בדיקת יחידות: טכניקות, מושגים, תרגול - 1

סוגי בדיקות

מה זה מבחן? על פי ויקיפדיה: "בדיקת תוכנה כוללת ביצוע של רכיב תוכנה או רכיב מערכת כדי להעריך נכס אחד או יותר של עניין." במילים אחרות, מדובר בבדיקה של תקינות המערכת שלנו במצבים מסוימים. ובכן, בוא נראה אילו סוגי בדיקות יש באופן כללי:
  • בדיקת יחידות - בדיקות שמטרתן לבדוק כל מודול של המערכת בנפרד. בדיקות אלו צריכות לחול על החלקים האטומיים הקטנים ביותר של המערכת, למשל מודולים.
  • בדיקת מערכת - בדיקה ברמה גבוהה כדי לבדוק את פעולתו של חלק גדול יותר מהאפליקציה או של המערכת כולה.
  • בדיקת רגרסיה - בדיקה המשמשת כדי לבדוק אם תכונות חדשות או תיקוני באגים משפיעים על הפונקציונליות הקיימת של האפליקציה או מציגים באגים ישנים.
  • בדיקה פונקציונלית - בדיקה האם חלק מהאפליקציה עומד בדרישות המפורטות במפרטים, בסיפורי המשתמש וכו'.

    סוגי בדיקות תפקודיות:

    • בדיקת קופסה לבנה - בדיקה האם חלק מהאפליקציה עומד בדרישות תוך הכרת היישום הפנימי של המערכת;
    • בדיקת קופסה שחורה - בדיקה אם חלק מהאפליקציה עומד בדרישות מבלי לדעת את היישום הפנימי של המערכת.

  • בדיקות ביצועים - בדיקות שנכתבות כדי לקבוע כיצד המערכת או חלק מהמערכת מתפקדים תחת עומס מסוים.
  • בדיקת עומס - בדיקות שנועדו לבדוק את יציבות המערכת בעומסים סטנדרטיים ולמצוא את העומס המרבי בו האפליקציה עדיין פועלת כהלכה.
  • בדיקת מאמץ - בדיקה שנועדה לבדוק את ביצועי האפליקציה בעומסים לא סטנדרטיים ולקבוע את העומס המרבי לפני כשל במערכת.
  • בדיקות אבטחה - בדיקות המשמשות לבדיקת אבטחת המערכת (מהאקרים, וירוסים, גישה לא מורשית לנתונים סודיים והתקפות מענגות אחרות).
  • בדיקת לוקליזציה - בדיקות של לוקליזציה של האפליקציה.
  • בדיקת שמישות - בדיקה שמטרתה לבדוק שימושיות, הבנה, אטרקטיביות ויכולת למידה.
כל זה נשמע טוב, אבל איך זה עובד בפועל? פָּשׁוּט! אנו משתמשים בפירמידת הבדיקה של מייק קון: הכל על בדיקת יחידות: טכניקות, מושגים, תרגול - 2זוהי גרסה פשוטה של ​​הפירמידה: כעת היא מחולקת לחלקים קטנים עוד יותר. אבל היום לא נהיה מתוחכמים מדי. נשקול את הגרסה הפשוטה ביותר.
  1. יחידה - סעיף זה מתייחס לבדיקות יחידה, אשר מיושמות בשכבות שונות של היישום. הם בודקים את היחידה הקטנה ביותר הניתנת לחלוקה של לוגיקה של יישום. לדוגמה, שיעורים, אבל לרוב שיטות. מבחנים אלו בדרך כלל מנסים ככל האפשר לבודד את הנבדק מכל היגיון חיצוני. כלומר, הם מנסים ליצור אשליה ששאר האפליקציה פועלת כמצופה.

    תמיד צריכות להיות הרבה מהבדיקות האלה (יותר מכל סוג אחר), מכיוון שהן בודקות חתיכות קטנות והן קלות מאוד, לא צורכות הרבה משאבים (כלומר זיכרון RAM וזמן).

  2. אינטגרציה - סעיף זה מתייחס לבדיקות אינטגרציה. בדיקה זו בודקת חלקים גדולים יותר של המערכת. כלומר, או שהוא משלב כמה פיסות לוגיקה (מספר שיטות או מחלקות), או שהוא בודק את נכונות האינטראקציה עם רכיב חיצוני. בדיקות אלו בדרך כלל קטנות יותר מבדיקות יחידה מכיוון שהן כבדות יותר.

    דוגמה למבחן אינטגרציה יכולה להיות חיבור למסד נתונים ובדיקת תקינות פעולת שיטות העבודה עמו.

  3. ממשק משתמש - סעיף זה מתייחס לבדיקות הבודקות את פעולת ממשק המשתמש. הם מערבים את ההיגיון בכל רמות האפליקציה, ולכן הם נקראים גם מבחני מקצה לקצה. ככלל, יש הרבה פחות מהם, כי הם המסורבלים ביותר וחייבים לבדוק את הנתיבים הנחוצים ביותר (המשומשים).

    בתמונה למעלה, אנו רואים שהחלקים השונים של המשולש משתנים בגודלם: בערך אותן פרופורציות קיימות במספר סוגי המבחנים השונים בעבודה אמיתית.

    היום נסתכל מקרוב על המבחנים הנפוצים ביותר, בדיקות יחידה, שכן כל מפתחי Java שמכבדים את עצמם צריכים להיות מסוגלים להשתמש בהם ברמה בסיסית.

מושגי מפתח בבדיקת יחידות

כיסוי מבחן (כיסוי קוד) הוא אחד המדדים העיקריים למידת הבדיקה של אפליקציה. זהו אחוז הקוד שמכוסה במבחנים (0-100%). בפועל, רבים רודפים אחרי אחוז זה כמטרה שלהם. זה משהו שאני לא מסכים איתו, מכיוון שזה אומר שמתחילים ליישם מבחנים היכן שאין בהם צורך. לדוגמה, נניח שיש לנו פעולות CRUD סטנדרטיות (צור/קבל/עדכן/מחק) בשירות שלנו ללא היגיון נוסף. שיטות אלו הן מתווכים בלבד המאצילים עבודה לשכבה שעובדת עם המאגר. במצב זה, אין לנו מה לבדוק, חוץ אולי אם השיטה הנתונה קוראת לשיטת DAO, אבל זו בדיחה. בדרך כלל משתמשים בכלים נוספים להערכת כיסוי המבחן: JaCoCo, Cobertura, Clover, Emma וכו'. למחקר מפורט יותר של נושא זה, הנה כמה מאמרים רלוונטיים: TDD מייצג פיתוח מונחה מבחן. בגישה זו, לפני ביצוע כל דבר אחר, אתה כותב מבחן שיבדוק קוד ספציפי. מסתבר שזו בדיקת קופסה שחורה: אנחנו יודעים שהקלט הוא ואנחנו יודעים מה הפלט צריך להיות. זה מאפשר להימנע משכפול קוד. פיתוח מונחה בדיקות מתחיל בתכנון ופיתוח בדיקות עבור כל פיסת פונקציונליות באפליקציה שלך. בגישת TDD, אנו יוצרים תחילה מבחן המגדיר ובודק את התנהגות הקוד. המטרה העיקרית של TDD היא להפוך את הקוד שלך ליותר מובן, פשוט יותר וללא שגיאות. הכל על בדיקת יחידות: טכניקות, מושגים, תרגול - 3הגישה מורכבת מהדברים הבאים:
  • אנחנו כותבים את המבחן שלנו.
  • אנחנו מפעילים את המבחן. באופן לא מפתיע, הוא נכשל, מכיוון שעדיין לא יישמנו את ההיגיון הנדרש.
  • הוסף את הקוד שגורם לבדיקה לעבור (אנחנו מפעילים את הבדיקה שוב).
  • אנחנו מחדשים את הקוד.
TDD מבוסס על בדיקות יחידה, מכיוון שהם אבני הבניין הקטנות ביותר בפירמידת אוטומציית הבדיקות. בעזרת בדיקות יחידה, אנו יכולים לבדוק את ההיגיון העסקי של כל מחלקה. BDD מייצג התפתחות מונעת התנהגות. גישה זו מבוססת על TDD. ליתר דיוק, הוא משתמש בדוגמאות בשפה פשוטה המסבירות את התנהגות המערכת עבור כל מי שמעורב בפיתוח. לא נעמיק במונח הזה, שכן הוא משפיע בעיקר על בודקים ואנליסטים עסקיים. מקרה מבחן הוא תרחיש המתאר את השלבים, התנאים הספציפיים והפרמטרים הנדרשים לבדיקת הקוד הנבדק. מתקן בדיקה הוא קוד שמגדיר את סביבת הבדיקה כדי לקבל את המצב הדרוש כדי שהשיטה הנבדקת תפעל בהצלחה. זוהי קבוצה מוגדרת מראש של אובייקטים והתנהגותם בתנאים מוגדרים.

שלבי הבדיקה

מבחן מורכב משלושה שלבים:
  • ציין נתוני בדיקה (מתקנים).
  • הפעילו את הקוד הנבדק (התקשרו לשיטה הנבדקת).
  • אמת את התוצאות והשוואה לתוצאות הצפויות.
הכל על בדיקת יחידות: טכניקות, מושגים, תרגול - 4כדי להבטיח מודולריות של הבדיקה, עליך לבודד משכבות אחרות של היישום. זה יכול להיעשות באמצעות בדל, לעג ומרגלים. מוקסים הם אובייקטים שניתן להתאים (לדוגמה, להתאים לכל מבחן). הם מאפשרים לנו לציין למה אנו מצפים מקריאות למתודה, כלומר התגובות הצפויות. אנו משתמשים באובייקטים מדומים כדי לוודא שאנו מקבלים את מה שאנו מצפים. קטעים מספקים תגובה מקודדת לשיחות במהלך הבדיקה. הם יכולים גם לאחסן מידע על השיחה (לדוגמה, פרמטרים או מספר השיחות). אלה מכונים לפעמים מרגלים. לפעמים אנשים מבלבלים בין המונחים בדל ולעג: ההבדל הוא שסטב לא בודק כלום - הוא רק מדמה מצב נתון. דומה הוא חפץ שיש לו ציפיות. למשל, שיטה נתונה חייבת להיקרא מספר מסוים של פעמים. במילים אחרות, המבחן שלך לעולם לא יישבר בגלל בדל, אבל אולי בגלל לעג.

סביבות בדיקה

אז, עכשיו לעניין. קיימות מספר סביבות בדיקה (מסגרות) זמינות עבור Java. הפופולריים שבהם הם JUnit ו-TestNG. לסקירה שלנו כאן, אנו משתמשים: הכל על בדיקת יחידות: טכניקות, מושגים, תרגול - 5מבחן JUnit הוא שיטה במחלקה המשמשת רק לבדיקה. הכיתה נקראת בדרך כלל זהה לכיתה שהיא בודקת, עם "מבחן" מצורף לסוף. לדוגמה, CarService -> CarServiceTest. מערכת הבנייה של Maven כוללת באופן אוטומטי מחלקות כאלה בהיקף הבדיקה. למעשה, מחלקה זו נקראת כיתת מבחן. בואו נעבור בקצרה על ההערות הבסיסיות:

  • @Test מציין שהשיטה היא מבחן (בעצם, שיטה המסומנת בהערה זו היא מבחן יחידה).
  • @Before מסמל שיטה שתתבצע לפני כל בדיקה. לדוגמה, כדי לאכלס מחלקה בנתוני בדיקה, קריאת נתוני קלט וכו'.
  • @After משמש לסימון שיטה שתיקרא לאחר כל בדיקה (למשל כדי לנקות נתונים או לשחזר ערכי ברירת מחדל).
  • @BeforeClass ממוקם מעל שיטה, בדומה ל-@Before. אבל שיטה כזו נקראת פעם אחת בלבד לפני כל המבחנים עבור המחלקה הנתונה ולכן חייבת להיות סטטית. הוא משמש לביצוע פעולות עתירות משאבים יותר, כגון יצירת מסד נתונים בדיקה.
  • @AfterClass הוא ההפך מ-@BeforeClass: הוא מבוצע פעם אחת עבור המחלקה הנתונה, אך רק לאחר כל המבחנים. הוא משמש, למשל, לניקוי משאבים קבועים או ניתוק ממסד נתונים.
  • @התעלם מציין ששיטה מושבתת ותתעלם ממנה במהלך ריצת הבדיקה הכוללת. זה משמש במצבים שונים, למשל, אם שיטת הבסיס שונתה והבדיקה עדיין לא עובדה מחדש כדי להתאים את השינויים. במקרים כאלה, רצוי להוסיף גם תיאור, כלומר @Ignore("Some description").
  • @Test(expected = Exception.class) משמש לבדיקות שליליות. אלו בדיקות שמאמתות כיצד השיטה מתנהגת במקרה של שגיאה, כלומר, הבדיקה מצפה שהשיטה תזרוק חריגה כלשהי. שיטה כזו מסומנת על ידי הערת @Test, אך עם ציון איזו שגיאה לתפוס.
  • @Test(timeout = 100) בודק שהשיטה מבוצעת תוך לא יותר מ-100 אלפיות שניות.
  • @Mock משמש מעל שדה כדי להקצות אובייקט מדמה (זה לא ביאור JUnit, אלא מגיע מ-Mockito). לפי הצורך, אנו קובעים את התנהגות המדומה למצב ספציפי ישירות בשיטת הבדיקה.
  • @RunWith(MockitoJUnitRunner.class) ממוקם מעל מחלקה. ביאור זה אומר ל-JUnit להפעיל את המבחנים בכיתה. ישנם רצים שונים, כולל אלה: MockitoJUnitRunner, JUnitPlatform ו-SpringRunner. ב-JUnit 5, ההערה @RunWith הוחלפה בהערת @ExtendWith החזקה יותר.
בואו נסתכל על כמה שיטות המשמשות להשוואת תוצאות:

  • assertEquals(Object expects, Object actuals) - בודק אם האובייקטים שעברו שווים.
  • assertTrue(דגל בוליאני) - בודק אם הערך המועבר נכון.
  • assertFalse(דגל בוליאני) - בודק אם הערך שעבר הוא שקר.
  • assertNull(Object object) - בודק אם האובייקט המועבר הוא null.
  • assertSame(Object firstObject, Object secondObject) - בודק אם הערכים שהועברו מתייחסים לאותו אובייקט.
  • assertThat(T t, Matcher matcher) - בודק אם t עומד בתנאי שצוין ב-matcher.
AssertJ מספק גם שיטת השוואה שימושית: 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 - הרץ שלנו. שורה 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 - קבעו את ההתנהגות של ה-DAO הדוגמנית. שורה 5 - הגדר את המופע שנעדכן על התקן שלנו. שורה 11 - השתמשו בשיטה וקחו את המופע המתקבל. שורה 13 - בדוק שהיא לא ריק. שורה 14 - השווה את מזהה התוצאה ואת ארגומנטי השיטה הנתונים. שורה 15 - בדוק אם השם עודכן. שורה 16 ​​- ראה את תוצאת המעבד. שורה 17 - לא ציינו את השדה הזה במופע, אז הוא צריך להישאר זהה. אנחנו בודקים את המצב הזה כאן. בואו נריץ את זה: הכל על בדיקת יחידות: טכניקות, מושגים, תרגול - 6המבחן ירוק! נוכל לנשום לרווחה :) לסיכום, בדיקה משפרת את איכות הקוד והופכת את תהליך הפיתוח לגמיש ואמין יותר. תארו לעצמכם כמה מאמץ נדרש כדי לעצב מחדש תוכנה הכוללת מאות קבצי כיתה. כשיש לנו מבחנים ליחידה שנכתבו לכל הכיתות האלה, אנחנו יכולים לבצע מחדש בביטחון. והכי חשוב, זה עוזר לנו למצוא בקלות באגים במהלך הפיתוח. חברים ובנות, זה כל מה שיש לי היום. תנו לי לייק והשאירו תגובה :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION