ה-Junit הינו פריימוורק לבדיקות יחידה בשפת Java , הוא הפופולרי ביותר בעולם ואחד הפופולריים בתחום שלנו באוטומציה. לאחר זמן רב שבו עבדנו עם גרסת ה-4.12 , יצאה גירסה חדשה ומרחיקת לכת: Junit5.
בפוסט זה נבין מהו אותו פריימוורק ובעיקר מה גרסה 5 נותנת לנו ש-4 לא, וישנם הרבה חידושים מגניבים.
אז קודם כל, מהו Junit ? ולמה אנחנו רוצים לעבוד איתו ?
בגדול – ה-Junit נותן לנו את היכולת להריץ את הבדיקות (הקוד) שלנו בצורה אוטומטית (כן, למעשה זה האוטומציה של האוטומציה)
בקטן יותר – ה-Junit נותן לנו כמה יכולות:
- Annotations
- Assertions
- Suite Executions
- ועוד…
התקנה:
התקנת Junit5 מתבצעת על ידי Build Management Tool כדוגמת Maven , להלן ה-Dependencies אותם יש לצרף לפרוייקט בשביל לעבוד עם Juni5:
junit-jupiter-api – חושף בפנינו את ה-API של Junit
junit-jupiter-engine – זהו dependency אשר מגדיר לנו את מנוע ההרצה של Junit
junit-vintage-engine – במידה ונרצה להמשיך ולעבוד עם גרסאות ישנות של Junit (3 או 4) נצטרך לעבוד עם ה-Dependecy הזה
junit-platform-launcher – חושף את ה-API של הקונפיגורציה שבד"כ הIDE משתמשים בו
junit-platform-runner – מאפשר לנו להריץ טסטים וחבילות של טסטים הכתובים ב-Junit4
אנוטציות:
האנוטציות מגיעות בתצורה של @ ואח"כ שם האנוטציה, כך אנו יכולים לכתוב את המחלקות שלנו בצורה בדיקתית, כך אנו יכולים להגדיר פונקציות ברות הרצה מטעם מנוע ההרצה של Junit (שיושב מעל המנוע של Java).
ב-Junit 5 כתבו לנו כמה אנוטציות חדשות, להלן סיכום של כל האנוטציות הנתמכות ב-Junit 5
למי שמכיר את junit ורגיל לעבוד עם גרסת 4, יכול בוודאי להבחין כי קיימות כאן אנוטציות חדשות, (כמו ה-nested) וכמו כן שינו את השמות של האנוטציות הקיימות (כמו ה-BeforeEach)
להלן דוגמת קוד הממחישה עבודה עם האנוטציות ב-junit5
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; public class JUnit5Sample { @BeforeAll static void beforeAll() { System.out.println("Executed once before all test methods in this class"); } @BeforeEach void beforeEach() { System.out.println("Executed before each test method in this class"); } @Test void testMethod1() { System.out.println("Test method1 executed"); } @Test void testMethod2() { System.out.println("Test method2 executed"); } @AfterEach void afterEach() { System.out.println("Executed after each test method in this class"); } @AfterAll static void afterAll() { System.out.println("Executed once after all test methods in this class"); } }
וכעת נוכל גם לשחק עם שאר האנוטציות הקיימות, כמו למשל לעבוד עם DisplayName:
@Test @DisplayName("Hello World") void test01(){ System.out.println("Test Hello World is Invoked"); }
אנוטציית ה-Disabled כשמה כן היא, לא תאפשר להריץ את הטסט אליו היא משוייכת (במידה ובאופן זמני נרצה לא להריץ את הטסט):
@Test @Disabled("Do not run this test") void test01() { System.out.println("Hello World"); }
ניתן גם להכיל את אנוטציית ה-Disabled על מחלקה ואז כל הטסטים תחת אותה מחלקה יהיו במצב של Disabled:
@Disabled public class AppTest { @Test void test01(){ System.out.println("Hello World"); } }
אנוטציית ה-Tag נועדה בשביל לפלטר טסטים מסויימים ולשייך טסט\ים לקבוצה כפי שניתן לראות בדוגמא הבאה:
@Test @Tag("Sanity") void test01() { System.out.println("Hello World"); } @Test @Tag("Regression") void test02() { System.out.println("Hello World 2"); } @Test @Tag("Sanity") @Tag("Regression") void test03() { System.out.println("Hello World 3"); }
אנו רואים בדוגמא הנ"ל כי test01 משוייך לקבוצת טסטים בשם Sanity , ו-test02 משוייך לקבוצה בשם Regression ואילו test03 משוייך לשתי קבוצות: Sanity ו-Regression
אנוטציית ה-RepeatedTest תאפשר לנו להריץ את אותו מקרה הבדיקה כמה פעמים , כמו כן ניתן למקרה בדיקה זה לשלוח פרמטרים בכדיי להזין לו ערכים שונים לאותה בדיקה, ראו דוגמא:
@Test @RepeatedTest(5) void test01() { System.out.println("Hello World"); }
כאן נשלח לו פרמטרים:
@Test @DisplayName("My Test Name") @RepeatedTest(value = 5, name = "{displayName} - repetition {currentRepetition} of {totalRepetitions}") void addNumber(TestInfo testInfo) { System.out.println("Hello World"); }
Nested Classes:
זהו חידוש מעניין של Junit5 , האפשרות לקונן מחלקות אחת בתוך השנייה בשביל לייצר טסטים מודולרים באופן כזה שיהיה אפשר לייצר קבוצה של תת בדיקות בתוך בדיקות, למשל כשארצה ליצור בדיקות עבור תת תפריט – הגדרות בתוך תפריט אב – ראשי.
@DisplayName("FatherClass") class JUnit5Sample { @BeforeAll static void beforeAll() { System.out.println("Before all test methods"); } @BeforeEach void beforeEach() { System.out.println("Before each test method"); } @AfterEach void afterEach() { System.out.println("After each test method"); } @AfterAll static void afterAll() { System.out.println("After all test methods"); } @Nested @DisplayName("Child Nested Class") class JUnit5NestedSample { @BeforeEach void beforeEach() { System.out.println("Before each test method of the JUnit5NestedSample class"); } @AfterEach void afterEach() { System.out.println("After each test method of the JUnit5NestedSample class"); } @Test @DisplayName("Example test for method JUnit5NestedSample") void sampleTestForMethod() { System.out.println("Example test for method JUnit5NestedSample"); } } }
שליחת פרמטרים לטסטים:
ניתן כמובן לשלוח ערכים מבחוץ אל תוך טסטים כך שהם ירוצו כמספר הפרמטרים ששלחנו אליו, כפי שנראה בדוגמא הבאה:
class MyClass { @ParameterizedTest @ValueSource(strings = {"Hello", "World"}) void test01(String message) { System.out.println(message); } }
במקרה הזה הטסט ירוץ פעמיים פעם אחת הוא ידפיס את המילה Hello ופעם שנייה ידפיס את המילה World
ניתן כמובן לשלוח לטסט גם מבנה של פרמטרים כמו מבנה של CSV , כמו בדוגמא הבאה:
@CsvSource({"1, 1, 2", "2, 3, 5"}) void sum(int a, int b, int sum) { assertEquals(sum, a + b); }
או פשוט לשלוח לו קובץ CSV חיצוני:
@CsvSource(resources = "C:/Data/values.csv") void sum(int a, int b, int sum) { assertEquals(sum, a + b); }
Assertions:
ה-Assertions הם פונקציות שמאפשרות לנו לבדוק נתונים במהלך הטסטים שלנו, Junit5 תומך בכל סוגי ה-Assertions שקיימים בגרסה 4 , למעשה ניתן לעבוד ב-Junit5 עם 3 סוגי Assertions:
1. ה-Assertions של Junit5 API , דוגמאות:
assertEquals(EXPECTED, ACTUAL); assertNotEquals(EXPECTED, ACTUAL); assertNull(null); assertNotNull(new Object()); assertTrue(true); assertFalse(false);
2. ה-Assertions של Hamcrest , דוגמאות:
assertThat(true, is(true)); assertThat(false, is(false)); assertThat(ACTUAL, is(EXPECTED)); assertThat(ACTUAL, not(EXPECTED));
3. ה-Assertions של AssertJ , דוגמאות:
assertThat(true).isTrue(); assertThat(false).isFalse(); assertThat(ACTUAL).isEqualByComparingTo(EXPECTED); assertThat(ACTUAL).isNotEqualByComparingTo(EXPECTED);
הרצה של Test Suite:
גם עם Junit5 נוכל להריץ חבילות של בדיקות תוך הגדרתם במחלקה נפרדת, למשל על ידי בחירה של packages שונים:
@RunWith(JUnitPlatform.class) @SelectPackages({"packageAAA","packageBBB"}) public class JUnit5SuiteExample { }
או לחילופין בחירה של מחלקות ספציפיות להרצה:
@RunWith(JUnitPlatform.class) @SelectClasses( { MyTestsA.class, MyTestsB.class, MyTestsC.class } ) public class JUnit5SuiteExample { }
ניתן לפלטר לפי Packages בהרצה:
@RunWith(JUnitPlatform.class) @SelectPackages("MyPackages") @IncludePackages("MyPackages.packageA") // Include Package public class JUnit5SuiteExample { }
@RunWith(JUnitPlatform.class) @SelectPackages("MyPackages") @ExcludePackages("MyPackages.packageA") // Exclude Package public class JUnit5SuiteExample { }
וכן לפלטר לפי מחלקות בהרצה:
@RunWith(JUnitPlatform.class) @SelectPackages("MyClasses") @IncludeClassName("MyClasses.classA") // Include Class public class JUnit5SuiteExample { }
@RunWith(JUnitPlatform.class) @SelectPackages("MyClasses") @ExcludeClassName("MyClasses.ClassA") // Exclude Class public class JUnit5SuiteExample { }
ולפלטר לפי תגיות (ע"י אנוטציית ה-Tag עליה כתבנו למעלה):
@RunWith(JUnitPlatform.class) @SelectPackages("MyPackages") @IncludeTags("Sanity") // Include Tags public class JUnit5SuiteExample { }
@RunWith(JUnitPlatform.class) @SelectPackages("MyPackages") @ExcludeTags("Sanity") // Exclude Tags public class JUnit5SuiteExample { }