Mocks and Stubs - הבנת זוגות מבחנים עם Mockito

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

הם לא מודעים לכך שמוקס הם רק אחד ממספר 'זוגות מבחנים' שג'רארד מסזארוס סיווג ב- xunitpatterns.com.

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

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

אעשה זאת באמצעות כמה דוגמאות קצרות ופשוטות במוקיטו.

במשך שנים אנשים כותבים גרסאות קלות של רכיבי מערכת כדי לעזור בבדיקות. באופן כללי זה נקרא דקירה. בשנת 2000 'המאמר' בדיקת אנדו: בדיקת יחידות עם אובייקטים מדומים 'הציג את הרעיון של אובייקט מדומה. מאז סיבים, Mocks ומספר סוגים אחרים של אובייקטים לבדיקה סווגו על ידי Meszos כ- Double Test.

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

קישור לכל אחד מהמאמרים החשובים הללו מוצג בסעיף ההתייחסות.

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

//xunitpatterns.com/Test%20Double.html

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

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

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

//docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html

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

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

//xunitpatterns.com/Dummy%20Object.html

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

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

למבחן לא היה אכפת פחות איזה לקוח מתווסף, כל עוד ספירת הלקוחות חוזרת כאחת.

public Customer createDummyCustomer() { County county = new County("Essex"); City city = new City("Romford", county); Address address = new Address("1234 Bank Street", city); Customer customer = new Customer("john", "dobie", address); return customer; } @Test public void addCustomerTest() { Customer dummy = createDummyCustomer(); AddressBook addressBook = new AddressBook(); addressBook.addCustomer(dummy); assertEquals(1, addressBook.getNumberOfCustomers()); } 

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

@Test(expected=Exception.class) public void addNullCustomerTest() { Customer dummy = null; AddressBook addressBook = new AddressBook(); addressBook.addCustomer(dummy); } 

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

@Test public void addCustomerWithDummyTest() { Customer dummy = mock(Customer.class); AddressBook addressBook = new AddressBook(); addressBook.addCustomer(dummy); Assert.assertEquals(1, addressBook.getNumberOfCustomers()); } 

זה קוד פשוט זה שיוצר אובייקט דמה שיועבר לשיחה.

Customer dummy = mock(Customer.class);

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

תפקיד מבחן הכפול הוא המייחד אותו, ולא התחביר המשמש ליצירת אחד.

שיעור זה עובד כתחליף פשוט למחלקת הלקוחות והופך את הבדיקה לקלה מאוד לקריאה.

//xunitpatterns.com/Test%20Stub.html

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

קח את הקוד הבא

public class SimplePricingService implements PricingService { PricingRepository repository; public SimplePricingService(PricingRepository pricingRepository) { this.repository = pricingRepository; } @Override public Price priceTrade(Trade trade) { return repository.getPriceForTrade(trade); } @Override public Price getTotalPriceForTrades(Collection trades) { Price totalPrice = new Price(); for (Trade trade : trades) { Price tradePrice = repository.getPriceForTrade(trade); totalPrice = totalPrice.add(tradePrice); } return totalPrice; } 

ל- SimplePricingService יש אובייקט אחד שמשתף פעולה שהוא המאגר המסחרי. מאגר הסחר מספק מחירי סחר לשירות התמחור בשיטת getPriceForTrade.

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

כלומר תשומות שמעולם לא עברנו למבחן.

זה מוצג להלן.

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

@Test public void testGetHighestPricedTrade() throws Exception { Price price1 = new Price(10); Price price2 = new Price(15); Price price3 = new Price(25); PricingRepository pricingRepository = mock(PricingRepository.class); when(pricingRepository.getPriceForTrade(any(Trade.class))) .thenReturn(price1, price2, price3); PricingService service = new SimplePricingService(pricingRepository); Price highestPrice = service.getHighestPricedTrade(getTrades()); assertEquals(price3.getAmount(), highestPrice.getAmount()); } 

דוגמה לחבלן

ישנן 2 גרסאות נפוצות של מבחני מבחן: של המגיב ושל החובב.

המגיבים משמשים לבדיקת הדרך המאושרת כמו בדוגמה הקודמת.

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

@Test(expected=TradeNotFoundException.class) public void testInvalidTrade() throws Exception { Trade trade = new FixtureHelper().getTrade(); TradeRepository tradeRepository = mock(TradeRepository.class); when(tradeRepository.getTradeById(anyLong())) .thenThrow(new TradeNotFoundException()); TradingService tradingService = new SimpleTradingService(tradeRepository); tradingService.getTradeById(trade.getId()); } 

//xunitpatterns.com/Mock%20Object.html

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

זה שונה מאוד מהתפקיד התומך של תוף המשמש לספק תוצאות לכל מה שאתה בודק.

בדל אנו משתמשים בתבנית של הגדרת ערך החזר לשיטה.

when(customer.getSurname()).thenReturn(surname); 

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

verify(listMock).add(s); 

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

הנה הקוד העיקרי.

public class SimpleTradingService implements TradingService{ TradeRepository tradeRepository; AuditService auditService; public SimpleTradingService(TradeRepository tradeRepository, AuditService auditService) { this.tradeRepository = tradeRepository; this.auditService = auditService; } public Long createTrade(Trade trade) throws CreateTradeException { Long id = tradeRepository.createTrade(trade); auditService.logNewTrade(trade); return id; } 

המבחן שלהלן יוצר תיל עבור המאגר המסחרי והלעג עבור AuditService

לאחר מכן אנו מתקשרים לאמת ב- AuditService הלעג כדי לוודא שה- TradeService קורא לו

שיטת logNewTrade כהלכה

@Mock TradeRepository tradeRepository; @Mock AuditService auditService; @Test public void testAuditLogEntryMadeForNewTrade() throws Exception { Trade trade = new Trade("Ref 1", "Description 1"); when(tradeRepository.createTrade(trade)).thenReturn(anyLong()); TradingService tradingService = new SimpleTradingService(tradeRepository, auditService); tradingService.createTrade(trade); verify(auditService).logNewTrade(trade); } 

השורה הבאה מבצעת את AuditService הלעג.

verify(auditService).logNewTrade(trade);

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

//xunitpatterns.com/Test%20Spy.html

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

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

Here is an example were we check the standard behaviour of a List. Note that we can both verify that the add method is called and also assert that the item was added to the list.

@Spy List listSpy = new ArrayList(); @Test public void testSpyReturnsRealValues() throws Exception { String s = "dobie"; listSpy.add(new String(s)); verify(listSpy).add(s); assertEquals(1, listSpy.size()); } 

Compare this with using a mock object where only the method call can be validated. Because we only mock the behaviour of the list, it does not record that the item has been added and returns the default value of zero when we call the size() method.

@Mock List listMock = new ArrayList(); @Test public void testMockReturnsZero() throws Exception { String s = "dobie"; listMock.add(new String(s)); verify(listMock).add(s); assertEquals(0, listMock.size()); } 

Another useful feature of the testSpy is the ability to stub return calls. When this is done the object will behave as normal until the stubbed method is called.

In this example we stub the get method to always throw a RuntimeException. The rest of the behaviour remains the same.

@Test(expected=RuntimeException.class) public void testSpyReturnsStubbedValues() throws Exception { listSpy.add(new String("dobie")); assertEquals(1, listSpy.size()); when(listSpy.get(anyInt())).thenThrow(new RuntimeException()); listSpy.get(0); } 

In this example we again keep the core behaviour but change the size() method to return 1 initially and 5 for all subsequent calls.

public void testSpyReturnsStubbedValues2() throws Exception { int size = 5; when(listSpy.size()).thenReturn(1, size); int mockedListSize = listSpy.size(); assertEquals(1, mockedListSize); mockedListSize = listSpy.size(); assertEquals(5, mockedListSize); mockedListSize = listSpy.size(); assertEquals(5, mockedListSize); } 

This is pretty Magic!

//xunitpatterns.com/Fake%20Object.html

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

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

בדוק דפוסים כפולים

בדיקת אנדו: בדיקת יחידות עם אובייקטים מדומים

תפקידים מדומים, לא אובייקטים

Mocks are not Stubs

//msdn.microsoft.com/en-us/magazine/cc163358.aspx

סיפור זה, "Mocks And Stubs - Understanding Test Doubles With Mockito" פורסם במקור בהוצאת JavaWorld.