Stub (στρατηγείο, υποκατάστατο) — ένα αντικείμενο δοκιμής που επιστρέφει προκαθορισμένες απαντήσεις σε κλήσεις μεθόδων αντί για πραγματική υλοποίηση. Στην ανάπτυξη κινητών, τα stub απομονώνουν την υπό δοκιμή μονάδα από αιτήματα δικτύου, βάση δεδομένων και σύστημα αρχείων, επιτρέποντας τον έλεγχο λογικής χωρίς ρύθμιση περιβάλλοντος. Σε αντίθεση με το mock, το stub δεν επαληθεύει συμπεριφορά — παρέχει μόνο δεδομένα. Περισσότερα στο άρθρο του Martin Fowler για test doubles.
Κύρια σημεία
Stub — είναι ένα αντικείμενο υποκατάστατο που αντικαθιστά την πραγματική εξάρτηση στη δοκιμή και επιστρέφει προκαθορισμένες τιμές σε συγκεκριμένες κλήσεις. Ο όρος εισήχθη στην ταξινόμηση του Gerard Meszaros (2007) στο βιβλίο “xUnit Test Patterns”. Το Stub ανήκει στην κατηγορία test doubles — αντικείμενα που αντικαθιστούν πραγματικά στοιχεία κατά τη διάρκεια της δοκιμής. Ο κύριος σκοπός του stub είναι να παρέχει προβλέψιμα δεδομένα στο υπό δοκιμή μπλοκ, εξαλείφοντας την αβεβαιότητα εξωτερικών συστημάτων.
Αρχή λειτουργίας — η δοκιμή ρυθμίζει το stub πριν από την εκτέλεση: “όταν κληθεί η μέθοδος getUsers(), επέστρεψε αυτήν τη λίστα χρηστών”. Το Stub δεν περιέχει επιχειρηματική λογική, δεν ελέγχει τη σειρά κλήσεων και δεν καταγράφει ιστορικό προσβάσεων. Απλώς στέκεται στη θέση του πραγματικού στοιχείου και επιστρέφει ό,τι του έχει ειπωθεί. Στο πλαίσιο της δοκιμής Android, αυτό σημαίνει ότι ο πελάτης OkHttp δεν κάνει πραγματικό αίτημα στον διακομιστή, αλλά λαμβάνει απάντηση από το MockWebServer που έχει ρυθμιστεί ως stub.
Πότε να χρησιμοποιείτε — τα stub είναι βέλτιστα για δοκιμή του επιπέδου UI (ViewModel, Presenter) και επιχειρηματικής λογικής (UseCase, Interactor), όπου πρέπει να ελεγχθεί η αντίδραση σε συγκεκριμένα δεδομένα: κενή λίστα, ο διακομιστής επέστρεψε σφάλμα 500, λήξη token. Κάθε περίπτωση όπου η δοκιμή απαιτεί συγκεκριμένη κατάσταση εισόδου είναι εργασία για stub. Για κάθε σενάριο δοκιμής δημιουργείται η δική του ρύθμιση stub, κάνοντας τις δοκιμές ευανάγνωστες και προβλέψιμες.
Gerard Meszaros (2007) στο βιβλίο “xUnit Test Patterns” εντόπισε πέντε τύπους test doubles: dummy, stub, spy, mock, fake. Κάθε τύπος λύνει τη δική του εργασία. Dummy — μεταβιβάζεται αλλά δεν χρησιμοποιείται. Stub — επιστρέφει δεδομένα. Spy — καταγράφει κλήσεις. Mock — επαληθεύει συμπεριφορά. Fake — περιέχει απλοποιημένη λογική. Η κατανόηση αυτής της ταξινόμησης βοηθά τον προγραμματιστή να επιλέξει το σωστό εργαλείο για κάθε σενάριο δοκιμής.
Αιτήματα δικτύου — το πιο συνηθισμένο σενάριο χρήσης stub. Η εφαρμογή κάνει κλήσεις HTTP στο API, και στη δοκιμή πρέπει να ελεγχθεί η αντίδραση σε διάφορες απαντήσεις: επιτυχές JSON, σφάλμα 401 (μη εξουσιοδοτημένο), χρονικό όριο, κενός πίνακας. Το MockWebServer (OkHttp) σε Android και το URLProtocol (iOS) λειτουργούν ως stub, επιστρέφοντας προκαθορισμένες απαντήσεις HTTP χωρίς πραγματική σύνδεση με τον διακομιστή. Αυτό επιταχύνει τις δοκιμές από δευτερόλεπτα σε χιλιοστά του δευτερολέπτου.
Βάση δεδομένων — τα Room (Android) και CoreData (iOS) έχουν in-memory παραλλαγές, αλλά η ρύθμισή τους εξακολουθεί να απαιτεί χρόνο. Το Stub αντί για αποθετήριο επιστρέφει προετοιμασμένες λίστες Entity χωρίς να αγγίζει τη βάση δεδομένων. Αυτό είναι ιδιαίτερα αποτελεσματικό για δοκιμή ViewModel, όπου πρέπει να ελεγχθεί η ταξινόμηση, το φιλτράρισμα ή ο μετασχηματισμός δεδομένων. Η δοκιμή εκτελείται σε χιλιοστά δευτερολέπτου ανεξάρτητα από τον όγκο δεδομένων.
Υπηρεσίες συστήματος — LocationManager, SensorManager, SharedPreferences απαιτούν πραγματική συσκευή ή εξομοιωτή. Το Stub για LocationProvider επιστρέφει καθορισμένες συντεταγμένες, για SensorManager — σταθερές τιμές επιταχυνσιόμετρου. Σε iOS, το ανάλογο είναι CLLocationManager με υλοποίηση δοκιμής του εκπροσώπου. Χωρίς stub, τέτοιες δοκιμές απαιτούν φυσική συσκευή με συγκεκριμένες συνθήκες.
Σύστημα αρχείων και κρυφή μνήμη — φόρτωση εικόνων, προσωρινή αποθήκευση απαντήσεων, εργασία με αρχεία ρύθμισης — όλες αυτές οι λειτουργίες εξαρτώνται από την κατάσταση του δίσκου. Το Stub για FileManager ή ImageCache επιστρέφει επιτυχία/σφάλμα χωρίς ανάγνωση πραγματικών αρχείων. Αυτό εξαλείφει ψευδείς αποτυχίες δοκιμών λόγω ασυμφωνίας διαδρομών ή δικαιωμάτων σε διαφορετικούς υπολογιστές προγραμματιστών.
Διαχωρισμός ευθυνών — τρεις τύποι test doubles επιλύουν διαφορετικές εργασίες. Stub: “δώσε μου δεδομένα”. Mock: “έλεγξε αν με κάλεσαν”. Fake: “λειτουργώ σαν πραγματικός, μόνο πιο απλά”. Η διαφορά είναι κρίσιμη για την αναγνωσιμότητα των δοκιμών: εάν μια δοκιμή χρησιμοποιεί mock εκεί όπου χρειάζεται stub, υπερφορτώνεται με κλήσεις verify που δεν σχετίζονται με το σενάριο δοκιμής.
| Χαρακτηριστικό | Stub | Mock | Fake |
|---|---|---|---|
| Σκοπός | Παροχή δεδομένων | Έλεγχος αλληλεπίδρασης | Απλοποιημένη υλοποίηση |
| Λογική | Όχι | Όχι | Ναι (αλλά απλοποιημένη) |
| Επαλήθευση | Όχι | Ναι (verify) | Έμμεση (μέσω κατάστασης) |
| Ευελιξία | Χαμηλή — σταθερές απαντήσεις | Μεσαία | Υψηλή — η λογική προσαρμόζεται |
| Ταχύτητα | Μέγιστη | Υψηλή | Μεσαία |
| Παράδειγμα | MockWebServer επιστρέφει JSON | Mockito.verify(repository).save() | InMemoryRepository με HashMap |
Πρακτικός κανόνας — εάν η δοκιμή ελέγχει ποια δεδομένα έλαβε το υπό δοκιμή στοιχείο — χρησιμοποιήστε stub. Εάν η δοκιμή ελέγχει αν το στοιχείο κάλεσε τη μέθοδο εξάρτησης με σωστά ορίσματα — χρησιμοποιήστε mock. Εάν απλώς θέλετε να αντικαταστήσετε τη βάση δεδομένων με έναν πίνακα κατακερματισμού — αυτό είναι fake. Η ανάμειξη τύπων σε μία δοκιμή την καθιστά εύθραυστη: όταν αλλάξει η υλοποίηση, θα πρέπει να ξαναγραφούν τόσο το stub όσο και η λογική verify.
Stub με verify — ένα συνηθισμένο λάθος όταν ο προγραμματιστής ρυθμίζει το stub και στη συνέχεια προσθέτει verify(stub).method(). Το Stub εξ ορισμού δεν πρέπει να επαληθεύεται — για επαλήθευση υπάρχει το mock. Εάν πρέπει να ελέγξετε ότι η μέθοδος κλήθηκε με συγκεκριμένα ορίσματα, χρησιμοποιήστε Mockito.mock() αντί για Mockito.stub(). Αυτός ο διαχωρισμός διατηρεί την πρόθεση της δοκιμής σαφή για άλλους προγραμματιστές.
MockWebServer — βιβλιοθήκη OkHttp για δημιουργία HTTP stub σε Android και JVM. Ξεκινά έναν τοπικό διακομιστή HTTP σε καθορισμένη θύρα που παρεμποδίζει αιτήματα του πελάτη OkHttp και επιστρέφει προκαθορισμένες απαντήσεις. Η ρύθμιση διαρκεί τρεις γραμμές: δημιουργία διακομιστή, τοποθέτηση απάντησης σε ουρά (enqueue), εκκίνηση. Η δοκιμή μπορεί να τοποθετήσει διαδοχικά πολλές απαντήσεις σε ουρά για σενάρια με σελιδοποίηση ή επαναλήψεις.
class UserRepositoryTest {
private val server = MockWebServer()
fun setup() {
server.start(8080)
val client = OkHttpClient.Builder()
.readTimeout(1, TimeUnit.SECONDS)
.build()
}
fun test_user_list_success() {
val json = "[{\"id\":1,\"name\":\"Alice\"}]"
server.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200)
)
val result = repository.getUsers()
assertEquals(1, result.size)
}
fun teardown() {
server.shutdown()
}
}
MockK — εναλλακτική του Mockito για Kotlin με υποστήριξη πρώτης κατηγορίας για coroutines, συναρτήσεις επέκτασης και sealed κλάσεις. Τα stub στο MockK δημιουργούνται μέσω coEvery (για συναρτήσεις suspend) και every (για συνηθισμένες συναρτήσεις). Σε αντίθεση με το MockWebServer, το MockK αντικαθιστά μεμονωμένες μεθόδους εξάρτησης, όχι ολόκληρο το επίπεδο HTTP. Αυτό είναι βολικό για δοκιμές μονάδας UseCase ή Interactor, όπου οι εξαρτήσεις είναι αφαιρέσεις αποθετηρίων.
interface UserRepository {
suspend fun getUsers(): List<User>
}
class GetUsersUseCaseTest {
private val repo = mockk<UserRepository>()
private val useCase = GetUsersUseCase(repo)
fun test_empty_list() = runTest {
coEvery { repo.getUsers() } returns emptyList()
val result = useCase.invoke()
assertTrue(result.isEmpty())
coVerify(exactly = 1) { repo.getUsers() }
}
}
Best practice — για δοκιμές ολοκλήρωσης χρησιμοποιήστε MockWebServer (παρεμποδίζει πραγματικό HTTP), για δοκιμές μονάδας — MockK (αντικαθιστά διεπαφές). Μην αντικαθιστάτε ό,τι δεν δοκιμάζετε: εάν η δοκιμή ελέγχει το Repository, μην αντικαθιστάτε τον πελάτη OkHttp μέσα σε αυτό — χρησιμοποιήστε πραγματικό MockWebServer στο επίπεδο HTTP. Αυτός ο κανόνας διατηρεί τις δοκιμές σχετικές και μειώνει την ευθραυστότητα κατά την αναδιάρθρωση.
Πρωτόκολλα Swift ως stub — στην εγγενή προσέγγιση iOS, το stub υλοποιείται με υποκατάσταση μιας δομής δοκιμής που συμμορφώνεται με το πρωτόκολλο εξάρτησης. Αντί για πραγματικό NetworkService, η δοκιμή λαμβάνει StubNetworkService που επιστρέφει σταθερά δεδομένα. Η Swift είναι γλώσσα με στατική πληκτρολόγηση, επομένως το stub πρέπει να συμμορφώνεται με το ίδιο πρωτόκολλο με την πραγματική υπηρεσία. Ο μεταγλωττιστής εγγυάται ότι το stub υλοποιεί όλες τις απαιτούμενες μεθόδους.
protocol NetworkServiceProtocol {
func fetchUsers() async throws -> [User]
}
struct StubNetworkService: NetworkServiceProtocol {
let result: Result<[User], Error>
func fetchUsers() async throws -> [User] {
try result.get()
}
}
final class UsersViewModelTests: XCTestCase {
func test_success_state() async {
let stub = StubNetworkService(
result: .success([User(name: "Alice")])
)
let vm = UsersViewModel(service: stub)
await vm.load()
XCTAssertEqual(vm.users.count, 1)
}
}
OCMock για Objective-C — βιβλιοθήκη για δημιουργία stub και mock σε παλαιότερα έργα iOS. Το OCMock υποστηρίζει μεθόδους stub με ορίσματα και επιστρεφόμενες τιμές. Τα σύγχρονα έργα σε Swift προτιμούν την προσέγγιση βασισμένη σε πρωτόκολλα με χειροκίνητα stub — αυτό παρέχει έλεγχο σε κάθε μέθοδο και δεν απαιτεί εξωτερικές εξαρτήσεις. Το OCMock παραμένει επιλογή για έργα όπου η πρωτοκόλληση όλων των εξαρτήσεων δεν είναι οικονομικά εφικτή.
URLProtocol για HTTP stub — μηχανισμός συστήματος iOS για παρεμπόδιση αιτημάτων δικτύου μέσω υποκλάσης URLProtocol. Η δοκιμή καταχωρεί ένα προσαρμοσμένο URLProtocol που παρεμποδίζει το URLSession και επιστρέφει απαντήσεις stub. Πλεονέκτημα έναντι χειροκίνητων stub: δεν χρειάζεται να αλλάξετε την αρχιτεκτονική της εφαρμογής — το URLSession παραμένει πραγματικό, αλλά τα δεδομένα αντικαθίστανται σε επίπεδο πρωτοκόλλου. Μειονέκτημα: δυσκολότερος εντοπισμός σφαλμάτων από μια ρητή υπηρεσία stub.
Συχνές Ερωτήσεις
Stub επιστρέφει προκαθορισμένα δεδομένα και δεν ελέγχει το γεγονός της κλήσης. Το Mock επιπλέον επαληθεύει ότι η μέθοδος κλήθηκε με σωστά ορίσματα (verify). Το Stub απαντά στην ερώτηση “τι να επιστρέψω”, το Mock — στην ερώτηση “έγινε κλήση”. Χρησιμοποιήστε stub για έλεγχο κατάστασης, mock — για έλεγχο αλληλεπίδρασης.
Fake χρειάζεται όταν η δοκιμή απαιτεί λειτουργική (έστω απλοποιημένη) υλοποίηση — για παράδειγμα, in-memory βάση δεδομένων αντί για Room. Το Stub είναι κατάλληλο για μεμονωμένα σενάρια με προκαθορισμένα δεδομένα. Αν επαναλαμβάνετε το ίδιο stub σε 10 δοκιμές — πιθανότατα χρειάζεστε Fake. Το Fake μειώνει την επανάληψη επειδή η λογική ζει σε μια κλάση.
Σε Android — MockK για αντικείμενα Kotlin (object) υποστηρίζει mockkObject(), συμπεριλαμβανομένων στατικών μεθόδων κλάσεων Java μέσω mockkStatic(). Σε iOS — οι στατικές μέθοδοι Swift δεν γίνονται άμεσα stub· χρησιμοποιήστε πρωτόκολλα και DI για να αντικαταστήσετε την κλήση static με μέθοδο παρουσίας πρωτοκόλλου. Τα στατικά stub είναι τεχνικό χρέος και πρέπει να αποφεύγονται σε νέο κώδικα.
Χρησιμοποιήστε MockWebServer (OkHttp) — λειτουργεί ως τοπικός διακομιστής HTTP που τοποθετεί απαντήσεις σε ουρά (enqueue). Για Retrofit, αρκεί να αλλάξετε το βασικό URL σε localhost:8080. Για Ktor, χρησιμοποιήστε MockEngine — τον ενσωματωμένο μηχανισμό για αντικατάσταση του HttpStatement. Και οι δύο προσεγγίσεις λειτουργούν χωρίς πραγματικό διαδίκτυο και παρέχουν πλήρη έλεγχο του κωδικού κατάστασης, του σώματος και των κεφαλίδων της απάντησης.
Spy τυλίγει το πραγματικό αντικείμενο και καταγράφει κλήσεις, ενώ το Stub αντικαθιστά πλήρως το αντικείμενο με σταθερές απαντήσεις. Το Spy επιτρέπει μερική χρήση της πραγματικής υλοποίησης (οι άλλες μέθοδοι λειτουργούν όπως πριν), ενώ το stub όχι. Εάν πρέπει να ελέγξετε ότι μια μέθοδος κλήθηκε αλλά μέρος της λογικής πρέπει να εκτελεστεί — χρησιμοποιήστε spy, όχι stub.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης