Spy — τι είναι, Mockito.spy() και επαλήθευση κλήσεων

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-04-10 Χρόνος ανάγνωσης: 9 λεπ

Spy (κατάσκοπος) — ένα αντικείμενο δοκιμής που τυλίγει μια πραγματική παρουσία και καταγράφει πληροφορίες για κάθε κλήση: ποιες μέθοδοι κλήθηκαν, με ποια ορίσματα, πόσες φορές. Σε αντίθεση με το mock, το spy χρησιμοποιεί την πραγματική υλοποίηση του τυλιγμένου αντικειμένου — οι κλήσεις περνούν μέσα από πραγματικό κώδικα και το spy καταγράφει μόνο γεγονότα. Μετά την εκτέλεση της δοκιμής, ο προγραμματιστής ελέγχει τις εγγραφές του κατασκόπου: „Κλήθηκε η μέθοδος sendAnalytics τρεις φορές;”. Περισσότερα — στον οδηγό δοκιμών Android.

Κύρια σημεία

  • Spy — αντικείμενο που καταγράφει κλήσεις μεθόδων της πραγματικής υλοποίησης χωρίς να την αντικαθιστά
  • Επαλήθευση — μετά τη δοκιμή, το spy επιτρέπει τον έλεγχο πόσες φορές και με ποια ορίσματα κλήθηκε η μέθοδος
  • Mockito.spy() — δημιουργεί κατάσκοπο στο Android για πραγματικά αντικείμενα Java/Kotlin
  • Μερική υποκατάσταση — το spy μπορεί να συνδυαστεί με stub: ορισμένες μεθόδους να υποκλαπούν, άλλες να αφεθούν
  • iOS — OCMock (Objective-C) και χειροκίνητοι κατάσκοποι μέσω πρωτοκόλλων σε Swift

Τι είναι το Spy και σε τι διαφέρει από το Mock;

Spy — είναι ένα περιτύλιγμα γύρω από ένα πραγματικό αντικείμενο που υποκλέπτει όλες τις κλήσεις μεθόδων και τις καταγράφει. Η πραγματική λογική του αντικειμένου εκτελείται: αν η μέθοδος αποθηκεύει δεδομένα, υπολογίζει μια τιμή ή κάνει ένα αίτημα — όλα γίνονται ως συνήθως. Επιπλέον, το spy καταγράφει μεταδεδομένα: όνομα μεθόδου, ορίσματα, αριθμό κλήσεων, χρόνο εκτέλεσης. Ο όρος αποτελεί μέρος της ταξινόμησης Meszaros (2007) και περιγράφεται λεπτομερώς στο άρθρο του Martin Fowler „Mocks Aren't Stubs”.

Βασική διαφορά από το Mock — το mock αντικαθιστά πλήρως το αντικείμενο με ένα stub δοκιμής, όλες οι μέθοδοι από προεπιλογή δεν κάνουν τίποτα. Το Spy τυλίγει ένα υπάρχον αντικείμενο: όλες οι μέθοδοι από προεπιλογή λειτουργούν ως συνήθως, αλλά ταυτόχρονα καταγράφονται. Αυτή η διαφορά είναι θεμελιώδης: το mock απομονώνει τον ελεγχόμενο κώδικα από την πραγματικότητα, το spy διατηρεί την πραγματικότητα και επιτρέπει την παρατήρησή της. Η επιλογή μεταξύ τους εξαρτάται από το τι ελέγχεται.

Πότε το Spy είναι η σωστή επιλογή

Spy — η σωστή επιλογή — αν ο ελεγχόμενος κώδικας τροποποιεί την κατάσταση ενός πραγματικού αντικειμένου και η δοκιμή πρέπει τόσο να ελέγξει το αποτέλεσμα (κατάσταση) όσο και να βεβαιωθεί ότι οι κλήσεις έγιναν στη σωστή σειρά. Το Mock δεν είναι κατάλληλο επειδή δεν εκτελεί την πραγματική υλοποίηση. Το Stub δεν είναι κατάλληλο επειδή δεν καταγράφει κλήσεις. Το Spy είναι το μοναδικό test double που ταυτόχρονα διατηρεί την πραγματική λογική και παρέχει πληροφορίες για τις κλήσεις.

Spy vs Mock: πότε να χρησιμοποιήσετε κατάσκοπο

Mock — πλήρης απομόνωση. Αν η δοκιμή δεν πρέπει να εξαρτάται από την υλοποίηση ενός πραγματικού αντικειμένου (για παράδειγμα, βάση δεδομένων ή πελάτης δικτύου), χρησιμοποιήστε mock. Το Mock εγγυάται ότι καμία κλήση δεν θα φτάσει στο πραγματικό στοιχείο. Αυτό είναι ασφαλές και προβλέψιμο. Μειονέκτημα: το mock δεν εκτελεί πραγματική λογική, οπότε αν ο ελεγχόμενος κώδικας βασίζεται στην επιστρεφόμενη τιμή — πρέπει να διαμορφωθεί ρητά μέσω when/stub.

Spy — πραγματική λογική + παρατήρηση. Αν ο ελεγχόμενος κώδικας αλληλεπιδρά με ένα αντικείμενο του οποίου η λογική είναι σημαντική για τη δοκιμή, όχι μόνο τα δεδομένα — χρησιμοποιήστε spy. Για παράδειγμα, AnalyticsTracker που συλλέγει συμβάντα και τα στέλνει περιοδικά. Η δοκιμή ελέγχει αν τα συμβάντα προστέθηκαν στο buffer και αν μετά την αποστολή το buffer καθαρίστηκε. Το Mock δεν μπορεί να το ελέγξει αυτό επειδή δεν εκτελεί την πραγματική λογική του tracker.

ΣενάριοSpyMock
Απαιτείται πραγματική λογικήΝαιΌχι (stub)
Επαλήθευση κλήσεωνΝαι (αριθμός, ορίσματα)Ναι (αριθμός, ορίσματα)
Μερική υποκατάστασηΝαι (ορισμένες μέθοδοι — spy, άλλες — stub)Όχι (όλες οι μέθοδοι — stub)
Κίνδυνος παρενεργειώνΥψηλός (πραγματικός κώδικας)Μηδενικός
ΤαχύτηταΧαμηλότερη (πραγματική λογική)Υψηλότερη (stub)
ΑναγνωσιμότηταΧαμηλότερη (δυσκολότερο να καταλάβει κανείς τι είναι πραγματικό)Υψηλότερη (όλα ρητά)

Αντυπρότυπο: spy για τα πάντα

Spy για τα πάντα — η χρήση spy αντί για mock σε όλες τις δοκιμές είναι λάθος. Το Spy εκτελεί πραγματικό κώδικα που μπορεί να έχει παρενέργειες: εγγραφή σε αρχείο, αποστολή HTTP, αλλαγή καθολικής κατάστασης. Αν η ελεγχόμενη ενότητα καλεί μια μέθοδο του spy-αντικειμένου που κάνει αίτημα HTTP, η δοκιμή γίνεται δοκιμή ολοκλήρωσης, όχι μοναδιαία δοκιμή. Κανόνας: αν το spy τυλίγει ένα αντικείμενο με λειτουργίες I/O — αυτό δεν είναι πλέον μοναδιαία δοκιμή. Χρησιμοποιήστε mock για απομόνωση I/O, spy — μόνο για αντικείμενα στη μνήμη χωρίς εξωτερικές επιδράσεις.

Mockito.spy() και spy στο MockK στο Android

Mockito.spy() — ο κλασικός τρόπος δημιουργίας κατασκόπου σε έργα Java/Kotlin. Η spy() λαμβάνει ένα πραγματικό αντικείμενο και επιστρέφει ένα περιτύλιγμα. Όλες οι κλήσεις από προεπιλογή ανατίθενται στο πραγματικό αντικείμενο και τα αποτελέσματα καταγράφονται. Μετά την εκτέλεση της δοκιμής, μέσω verify() μπορεί να ελεγχθεί ο αριθμός κλήσεων και τα ορίσματα. Για μεθόδους που πρέπει να επιστρέφουν δεδομένα δοκιμής, χρησιμοποιείται doReturn/when — αυτό ονομάζεται „μερική υποκατάσταση” (partial mocking).

kotlin
class AnalyticsReporterTest {

    private val realTracker = AnalyticsTracker()
    private val spyTracker = Mockito.spy(realTracker)

    fun test_event_tracked() {
        val event = AnalyticsEvent("login")
        spyTracker.track(event)

        Mockito.verify(spyTracker).track(event)
        assertEquals(1, spyTracker.getBufferedCount())
    }

    fun test_track_with_exception() {
        Mockito.doThrow(RuntimeException("network"))
            .when(spyTracker).flush()

        spyTracker.track(AnalyticsEvent("login"))
        assertTrue(spyTracker.hasPendingEvents())
    }
}

MockK.spyk() — εναλλακτική για έργα Kotlin με καλύτερη υποστήριξη για coroutine και sealed κλάσεις. Το MockK.spyk() δημιουργεί κατάσκοπο, ανάλογο του Mockito.spy(). Υποστηρίζει coVerify για συναρτήσεις suspend και every για μερική υποκατάσταση. Σε αντίθεση με το Mockito, το MockK δεν υποστηρίζει spy για final κλάσεις (όλες οι κλάσεις στο Kotlin είναι από προεπιλογή final) — πρέπει είτε να ανοίξετε την κλάση (open) είτε να χρησιμοποιήσετε διεπαφή.

kotlin
class LoginUseCaseTest {

    private val realRepo = UserRepository()
    private val spyRepo = spyk(realRepo)

    private val useCase = LoginUseCase(spyRepo)

    fun test_login_calls_save() = runTest {
        every { spyRepo.getUser(any()) } returns User("test")

        val result = useCase.login("test", "pass")

        coVerify { spyRepo.saveLoginTime(any()) }
        assertTrue(result.isSuccess)
    }
}

Μερική υποκατάσταση μέσω spy

Μερική υποκατάσταση — μια ισχυρή αλλά επικίνδυνη τεχνική. Μπορείτε να δημιουργήσετε spy ενός αντικειμένου και να παρακάμψετε (stub) μόνο ορισμένες μεθόδους, αφήνοντας τις άλλες πραγματικές. Παράδειγμα: ένα spy-αποθετήριο του οποίου η getUser() επιστρέφει δεδομένα δοκιμής και η saveUser() αποθηκεύει πραγματικά σε μια λίστα στη μνήμη. Αυτό επιτρέπει το συνδυασμό των πλεονεκτημάτων των stub (ελεγχόμενα δεδομένα) και του spy (πραγματική λογική). Μειονέκτημα: πολυπλοκότητα ανάγνωσης της δοκιμής — δεν είναι προφανές ποιες μέθοδοι είναι πραγματικές και ποιες είναι stub.

Υλοποίηση Spy στο iOS με OCMock και πρωτόκολλα

OCMock για Objective-C — βιβλιοθήκη που υποστηρίζει τη δημιουργία spy-αντικειμένων μέσω niceMock. Το OCMock υποκλέπτει κλήσεις μεθόδων χρησιμοποιώντας το runtime του Objective-C και τις καταγράφει. Μετά την εκτέλεση της δοκιμής, καλείται η verify. Το OCMock υποστηρίζει spy για οποιοδήποτε αντικείμενο (στο Objective-C όλες οι μέθοδοι είναι δυναμικές), που δίνει πλεονέκτημα έναντι του Swift, όπου το spy είναι δυνατό μόνο μέσω πρωτοκόλλων.

objective-c
// Δημιουργία spy για πραγματικό αντικείμενο
AnalyticsTracker *realTracker = [[AnalyticsTracker alloc] init];
AnalyticsTracker *spy = [OCMockObject partialMockForObject:realTracker];

// Εκτέλεση δοκιμής
[spy trackEvent:@"login"];

// Επαλήθευση
[[spy verify] trackEvent:@"login"];
XCTAssertEqual([realTracker eventCount], 1);

Swift protocol-based spy — στο Swift δεν υπάρχει αντανάκλαση runtime Objective-C, επομένως το spy δημιουργείται χειροκίνητα. Η δομή δοκιμής υλοποιεί το πρωτόκολλο και εσωτερικά καλεί το πραγματικό αντικείμενο, ταυτόχρονα καταγράφοντας τις κλήσεις. Αυτό είναι περισσότερος κώδικας, αλλά πλήρως ελέγξιμο και type-safe. Οι χειροκίνητοι κατάσκοποι δεν απαιτούν εξωτερικές βιβλιοθήκες και δεν χρησιμοποιούν runtime — όλα ελέγχονται στη φάση μεταγλώττισης.

swift
protocol AnalyticsProtocol {
    func trackEvent(name: String)
}

final class SpyAnalytics: AnalyticsProtocol {
    private let real: AnalyticsProtocol
    private var events: [String] = []

    init(real: AnalyticsProtocol) {
        self.real = real
    }

    func trackEvent(name: String) {
        events.append(name)
        real.trackEvent(name: name)
    }

    func verifyTracked(name: String) -> Bool {
        return events.contains(name)
    }
}

Πότε να χρησιμοποιήσετε OCMock vs χειροκίνητο spy — για κώδικα Objective-C χρησιμοποιήστε OCMock (λιγότερο boilerplate). Για Swift — προτιμώνται οι χειροκίνητοι κατάσκοποι μέσω πρωτοκόλλων. Ο χειροκίνητος spy δίνει πλήρη έλεγχο επί της καταγραφής κλήσεων, δεν απαιτεί αντανάκλαση και λειτουργεί με τύπους τιμών (struct). Το μόνο μειονέκτημα: ο κώδικας της κλάσης spy πρέπει να παραμένει συγχρονισμένος με το πρωτόκολλο κατά την προσθήκη νέων μεθόδων.

Τυπικά σενάρια εφαρμογής του Spy

Έλεγχος αναλυτικών — το πιο συνηθισμένο σενάριο χρήσης spy. Στον κώδικα παραγωγής, οι κλήσεις αναλυτικών είναι διάσπαρτες σε ολόκληρη την εφαρμογή: login, logout, purchase, error. Η δοκιμή δημιουργεί ένα spy-περιτύλιγμα για το AnalyticsTracker, εκτελεί το σενάριο (σύνδεση, προβολή προϊόντος, προσθήκη στο καλάθι, αγορά) και ελέγχει ότι όλα τα απαραίτητα συμβάντα στάλθηκαν στη σωστή σειρά. Το Mock δεν είναι κατάλληλο επειδή το AnalyticsTracker περιέχει λογική προσωρινής αποθήκευσης και αποστολής.

Χρονοδιακόπτες και προγραμματιστές — η δοκιμή κώδικα που χρησιμοποιεί Handler (Android) ή Timer (iOS) είναι δύσκολη λόγω πραγματικού χρόνου. Το spy-περιτύλιγμα για Scheduler καταγράφει ποιες εργασίες προγραμματίστηκαν και με ποια καθυστέρηση. Η δοκιμή δημιουργεί spy του πραγματικού Handler, εκτελεί την ενέργεια και ελέγχει αν το Handler.postDelayed(runnable, delay) κλήθηκε με τη σωστή καθυστέρηση. Η πραγματική εργασία δεν εκτελείται — το spy υποκλέπτει και καταγράφει την κλήση.

Καταγραφή και πληροφορίες εντοπισμού σφαλμάτων — στην παραγωγή, τα αρχεία καταγραφής μπορεί να είναι απενεργοποιημένα ή να γράφονται σε αρχείο. Το spy-περιτύλιγμα για Logger καταγράφει όλα τα μηνύματα σε μια λίστα στη μνήμη που η δοκιμή ελέγχει μετά την εκτέλεση. Αυτό επιτρέπει τον έλεγχο ότι κατά την ώρα σφάλματος γράφεται ένα σωστό μήνυμα, χωρίς να γεμίζει η κονσόλα. Οι χειροκίνητοι κατάσκοποι για Logger είναι ιδιαίτερα χρήσιμοι στο iOS, όπου το OSLog δεν έχει API δοκιμής.

Έλεγχος σειράς κλήσεων — ορισμένα σενάρια απαιτούν αυστηρή σειρά λειτουργιών: άνοιγμα σύνδεσης, αποστολή δεδομένων, κλείσιμο σύνδεσης. Το Mockito επιτρέπει τον έλεγχο της σειράς μέσω InOrder.verify(). Το Spy κάνει το ίδιο αλλά διατηρεί την πραγματική εκτέλεση. Αν δεν είναι σημαντική μόνο η σειρά αλλά και το αποτέλεσμα κάθε βήματος (η σύνδεση άνοιξε πραγματικά) — χρησιμοποιήστε spy, όχι mock.

Συχνές Ερωτήσεις

Spy vs Mock: ποια είναι η κύρια διαφορά;

Spy τυλίγει ένα πραγματικό αντικείμενο και εκτελεί τη λογική του, επιπλέον καταγράφοντας κλήσεις. Το Mock αντικαθιστά πλήρως το αντικείμενο με stub — καμία πραγματική λογική δεν εκτελείται. Το Spy διατηρεί τη συμπεριφορά, το mock όχι. Επιλέξτε spy όταν η πραγματική εργασία του αντικειμένου είναι σημαντική επιλέξτε mock όταν πρέπει να απομονώσετε τη δοκιμή από μια εξωτερική εξάρτηση.

Πότε το Spy είναι κακή επιλογή;

Όταν το spy-περιτύλιγμα οδηγεί σε πραγματικές λειτουργίες I/O. Αν το spy τυλίγει ένα αντικείμενο που γράφει σε αρχείο, στέλνει HTTP ή διαβάζει από δίσκο — η δοκιμή παύει να είναι μοναδιαία δοκιμή. Δεύτερη περίπτωση: η δοκιμή ελέγχει μόνο την επιστρεφόμενη τιμή χωρίς ενδιαφέρον για κλήσεις — εδώ αρκεί το stub και το spy είναι περιττό. Τρίτη: ο κώδικας βασίζεται στην εσωτερική κατάσταση του spy — αυτή είναι μια εύθραυστη δοκιμή.

Υποστηρίζει το MockK spy;

Ναι, μέσω spyk() — ανάλογο του Mockito.spy(). Το MockK.spyk() δημιουργεί κατάσκοπο γύρω από ένα πραγματικό αντικείμενο, υποστηρίζει every για μερική υποκατάσταση και coVerify/coroutinesVerify για συναρτήσεις suspend. Περιορισμός: δεν λειτουργεί με final κλάσεις (απαιτείται open ή διεπαφή). Για κλάσεις Java, το MockK υποστηρίζει επίσης spyk(), αλλά απαιτεί την επισήμανση @MockKJvmInline.

Μπορεί να γίνει Spy από Mock;

Τεχνικά — όχι. Το Mock είναι ένα stub που δεν περιέχει πραγματική υλοποίηση. Το Spy εξ ορισμού τυλίγει ένα πραγματικό αντικείμενο. Στο Mockito, ένα mock δεν μπορεί να μετατραπεί σε spy. Αλλά το αντίθετο μπορεί να γίνει: δημιουργήστε ένα spy και παρακάμψτε μέρος των μεθόδων μέσω doReturn/when (partial mocking). Αυτό δίνει συμπεριφορά παρόμοια με mock για επιλεγμένες μεθόδους του spy-αντικειμένου.

Spy στο Swift — υποχρεωτικά μέσω πρωτοκόλλου;

Υποχρεωτικά. Στο Swift δεν υπάρχει δυναμική μεσολάβηση όπως σε Java/Kotlin. Για τη δημιουργία spy απαιτείται ένα πρωτόκολλο που υλοποιείται τόσο από την κλάση παραγωγής όσο και από την κλάση spy. Το Swift-protocol-based spy είναι μια χειροκίνητη υλοποίηση που λαμβάνει ένα πραγματικό αντικείμενο, του αναθέτει κλήσεις και καταγράφει μεταδεδομένα. Εναλλακτική: η βιβλιοθήκη Cuckoo, η οποία δημιουργεί κλάσεις spy μέσω SourceKit.

Σύνοψη

  • Spy — περιτύλιγμα γύρω από πραγματικό αντικείμενο που καταγράφει όλες τις κλήσεις μεθόδων χωρίς αντικατάσταση λογικής
  • Διαφορά από Mock — το spy εκτελεί πραγματικό κώδικα, το mock τον αντικαθιστά με stub
  • Mockito.spy() — για Java/Kotlin, τυλίγει πραγματικό αντικείμενο με δυνατότητα verify και partial mock
  • MockK.spyk() — ανάλογο Kotlin με υποστήριξη coroutine, sealed κλάσεων και coVerify
  • iOS — OCMock για Objective-C, χειροκίνητο protocol-based spy για Swift
  • Κύριο σενάριο — έλεγχος αναλυτικών, χρονοδιακοπτών, καταγραφών και σειράς κλήσεων
  • Προσοχή — το spy με λειτουργίες I/O μετατρέπει μοναδιαία δοκιμή σε δοκιμή ολοκλήρωσης

Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση

Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης