ה-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
{
}

 

השאר הערה\הודעה