מדריך JUnit 5, חלק 1: בדיקת יחידות עם JUnit 5, Mockito ו- Hamcrest

JUnit 5 הוא התקן בפועל החדש לפיתוח מבחני יחידות בג'אווה. הגרסה החדשה ביותר הזו הותירה אחריה את המגבלות של Java 5 ושילבה תכונות רבות מ- Java 8, בעיקר תמיכה בביטויי למבדה.

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

הורד קבל את הקוד קבל את קוד המקור לדוגמאות במדריך זה. נוצר על ידי סטיבן היינס עבור JavaWorld.

פיתוח מונחה מבחן

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

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

  1. הוסף מבחן.
  2. הפעל את כל הבדיקות שלך וצפה במבחן החדש שנכשל.
  3. יישם את הקוד.
  4. הפעל את כל הבדיקות שלך וצפה במבחן החדש בהצלחה.
  5. פקטור הקוד מחדש.

איור 1 מראה מחזור חיים זה של TDD.

סטיבן היינס

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

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

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

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

אימוץ JUnit 5

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

  • JUnit 5 ארוז כעת org.junit.jupiterבקבוצה, שמשנה את האופן שבו תכלול אותו בפרויקטים של Maven ו- Gradle.
  • JUnit 4 דרש מינימום JDK של JDK 5; JUnit 5 דורש מינימום JDK 8.
  • JUnit 4 של @Before, @BeforeClass, @After, ו @AfterClassהסברים הוחלפו @BeforeEach, @BeforeAll, @AfterEach, ו @AfterAll, בהתאמה.
  • @Ignoreההערה של JUnit 4 הוחלפה על ידי @Disabledההערה.
  • @Categoryביאור הוחלף על ידי @Tagהביאור.
  • JUnit 5 מוסיף קבוצה חדשה של שיטות קביעה.
  • רצים הוחלפו בתוספים, עם ממשק API חדש למיישמי הרחבות.
  • JUnit 5 מציג הנחות שמונעות ביצוע ביצוע בדיקה.
  • JUnit 5 תומך בשיעורי מבחן מקוננים ודינמיים.

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

בדיקת יחידות עם JUnit 5

נתחיל בפשטות, עם דוגמה מקצה לקצה לתצורה של פרויקט לשימוש ב- JUnit 5 לבדיקת יחידה. רישום 1 מציג MathToolsמחלקה ששיטתה ממירה מניין ומכנה ל- a double.

רישום 1. דוגמה לפרויקט JUnit 5 (MathTools.java)

 package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException("Denominator must not be 0"); } return (double)numerator / (double)denominator; } }

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

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

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

רישום 2. שיעור בחינה של JUnit 5 (MathToolsTest.java)

 package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } }

ברשימה 2, testConvertToDecimalInvalidDenominatorהשיטה מבצעת את MathTools::convertToDecimalהשיטה בתוך assertThrowsשיחה. הטיעון הראשון הוא סוג החריג הצפוי שיש לזרוק. הטיעון השני הוא פונקציה שתזרוק את החריג הזה. assertThrowsהשיטה מבצעת הפונקציה ומאמת כי הסוג של חריג צפוי נזרק.

כיתת הטענות ושיטותיה

org.junit.jupiter.api.Testהביאור מציין שיטת בדיקה. שים לב @Testשההערה מגיעה כעת מחבילת ה- API של JUnit 5 Jupiter במקום מחבילת JUnit 4 org.junit. testConvertToDecimalSuccessהשיטה ראשונה מבצעת את MathTools::convertToDecimalהשיטה עם מונה 3 ו מכנה של 4, אז טוענת שהתוצאה שווה 0.75. org.junit.jupiter.api.Assertionsבכיתה מספקת סט של staticשיטות להשוואת תוצאות בפועל צפוי. Assertionsבכיתה יש את השיטות הבאות, אשר מכסים את רוב סוגי נתונים פרימיטיביים:

  • assertArrayEquals משווה את התוכן של מערך בפועל למערך צפוי.
  • assertEquals משווה ערך בפועל לערך צפוי.
  • assertNotEquals משווה שני ערכים כדי לאמת שהם לא שווים.
  • assertTrue מאמת שהערך שסופק נכון.
  • assertFalse מאמת שהערך שסופק הוא שקר.
  • assertLinesMatchמשווה שתי רשימות של Stringס.
  • assertNull מאמת שהערך שסופק הוא אפס.
  • assertNotNull מאמת שהערך שסופק אינו ריק.
  • assertSame מאמת ששני ערכים מתייחסים לאותו אובייקט.
  • assertNotSame מאמת ששני ערכים אינם מתייחסים לאותו אובייקט.
  • assertThrowsמאמת שביצוע של שיטה מעורר חריג צפוי (אתה יכול לראות זאת testConvertToDecimalInvalidDenominatorבדוגמה לעיל).
  • assertTimeout מאמת כי פונקציה שסופקה הושלמה תוך זמן קצוב שנקבע.
  • assertTimeoutPreemptively מאמת כי פונקציה שסופקה הושלמה בתוך פסק זמן מוגדר, אך לאחר שהושג הזמן הקצוב היא הורגת את ביצוע הפונקציה.

אם אחת משיטות הקביעה הללו נכשלת, בדיקת היחידה מסומנת ככושלת. הודעת כשל זו תיכתב למסך בעת הפעלת הבדיקה ותישמר בקובץ דוח.

שימוש בדלתא עם assertEquals

When using float and double values in an assertEquals, you can also specify a delta that represents a threshold of difference between the two. In our example we could have added a delta of 0.001, in case 0.75 was actually returned as 0.750001.

Analyzing your test results

In addition to validating a value or behavior, the assert methods can also accept a textual description of the error, which can help you diagnose failures. For example:

 Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); 

The output will show the expected value of 0.75 and the actual value. It will also display the specified message, which can help you understand the context of the error. The difference between the two variations is that the first one always creates the message, even if it is not displayed, whereas the second one only constructs the message if the assertion fails. In this case, the construction of the message is trivial, so it doesn't really matter. Still, there is no need to construct an error message for a test that passes, so it's usually a best practice to use the second style.

Finally, if you're using an IDE like IntelliJ to run your tests, each test method will be displayed by its method name. This is fine if your method names are readable, but you can also add a @DisplayName annotation to your test methods to better identify the tests:

@Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); }

Running your unit test

In order to run JUnit 5 tests from a Maven project, you need to include the maven-surefire-plugin in the Maven pom.xml file and add a new dependency. Listing 3 shows the pom.xml file for this project.

Listing 3. Maven pom.xml for an example JUnit 5 project

  4.0.0 com.javaworld.geekcap junit5 jar 1.0-SNAPSHOT    org.apache.maven.plugins maven-compiler-plugin 3.8.1  8 8    org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4    junit5 //maven.apache.org   org.junit.jupiter junit-jupiter 5.6.0 test   

JUnit 5 dependencies

JUnit 5 packages its components in the org.junit.jupiter group and we need to add the junit-jupiter artifact, which is an aggregator artifact that imports the following dependencies:

  • junit-jupiter-api defines the API for writing tests and extensions.
  • junit-jupiter-engine הוא יישום מנוע הבדיקה שמריץ את מבחני היחידות.
  • junit-jupiter-params מספק תמיכה במבחנים פרמטרים.

לאחר מכן, עלינו להוסיף את maven-surefire-pluginתוסף ה- build כדי להריץ את הבדיקות.

לבסוף, הקפד לכלול את maven-compiler-pluginהגרסה של Java 8 ואילך, כך שתוכל להשתמש בתכונות Java 8 כמו lambdas.

תריץ את זה!

השתמש בפקודה הבאה כדי להריץ את מחלקת הבדיקה מ- IDE שלך או מ Maven:

mvn clean test

אם אתה מצליח, אתה אמור לראות פלט דומה לדברים הבאים:

 [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.832 s [INFO] Finished at: 2020-02-16T08:21:15-05:00 [INFO] ------------------------------------------------------------------------