ההבדל בין סתום, מדומה ומרגל במסגרת ספוק

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

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

ספוק היא מסגרת בדיקה עבור ג'אווה ו קִצבִּי המסייע באוטומציה של תהליך הבדיקה הידנית של יישום התוכנה. היא מציגה את הלעג, הבדים והמרגלים שלה, ומגיעה עם יכולות מובנות לבדיקות שבדרך כלל דורשות ספריות נוספות.

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

2. תלות Maven

לפני שנתחיל, נוסיף את התלות שלנו ב- Maven:

 org.spockframework spock-core 1.3-RC1-groovy-2.5 test org.codehaus.groovy groovy-all 2.4.7 test 

שים לב שנצטרך את 1.3-RC1-groovy-2.5 גרסת ספוק. מרגל יוצג בגרסה היציבה הבאה של Spock Framework. עכשיו מרגל זמין במועמד הגרסה הראשונה לגרסה 1.3.

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

3. בדיקות מבוססות אינטראקציה

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

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

כמו רוב ספריות Java, Spock משתמש ב- proxy דינמי של JDK לצורך ממשק ללעג ולפרוקסי Byte Buddy או cglib לשיעורי לעג. זה יוצר יישומים מדומים בזמן ריצה.

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

4. שיחות בשיטת הדקירה

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

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

בואו נעבור לקוד לדוגמא עם לוגיקה עסקית.

4.1. קוד בבדיקה

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

פריט בכיתה ציבורית {פרטי סופי מחרוזת מזהה; פרטי סופי מחרוזת; // קונסטרוקטור סטנדרטי, גטרס, שווה}

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

פריט חדש ('1', 'שם') == פריט חדש ('1', 'שם')

עכשיו, בואו ניצור ממשק פריט ספק בשיטה אחת:

ממשק ציבורי ItemProvider {List getItems (List itemIds); }

נצטרך גם שיעור שייבדק. נוסיף פריט ספק כתלות ב פריט שירות:

מעמד ציבורי ItemService {private final ItemProvider itemProvider; שירות ציבורי (ItemProvider itemProvider) {this.itemProvider = itemProvider; } רשימת getAllItemsSortedByName (List itemIds) {List items = itemProvider.getItems (itemIds); להחזיר items.stream () .sorted (Comparator.comparing (פריט :: getName)) .collect (Collectors.toList ()); }}

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

בקוד זה, נצטרך לדחות את התלות החיצונית, כי אנחנו רק רוצים לבדוק את ההיגיון שלנו הכלול ב- getAllItemsSortedByName שיטה.

4.2. שימוש באובייקט סתום בקוד הנבדק

בוא נתחיל את ItemService חפץ ב להכין() שיטה באמצעות בָּדָל בשביל ה פריט ספק תלות:

ItemProvider itemProvider ItemService itemService def setup () {itemProvider = Stub (ItemProvider) itemService = ItemService חדש (itemProvider)}

עַכשָׁיו, בואו נעשה פריט ספק להחזיר רשימה של פריטים בכל קריאה עם הטיעון הספציפי:

itemProvider.getItems (['offer-id', 'offer-id-2']) >> [פריט חדש ('offer-id-2', 'Zname'), פריט חדש ('offer-id', 'Aname ')]

אנו משתמשים >> באופראנד כדי לדחות את השיטה. ה getItems השיטה תמיד תחזיר רשימה של שני פריטים כאשר תתקשר אליה ['Offer-id', 'offer-id-2'] רשימה. [] הוא קִצבִּי קיצור דרך ליצירת רשימות.

להלן כל שיטת הבדיקה:

def 'צריך להחזיר פריטים ממוינים לפי שם' () {נתון: def ids = ['offer-id', 'offer-id-2'] itemProvider.getItems (ids) >> [פריט חדש ('offer-id-2 ',' Zname '), פריט חדש (' id-offer ',' Aname ')] כאשר: List items = itemService.getAllItemsSortedByName (ids) ואז: items.collect {it.name} == [' Aname ',' Zname ']}

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

5. שיטות כיתה לגלוג

עכשיו, בואו נדבר על שיעורי לעג או ממשקים בספוק.

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

נבדוק את האינטראקציות בקוד הדוגמה שתיארנו להלן.

5.1. קוד עם אינטראקציה

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

המתווך למשלוח הודעות הוא RabbitMQ או Kafka, אז באופן כללי, פשוט נתאר את החוזה שלנו:

ממשק ציבורי EventPublisher {void publish (String addedOfferId); }

שיטת הבדיקה שלנו תשמור פריטים שאינם ריקים במסד הנתונים ואז תפרסם את האירוע. שמירת פריט במסד הנתונים אינה רלוונטית בדוגמה שלנו, אז פשוט נגיב תגובה:

void saveItems (List itemIds) {List notEmptyOfferIds = itemIds.stream () .filter (itemId ->! itemId.isEmpty ()) .collect (Collectors.toList ()); // שמור במסד הנתונים notEmptyOfferIds.forEach (eventPublisher :: publish); }

5.2. אימות אינטראקציה עם אובייקטים ללעג

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

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

class ItemServiceTest מרחיב מפרט {ItemProvider itemProvider ItemService itemService EventPublisher eventPublisher def setup () {itemProvider = Stub (ItemProvider) eventPublisher = Mock (EventPublisher) itemService = פריט שירות חדש (itemProvider, eventPublisher)}

כעת נוכל לכתוב את שיטת הבדיקה שלנו. נעביר 3 מיתרים: ”,‘ a ’,‘ b 'ואנחנו מצפים שזה שלנו eventPublisher יפרסם שני אירועים עם מחרוזות 'a' ו- 'b':

def 'צריך לפרסם אירועים אודות הצעות שמורות חדשות שאינן ריקות' () {given: def offerIds = ['', 'a', 'b'] when: itemService.saveItems (offerIds) ואז: 1 * eventPublisher.publish (' a ') 1 * eventPublisher.publish (' b ')}

בואו נסתכל מקרוב על הקביעה שלנו בגמר לאחר מכן סָעִיף:

1 * eventPublisher.publish ('a')

אנו מצפים לכך itemService יקרא eventPublisher.publish (מחרוזת) עם 'a' כטענה.

בדקירות דיברנו על אילוצי ויכוחים. אותם כללים חלים על לעגים. אנחנו יכולים לאמת זאת eventPublisher.publish (מחרוזת) נקרא פעמיים עם כל טיעון לא ריק ולא ריק:

2 * eventPublisher.publish ({it! = Null &&! It.isEmpty ()})

5.3. שילוב של לעג וסטיב

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

בואו נעקוף פריט ספק עם מדומה (מחלקה) וליצור חדש ItemService:

given: itemProvider = Mock (ItemProvider) itemProvider.getItems (['item-id']) >> [פריט חדש ('item-id', 'name')] itemService = Item Item חדש (itemProvider, eventPublisher) כאשר: פריטי def = itemService.getAllItemsSortedByName (['item-id']) ואז: items == [פריט חדש ('item-id', 'name')] 

אנחנו יכולים לשכתב את הדקירות מה- נָתוּן סָעִיף:

1 * itemProvider.getItems (['item-id']) >> [פריט חדש ('item-id', 'name')]

אז באופן כללי, שורה זו אומרת: itemProvider.getItems ייקרא פעם אחת עם ['מספר זיהוי של הפריט'] ויכוח והחזר מערך נתון.

אנחנו כבר יודעים שלעג יכול להתנהג כמו בדלים. כל הכללים בנוגע לאילוצי ויכוחים, החזרת ערכים מרובים ותופעות לוואי חלים גם על לִלְעוֹג.

6. שיעורי ריגול בספוק

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

בניגוד ל לִלְעוֹג ו בָּדָל, אנחנו לא יכולים ליצור מרגל על ממשק. הוא עוטף אובייקט ממשי, לכן בנוסף נצטרך להעביר טיעונים עבור הבנאי. אחרת, בונה ברירת המחדל של הסוג יופעל.

6.1. קוד בבדיקה

בואו ניצור יישום פשוט עבור EventPublisher. LoggingEventPublisher ידפיס במסוף את מזהה כל פריט שנוסף. הנה יישום שיטת הממשק:

@ עקוב על פרסום חלל ציבורי (String addedOfferId) {System.out.println ("פרסמתי:" + addedOfferId); }

6.2. בודקים עם מרגל

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

eventPublisher = Spy (LoggingEventPublisher)

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

נתון: eventPublisher = Spy (LoggingEventPublisher) itemService = שירות פריט חדש (itemProvider, eventPublisher) כאשר: itemService.saveItems (['item-id']) ואז: 1 * eventPublisher.publish ('מזהה פריט')

אימתנו שה- eventPublisher.publish שיטה נקראה רק פעם אחת. בנוסף, שיחת השיטה הועברה לאובייקט האמיתי, כך שנראה את הפלט של println בקונסולה:

פרסמתי: מזהה פריט

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

7. בדיקות יחידה טובות

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

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

8. מסקנה

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

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


$config[zx-auto] not found$config[zx-overlay] not found