Προγραμματισμός

Mocks and Stubs - Κατανόηση των δοκιμαστικών διπλών με τον Mockito

Ένα κοινό πράγμα που συναντώ είναι ότι οι ομάδες που χρησιμοποιούν ένα πλαστό πλαίσιο υποθέτουν ότι κοροϊδεύουν.

Δεν γνωρίζουν ότι οι Mocks είναι μόνο ένα από τα «Test Doubles» που ο Gerard Meszaros έχει κατηγοριοποιήσει στο xunitpatterns.com.

Είναι σημαντικό να συνειδητοποιήσετε ότι κάθε τύπος δοκιμαστικού διπλού έχει διαφορετικό ρόλο στη δοκιμή. Με τον ίδιο τρόπο που πρέπει να μάθετε διαφορετικά μοτίβα ή επανακατασκευές, πρέπει να κατανοήσετε τους πρωτόγονους ρόλους κάθε τύπου διπλού δοκιμής. Αυτά μπορούν στη συνέχεια να συνδυαστούν για να καλύψουν τις ανάγκες δοκιμών σας.

Θα καλύψω ένα πολύ σύντομο ιστορικό για το πώς προέκυψε αυτή η ταξινόμηση και πώς κάθε ένας από τους τύπους διαφέρει.

Θα το κάνω χρησιμοποιώντας μερικά σύντομα, απλά παραδείγματα στο Mockito.

Εδώ και χρόνια οι άνθρωποι γράφουν ελαφρές εκδόσεις των στοιχείων του συστήματος για να βοηθήσουν στη δοκιμή. Σε γενικές γραμμές ονομάστηκε stubbing. Το 2000, το άρθρο «Endo-Testing: Unit Testing with Mock Objects» εισήγαγε την έννοια ενός Mock Object. Έκτοτε, τα Stubs, Mocks και διάφοροι άλλοι τύποι δοκιμαστικών αντικειμένων έχουν ταξινομηθεί από τον Meszaros ως Test Doubles.

Αυτή η ορολογία έχει αναφερθεί από τον Martin Fowler στο "Mocks Are Not Stubs" και υιοθετείται στην κοινότητα της Microsoft όπως φαίνεται στην ενότητα "Exploring The Continuum of Test Doubles"

Ένας σύνδεσμος για καθένα από αυτά τα σημαντικά έγγραφα παρουσιάζεται στην ενότητα αναφοράς.

Το παραπάνω διάγραμμα δείχνει τους τύπους δοκιμής που χρησιμοποιούνται συνήθως. Η ακόλουθη διεύθυνση URL παρέχει μια καλή παραπομπή σε καθένα από τα μοτίβα και τις δυνατότητές τους, καθώς και εναλλακτική ορολογία.

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

Το Mockito είναι ένα πλαίσιο δοκιμών κατάσκοπος και είναι πολύ απλό να το μάθεις. Αξιοσημείωτο με το Mockito είναι ότι οι προσδοκίες για ψεύτικα αντικείμενα δεν καθορίζονται πριν από τη δοκιμή, καθώς μερικές φορές βρίσκονται σε άλλα πλαστά πλαίσια. Αυτό οδηγεί σε ένα πιο φυσικό στυλ (IMHO) όταν αρχίζει να κοροϊδεύει.

Τα ακόλουθα παραδείγματα είναι εδώ καθαρά για να δώσουν μια απλή επίδειξη της χρήσης του Mockito για την εφαρμογή των διαφόρων τύπων διπλών δοκιμών.

Υπάρχει ένας πολύ μεγαλύτερος αριθμός συγκεκριμένων παραδειγμάτων σχετικά με τον τρόπο χρήσης του Mockito στον ιστότοπο.

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

Ακολουθούν μερικά βασικά παραδείγματα που χρησιμοποιούν το Mockito για να δείξουν το ρόλο κάθε διπλού τεστ, όπως ορίζεται από τον Meszaros.

Έχω συμπεριλάβει έναν σύνδεσμο για τον κύριο ορισμό για κάθε έναν ώστε να μπορείτε να λάβετε περισσότερα παραδείγματα και έναν πλήρη ορισμό.

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

Αυτό είναι το απλούστερο από όλα τα διπλά δοκιμαστικά. Αυτό είναι ένα αντικείμενο που δεν έχει εφαρμογή, το οποίο χρησιμοποιείται αποκλειστικά για να συμπληρώσει ορίσματα κλήσεων μεθόδου που δεν σχετίζονται με τη δοκιμή σας.

Για παράδειγμα, ο παρακάτω κώδικας χρησιμοποιεί πολύ κώδικα για να δημιουργήσει τον πελάτη που δεν είναι σημαντικός για τη δοκιμή.

Η δοκιμή δεν μπορούσε να ενδιαφερθεί λιγότερο για τον πελάτη που προστίθεται, αρκεί ο αριθμός των πελατών να επιστρέφει ως ένας.

δημόσια Πελάτης createDummyCustomer () {County county = new County ("Essex"); Πόλη πόλης = νέα Πόλη ("Romford", κομητεία); Διεύθυνση διεύθυνση = νέα διεύθυνση ("1234 Bank Street", πόλη); Πελάτης πελάτη = νέος πελάτης ("john", "dobie", διεύθυνση); πελάτης επιστροφής } @Test public void addCustomerTest () {Customer dummy = createDummyCustomer (); BookBook addressBook = νέο βιβλίο διευθύνσεων (); addressBook.addCustomer (ανδρείκελο); assertEquals (1, addressBook.getNumberOfCustomers ()); } 

Στην πραγματικότητα δεν ενδιαφερόμαστε για το περιεχόμενο του αντικειμένου του πελάτη - αλλά απαιτείται. Μπορούμε να δοκιμάσουμε μια μηδενική τιμή, αλλά αν ο κώδικας είναι σωστός, θα περίμενε κανείς κάποια εξαίρεση.

@Test (αναμένεται = Exception.class) δημόσιο κενό addNullCustomerTest () {Customer dummy = null; BookBook addressBook = νέο βιβλίο διευθύνσεων (); addressBook.addCustomer (ανδρείκελο); } 

Για να το αποφύγουμε αυτό μπορούμε να χρησιμοποιήσουμε ένα απλό ανδρείκελο Mockito για να πάρουμε την επιθυμητή συμπεριφορά.

@Test public void addCustomerWithDummyTest () {Customer dummy = mock (Customer.class); BookBook addressBook = νέο βιβλίο διευθύνσεων (); addressBook.addCustomer (ανδρείκελο); Assert.assertEquals (1, addressBook.getNumberOfCustomers ()); } 

Είναι αυτός ο απλός κώδικας που δημιουργεί ένα πλαστό αντικείμενο που πρέπει να περάσει στην κλήση.

Πελάτης ψεύτικος = mock (Customer.class);

Μην ξεγελιέστε από την ψεύτικη σύνταξη - ο ρόλος που παίζεται εδώ είναι αυτός ενός ανδρεικέλου, όχι ενός ψεύτικου.

Είναι ο ρόλος του δοκιμαστικού διπλού που το ξεχωρίζει, όχι η σύνταξη που χρησιμοποιήθηκε για τη δημιουργία ενός.

Αυτή η τάξη λειτουργεί ως απλό υποκατάστατο της κατηγορίας πελατών και κάνει τη δοκιμή πολύ ευανάγνωστη.

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

Ο ρόλος του δοκιμαστικού στελέχους είναι να επιστρέφει ελεγχόμενες τιμές στο αντικείμενο που δοκιμάζεται. Αυτά περιγράφονται ως έμμεσες εισόδους στο τεστ. Ας ελπίσουμε ότι ένα παράδειγμα θα διευκρινίσει τι σημαίνει αυτό.

Πάρτε τον ακόλουθο κωδικό

Δημόσια τάξη SimplePricingService εφαρμόζει PricingService {PricingRepository repository; δημόσιο SimplePricingService (PricingRepository pricingRepository) {this.repository = pricingRepository; } @Override public Price priceTrade (Trade trade) {return repository.getPriceForTrade (εμπόριο); } @Override public Price getTotalPriceForTrades (Συλλογή συναλλαγών) {Price totalPrice = new Price (); για (Trade trade: trades) {Price tradePrice = repository.getPriceForTrade (εμπόριο); totalPrice = totalPrice.add (tradePrice); } επιστροφή totalPrice; } 

Το SimplePricingService έχει ένα αντικείμενο συνεργασίας που είναι το αρχείο καταγραφής συναλλαγών. Το αρχείο καταγραφής συναλλαγών παρέχει τιμές συναλλαγών στην υπηρεσία τιμολόγησης μέσω της μεθόδου getPriceForTrade.

Για να δοκιμάσουμε τη λογική των businees στο SimplePricingService, πρέπει να ελέγξουμε αυτές τις έμμεσες εισόδους

δηλ. εισόδους που δεν περάσαμε ποτέ στη δοκιμή.

Αυτό φαίνεται παρακάτω.

Στο ακόλουθο παράδειγμα στέλνουμε το PricingRepository για να επιστρέψουμε γνωστές τιμές που μπορούν να χρησιμοποιηθούν για τον έλεγχο της επιχειρηματικής λογικής του SimpleTradeService.

@Test public void testGetHighestPricedTrade () ρίχνει την εξαίρεση {Price price1 = new Price (10); Τιμή τιμής 2 = νέα τιμή (15); Τιμή τιμής 3 = νέα τιμή (25); PricingRepository pricingRepository = mock (PricingRepository.class); όταν (pricingRepository.getPriceForTrade (any (Trade.class))) .thenReturn (τιμή1, τιμή2, τιμή3); Υπηρεσία PricingService = νέο SimplePricingService (pricingRepository); Τιμή υψηλότερη Τιμή = service.getHighestPricedTrade (getTrades ()); assertEquals (τιμή3.getAmount (), tertinggiPrice.getAmount ()); } 

Παράδειγμα Saboteur

Υπάρχουν 2 κοινές παραλλαγές του Test Stubs: Responder's και Saboteur's.

Οι ανταποκριτές χρησιμοποιούνται για να δοκιμάσουν το χαρούμενο μονοπάτι όπως στο προηγούμενο παράδειγμα.

Ένας σαμποτέρ χρησιμοποιείται για τη δοκιμή εξαιρετικής συμπεριφοράς όπως παρακάτω.

@Test (αναμένεται = TradeNotFoundException.class) public void testInvalidTrade () ρίχνει την εξαίρεση {Trade trade = new FixtureHelper (). GetTrade (); TradeRepository tradeRepository = mock (TradeRepository.class); όταν (tradeRepository.getTradeById (anyLong ())) .thenThrow (νέο TradeNotFoundException ()); TradingService tradingService = νέο SimpleTradingService (tradeRepository); tradingService.getTradeById (trade.getId ()); } 

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

Τα πλαστά αντικείμενα χρησιμοποιούνται για την επαλήθευση της συμπεριφοράς των αντικειμένων κατά τη διάρκεια μιας δοκιμής. Με τη συμπεριφορά του αντικειμένου εννοώ ότι ελέγχουμε ότι οι σωστές μέθοδοι και διαδρομές ασκούνται στο αντικείμενο όταν εκτελείται η δοκιμή.

Αυτό είναι πολύ διαφορετικό από τον υποστηρικτικό ρόλο ενός στέλεχος που χρησιμοποιείται για να παρέχει αποτελέσματα σε ό, τι δοκιμάζετε.

Σε ένα στέλεχος χρησιμοποιούμε το μοτίβο καθορισμού μιας τιμής επιστροφής για μια μέθοδο.

όταν (customer.getSurname ()). τότε Επιστροφή (επώνυμο); 

Σε ένα ψεύτικο ελέγχουμε τη συμπεριφορά του αντικειμένου χρησιμοποιώντας την ακόλουθη φόρμα.

επαλήθευση (listMock) .add (s); 

Εδώ είναι ένα απλό παράδειγμα όπου θέλουμε να ελέγξουμε ότι ένα νέο εμπόριο ελέγχεται σωστά.

Εδώ είναι ο κύριος κωδικός.

δημόσια τάξη SimpleTradingService εφαρμόζει το TradingService {TradeRepository tradeRepository; AuditService auditService; δημόσιο SimpleTradingService (TradeRepository tradeRepository, AuditService auditService) {this.tradeRepository = tradeRepository; this.auditService = auditService; } Δημόσιο Long createTrade (Trade trade) ρίχνει το CreateTradeException {Long id = tradeRepository.createTrade (trade); auditService.logNewTrade (εμπόριο); αναγνωριστικό επιστροφής; } 

Η παρακάτω δοκιμή δημιουργεί ένα στέλεχος για το αρχείο καταγραφής συναλλαγών και πλαστή για το AuditService

Στη συνέχεια καλούμε επαλήθευση στο πλαστό AuditService για να βεβαιωθούμε ότι το TradeService το ονομάζει

Η μέθοδος logNewTrade σωστά

@Mock TradeRepository tradeRepository; @Mock AuditService auditService; @Test public void testAuditLogEntryMadeForNewTrade () ρίχνει την εξαίρεση {Trade trade = new Trade ("Ref 1", "Description 1"); όταν (tradeRepository.createTrade (trade)). thenReturn (anyLong ()); TradingService tradingService = νέο SimpleTradingService (tradeRepository, auditService); tradingService.createTrade (εμπόριο); επαλήθευση (auditService) .logNewTrade (εμπόριο); } 

Η ακόλουθη γραμμή κάνει τον έλεγχο στην πλαστή AuditService.

επαλήθευση (auditService) .logNewTrade (εμπόριο);

Αυτή η δοκιμή μας επιτρέπει να δείξουμε ότι η υπηρεσία ελέγχου συμπεριφέρεται σωστά κατά τη δημιουργία μιας συναλλαγής.

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

Αξίζει να ρίξετε μια ματιά στον παραπάνω σύνδεσμο για τον αυστηρό ορισμό του Test Spy.

Ωστόσο, στο Mockito μου αρέσει να το χρησιμοποιώ για να μπορέσω να τυλίξεις ένα πραγματικό αντικείμενο και, στη συνέχεια, να επαληθεύσω ή να τροποποιήσω τη συμπεριφορά του για να υποστηρίξεις τις δοκιμές σου.

Εδώ είναι ένα παράδειγμα που ελέγξαμε την τυπική συμπεριφορά μιας λίστας. Σημειώστε ότι μπορούμε και οι δύο να επαληθεύσουμε ότι καλείται η μέθοδος προσθήκης και επίσης να επιβεβαιώσουμε ότι το στοιχείο προστέθηκε στη λίστα.

@Spy List listSpy = νέο ArrayList (); @Test public void testSpyReturnsRealValues ​​() ρίχνει την εξαίρεση {String s = "dobie"; listSpy.add (νέα συμβολοσειρά); επαλήθευση (listSpy) .add (s); assertEquals (1, listSpy.size ()); } 

Συγκρίνετε αυτό με ένα πλαστό αντικείμενο όπου μπορεί να επικυρωθεί μόνο η μέθοδος κλήσης. Επειδή πλαστογραφούμε μόνο τη συμπεριφορά της λίστας, δεν καταγράφει ότι το στοιχείο έχει προστεθεί και επιστρέφει την προεπιλεγμένη τιμή μηδέν όταν καλούμε τη μέθοδο size ().

@Mock List listMock = νέο ArrayList (); @Test public void testMockReturnsZero () ρίχνει την εξαίρεση {String s = "dobie"; listMock.add (νέα συμβολοσειρά); επαλήθευση (listMock) .add (s); assertEquals (0, listMock.size ()); } 

Ένα άλλο χρήσιμο χαρακτηριστικό του testSpy είναι η δυνατότητα διακοπής κλήσεων επιστροφής. Όταν γίνει αυτό, το αντικείμενο θα συμπεριφέρεται κανονικά έως ότου κληθεί η μέθοδος stubbed.

Σε αυτό το παράδειγμα στέλνουμε τη μέθοδο get για να ρίχνουμε πάντα ένα RuntimeException. Η υπόλοιπη συμπεριφορά παραμένει η ίδια.

@Test (Αναμένεται = RuntimeException.class) public void testSpyReturnsStubbedValues ​​() ρίχνει την Εξαίρεση {listSpy.add (new String ("dobie")); assertEquals (1, listSpy.size ()); όταν (listSpy.get (anyInt ())). thenThrow (νέο RuntimeException ()); listSpy.get (0); } 

Σε αυτό το παράδειγμα διατηρούμε ξανά τη βασική συμπεριφορά αλλά αλλάζουμε τη μέθοδο size () για να επιστρέψουμε 1 αρχικά και 5 για όλες τις επόμενες κλήσεις.

public void testSpyReturnsStubbedValues2 () ρίχνει την εξαίρεση {int size = 5; όταν (listSpy.size ()). τότε Επιστροφή (1, μέγεθος); int mockedListSize = listSpy.size (); assertEquals (1, mockedListSize); mockedListSize = listSpy.size (); assertEquals (5, mockedListSize); mockedListSize = listSpy.size (); assertEquals (5, mockedListSize); } 

Αυτό είναι αρκετά μαγικό!

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

Τα πλαστά αντικείμενα είναι συνήθως χειροποίητα ή ελαφριά αντικείμενα που χρησιμοποιούνται μόνο για δοκιμές και δεν είναι κατάλληλα για παραγωγή. Ένα καλό παράδειγμα θα ήταν μια βάση δεδομένων στη μνήμη ή ένα ψεύτικο επίπεδο υπηρεσίας.

Τείνουν να παρέχουν πολύ περισσότερη λειτουργικότητα από τα τυπικά διπλά δοκιμαστικά και ως εκ τούτου πιθανώς δεν είναι συνήθως υποψήφιοι για εφαρμογή χρησιμοποιώντας το Mockito. Αυτό δεν σημαίνει ότι δεν θα μπορούσαν να κατασκευαστούν ως τέτοια, αλλά μάλλον δεν αξίζει να εφαρμοστούν με αυτόν τον τρόπο.

Δοκιμάστε διπλά μοτίβα

Endo-Testing: Δοκιμή μονάδας με αντικείμενα Mock

Χλευαστικοί ρόλοι, όχι αντικείμενα

Οι χλευασμοί δεν είναι στέλεχος

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

Αυτή η ιστορία, "Mocks And Stubs - Understanding Test Doubles With Mockito" δημοσιεύθηκε αρχικά από την JavaWorld.

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