מדריך לבדיקות דינמיות בג'וניט 5

1. סקירה כללית

בדיקה דינמית היא מודל תכנות חדש שהוצג ב- JUnit 5. במאמר זה נבדוק מהן בדיוק מבחנים דינמיים וכיצד ליצור אותם.

אם אתה חדש לגמרי ב- JUnit 5, כדאי לך לבדוק את התצוגה המקדימה של JUnit 5 ואת המדריך העיקרי שלנו.

2. מה זה a DynamicTest?

המבחנים הסטנדרטיים עם הערות עם @מִבְחָן ביאור הם מבחנים סטטיים שצוינו במלואם בזמן הקומפילציה. א DynamicTest היא בדיקה שנוצרה בזמן ריצה. בדיקות אלה נוצרות על ידי שיטת מפעל המסומנת עם ה- @TestFactory ביאור.

א @TestFactory השיטה חייבת להחזיר א זרם, אוסף, ניתן לנידון, או איטרטור שֶׁל DynamicTest מקרים. החזרת כל דבר אחר תביא ל- JUnitException מכיוון שלא ניתן לזהות את סוגי ההחזרות הלא חוקיים בזמן הקומפילציה. מלבד זאת, א @TestFactory השיטה לא יכולה להיות סטטיג אוֹ פְּרָטִי.

ה DynamicTests מבוצעים באופן שונה מהסטנדרט @מִבְחָןואינם תומכים בשיחות חוזרות במהלך מחזור החיים. המשמעות, ה @ לפני כל אחד וה @אחרי כל אחד שיטות לא ייקראו עבור DynamicTestס.

3. יצירה מבחני DynamicTests

ראשית, בואו נסתכל על דרכי יצירה שונות DynamicTestס.

הדוגמאות כאן אינן דינאמיות במהותן, אך הן יספקו נקודת מוצא טובה ליצירת דוגמאות דינמיות באמת.

אנחנו הולכים ליצור אוסף שֶׁל DynamicTest:

@TestFactory Collection DynamicTestsWithCollection () {return Arrays.asList (DynamicTest.dynamicTest ("הוסף מבחן", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("הכפל בדיקה", ( ) -> assertEquals (4, Math.multiplyExact (2, 2)))); }

ה @TestFactory השיטה אומרת ל- JUnit כי זהו מפעל ליצירת בדיקות דינמיות. כפי שאנו רואים, אנו רק מחזירים א אוסף שֶׁל DynamicTest. כל אחד מה DynamicTest מורכב משני חלקים, שם הבדיקה או שם התצוגה, ו- ביצוע.

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

הוסף מבחן (dynamicTestsWithCollection ()) הכפל בדיקה (dynamicTestsWithCollection ())

ניתן לשנות את אותה בדיקה כדי להחזיר ניתן לנידון, איטרטור, או א זרם:

@TestFactory DynamicTestsWithIterable () {להחזיר Arrays.asList (DynamicTest.dynamicTest ("הוסף מבחן", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("הכפל בדיקה", ( ) -> assertEquals (4, Math.multiplyExact (2, 2)))); } @TestFactory Iterator dynamicTestsWithIterator () {return Arrays.asList (DynamicTest.dynamicTest ("הוסף מבחן", () -> assertEquals (2, Math.addExact (1, 1))), DynamicTest.dynamicTest ("Test Multiply", () -> assertEquals (4, Math.multiplyExact (2, 2)))) .iterator (); } @TestFactory זרם dynamicTestsFromIntStream () {החזר IntStream.iterate (0, n -> n + 2) .limit (10) .mapToObj (n -> DynamicTest.dynamicTest ("test" + n, () -> assertTrue (n % 2 == 0))); }

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

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

4. יצירת א זרם שֶׁל מבחני DynamicTests

למטרות הדגמה, שקול א DomainNameResolver שמחזירה כתובת IP כאשר אנו מעבירים את שם הדומיין כקלט.

למען הפשטות, בואו נסתכל על השלד ברמה הגבוהה של שיטת המפעל שלנו:

@TestFactory זרם dynamicTestsFromStream () {// קלט ופלט לדוגמא רשימת inputList = Arrays.asList ("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com"); רשימת outputList = Arrays.asList ("154.174.10.56", "211.152.104.132", "178.144.120.156"); // מחולל קלט שמייצר קלט באמצעות inputList /*... קוד כאן ... * / // מחולל שמות תצוגה שיוצר // שם אחר בהתבסס על קוד הקלט /*... כאן ... * / // מנהל הבדיקה, שלמעשה יש לו / / את ההיגיון לבצע את מקרה הבדיקה /*... קוד כאן ... * / // לשלב הכל ולהחזיר כאן זרם של DynamicTest /*... code ... * /}

אין הרבה קוד שקשור ל DynamicTest כאן מלבד @TestFactory ביאור, שאנו כבר מכירים.

השניים רשימת מערךs ישמש כקלט ל- DomainNameResolver והתפוקה הצפויה בהתאמה.

בואו נסתכל על מחולל הקלט:

Interator inputGenerator = inputList.iterator ();

מחולל הקלט אינו אלא איטרטור שֶׁל חוּט. זה משתמש שלנו רשימת קלט ומחזיר את שם התחום בזה אחר זה.

מחולל שמות התצוגה פשוט למדי:

פונקציה displayNameGenerator = (קלט) -> "פתרון:" + קלט;

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

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

עכשיו בואו נסתכל על החלק המרכזי של הבדיקה שלנו - קוד ביצוע הבדיקה:

DomainNameResolver resolver = new DomainNameResolver (); ThrowingConsumer testExecutor = (קלט) -> {int id = inputList.indexOf (קלט); assertEquals (outputList.get (id), resolver.resolveDomain (קלט)); };

השתמשנו ב- ThrowingConsumer, שהוא א @FunctionalInterface לכתיבת מקרה המבחן. עבור כל קלט שנוצר על ידי מחולל הנתונים, אנו מביאים את הפלט הצפוי מה- פלט רשימה והתפוקה בפועל ממופע של DomainNameResolver.

עכשיו החלק האחרון הוא פשוט להרכיב את כל החלקים ולחזור כ- זרם שֶׁל DynamicTest:

להחזיר את DynamicTest.stream (inputGenerator, displayNameGenerator, testExecutor);

זהו זה. הפעלת הבדיקה תציג את הדוח המכיל את השמות שהוגדרו על ידי מחולל שמות התצוגה שלנו:

פתרון: www.somedomain.com (dynamicTestsFromStream ()) פתרון: www.anotherdomain.com (dynamicTestsFromStream ()) פתרון: www.yetanotherdomain.com (dynamicTestsFromStream ())

5. שיפור ה DynamicTest שימוש בתכונות Java 8

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

@TestFactory זרם dynamicTestsFromStreamInJava8 () {DomainNameResolver resolver = new DomainNameResolver (); רשימה domainNames = Arrays.asList ("www.somedomain.com", "www.anotherdomain.com", "www.yetanotherdomain.com"); רשימת outputList = Arrays.asList ("154.174.10.56", "211.152.104.132", "178.144.120.156"); להחזיר inputList.stream () .map (dom -> DynamicTest.dynamicTest ("פתרון:" + dom, () -> {int id = inputList.indexOf (dom); assertEquals (outputList.get (id), resolver.resolveDomain) (dom));})); }

לקוד הנ"ל יש את אותה השפעה לזה שראינו בפרק הקודם. ה inputList.stream (). מפה () מספק את זרם הקלטים (מחולל קלט). הוויכוח הראשון ל dynamicTest () הוא מחולל שמות התצוגה שלנו ("פתרון:" + dom) ואילו הטיעון השני, א למבדה, הוא מבחן הבדיקה שלנו.

הפלט יהיה זהה לזה של הסעיף הקודם.

6. דוגמא נוספת

בדוגמה זו אנו בוחנים עוד יותר את כוחן של המבחנים הדינמיים לסינון התשומות על סמך מקרי הבדיקה:

@TestFactory זרם dynamicTestsForEmployeeWorkflows () {List inputList = Arrays.asList (עובד חדש (1, "פרד"), עובד חדש (2), עובד חדש (3, "ג'ון")); EmployoDao dao = EmployeeDao חדש (); זרם saveEmployeeStream = inputList.stream () .map (emp -> DynamicTest.dynamicTest ("saveEmployee:" + emp.toString (), () -> {עובד החזיר = dao.save (emp.getId ()); assertEquals ( return.getId (), emp.getId ());})); זרם saveEmployeeWithFirstNameStream = inputList.stream () .filter (emp ->! Emp.getFirstName (). IsEmpty ()) .map (emp -> DynamicTest.dynamicTest ("saveEmployeeWithName" + emp.toString (), () -> { עובד החזיר = dao.save (emp.getId (), emp.getFirstName ()); assertEquals (Return.getId (), emp.getId ()); assertEquals (Return.getFirstName (), emp.getFirstName ()); })); להחזיר את Stream.concat (saveEmployeeStream, saveEmployeeWithFirstNameStream); }

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

לבסוף, אנו משלבים את שני הזרמים ומחזירים את כל הבדיקות כסינגל זרם.

עכשיו, בואו נסתכל על הפלט:

saveEmployee: עובד [id = 1, firstName = Fred] (dynamicTestsForEmployeeWorkflows ()) saveEm עובד: עובד [id = 2, firstName =] (dynamicTestsForEmployeeWorkflows ()) saveEm עובד: עובד [id = 3, firstName = John] (dynamicTestsForEm עובד) saveEmployeeWithNameEmployee [id = 1, firstName = Fred] (dynamicTestsForEmployeeWorkflows ()) saveEmployeeWithNameEmployee [id = 3, firstName = John] (dynamicTestsForEmployeeWorkflows ())

7. מסקנה

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

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

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

תוכלו לקרוא עוד על תכונות אחרות של JUnit 5 במאמר שלנו על בדיקות חוזרות ב- JUnit 5.

אל תשכח לבדוק את קוד המקור המלא של מאמר זה ב- GitHub.