Unit testing στην ανάπτυξη εφαρμογών για κινητά: τι είναι, μέθοδοι και frameworks

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

Το unit testing είναι μια μέθοδος ελέγχου λογισμικού κατά την οποία ελέγχεται η ορθότητα λειτουργίας μεμονωμένων modules ή συναρτήσεων κώδικα σε απομόνωση από το υπόλοιπο σύστημα. Σύμφωνα με τα δεδομένα του Martin Fowler, 2026, τα unit tests αποτελούν το θεμέλιο του CI/CD και του refactoring, παρέχοντας γρήγορη ανατροφοδότηση σχετικά με τη λειτουργικότητα του κώδικα. Το modular testing βοηθά στον εντοπισμό σφαλμάτων στα πρώτα στάδια ανάπτυξης, μειώνοντας το κόστος διόρθωσής τους δεκάδες φορές.

Βασικά σημεία

  • Unit test — έλεγχος ενός module (συνάρτησης, μεθόδου, κλάσης) σε απομόνωση από εξωτερικές εξαρτήσεις
  • Αρχές FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — η βάση των ποιοτικών tests
  • Mocks και stubs — υποκατάστατα εξωτερικών εξαρτήσεων (DB, API, σύστημα αρχείων) που εξασφαλίζουν την απομόνωση του test
  • TDD (Test-Driven Development) — μεθοδολογία ανάπτυξης μέσω testing: red-green-refactor
  • Πυραμίδα tests — τα unit tests καταλαμβάνουν το 70% της πυραμίδας, παρέχοντας γρήγορη ανατροφοδότηση σε κάθε commit

Τι είναι το unit testing;

Το unit testing είναι η διαδικασία ελέγχου μεμονωμένων μονάδων (unit) του πηγαίου κώδικα — συναρτήσεων, μεθόδων, κλάσεων — σε απομόνωση από το υπόλοιπο πρόγραμμα. Κάθε test εκτελεί ένα συγκεκριμένο σενάριο χρήσης του module και ελέγχει αν το αποτέλεσμα αντιστοιχεί στο αναμενόμενο. Τα unit tests γράφονται στην ίδια γλώσσα προγραμματισμού με τον βασικό κώδικα και εκτελούνται αυτόματα στο περιβάλλον ανάπτυξης ή στο CI/CD pipeline. Σε αντίθεση με τα integration tests, τα unit tests δεν αλληλεπιδρούν με πραγματικές βάσεις δεδομένων, σύστημα αρχείων ή δικτυακές υπηρεσίες.

Γιατί χρειαζόμαστε unit tests;

Ο κύριος στόχος είναι η γρήγορη ανατροφοδότηση σχετικά με την ορθότητα του κώδικα μετά από αλλαγές. Αν ένας προγραμματιστής κάνει refactor σε μια μέθοδο, το σύνολο των unit tests επιβεβαιώνει ότι η συμπεριφορά δεν έχει σπάσει. Σύμφωνα με το Google Testing Blog (2025), τα projects με κάλυψη unit tests άνω του 60% έχουν 2,5 φορές λιγότερα περιστατικά σε παραγωγή. Πρόσθετα πλεονεκτήματα: τεκμηρίωση κώδικα (τα tests δείχνουν πώς να χρησιμοποιείται το API), απλοποίηση του refactoring (η υλοποίηση μπορεί να αλλάξει διατηρώντας τη συμπεριφορά) και γρήγορη διάγνωση regressions.

Τι θεωρείται unit test;

Δεν είναι κάθε αυτόματο test unit test. Κριτήρια: ελέγχεται ένα module (κλάση ή συνάρτηση), οι εξωτερικές εξαρτήσεις αντικαθίστανται με mocks ή stubs, το test εκτελείται σε χιλιοστά του δευτερολέπτου, δεν απαιτεί εκκίνηση server ή βάσης δεδομένων. Ένα test που προσπελαύνει πραγματική βάση δεδομένων είναι integration test. Ένα test που ανοίγει browser είναι E2E test. Η κατανόηση των ορίων μεταξύ των τύπων tests είναι σημαντική για τη σωστή κατανομή προσπαθειών στην πυραμίδα testing.

Οι αρχές FIRST και η δομή AAA

Τα ποιοτικά unit tests ακολουθούν τις αρχές FIRST, όπως διατυπώθηκαν από τον Robert C. Martin. Κάθε test πρέπει να είναι Fast (γρήγορο — χιλιοστά δευτερολέπτου), Isolated (απομονωμένο — δεν εξαρτάται από άλλα tests), Repeatable (αναπαραγώγιμο — ίδιο αποτέλεσμα σε οποιοδήποτε μηχάνημα), Self-validating (αυτοεπαληθευόμενο — αποτέλεσμα “passed” ή “failed”, χωρίς χειροκίνητο έλεγχο) και Timely (έγκαιρο — γραμμένο πριν ή ταυτόχρονα με τον κώδικα). Η παραβίαση οποιασδήποτε αρχής μειώνει την αξία του test.

Δομή AAA (Arrange-Act-Assert)

Το τυπικό μοτίβο για τη σύνταξη unit tests. Arrange — προετοιμασία δεδομένων και εξαρτήσεων: δημιουργία αντικειμένων, ρύθμιση mocks, καθορισμός παραμέτρων εισόδου. Act — εκτέλεση της υπό δοκιμή ενέργειας: κλήση μεθόδου ή συνάρτησης. Assert — έλεγχος αποτελέσματος: σύγκριση πραγματικής τιμής με την αναμενόμενη. Ο διαχωρισμός σε τρία blocks κάνει το test αναγνώσιμο και κατανοητό. Αν το block Assert απαιτεί σύνθετη λογική, το test πιθανώς ελέγχει πάρα πολλά πράγματα ταυτόχρονα.

kotlin
// Παράδειγμα unit test με το μοτίβο AAA σε Kotlin με JUnit 5
class CalculatorTest {

    private lateinit var calculator: Calculator

    @BeforeEach
    fun setUp() {
        // ARRANGE — δημιουργούμε το υπό δοκιμή αντικείμενο
        calculator = Calculator()
    }

    @Test
    fun addition_shouldReturnCorrectSum() {
        // ACT — εκτελούμε την ενέργεια
        val result = calculator.add(2, 3)

        // ASSERT — ελέγχουμε το αποτέλεσμα
        Assertions.assertEquals(5, result)
    }
}

Ονομασία tests

Το όνομα του test πρέπει να περιγράφει τι ελέγχεται και ποιο αποτέλεσμα αναμένεται. Μορφή: [methodName]_[scenario]_[expectedResult]. Παράδειγμα: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Ένα καλό όνομα test αντικαθιστά το σχόλιο και σε περίπτωση αποτυχίας υποδεικνύει αμέσως ποια λειτουργικότητα έχει σπάσει. Αποφύγετε ονόματα όπως test1, checkSomething ή verify — δεν μεταφέρουν πληροφορία και δυσχεραίνουν τη διάγνωση.

Mocks, stubs και fakes: τι και πότε να χρησιμοποιείτε

Για την απομόνωση του υπό δοκιμή module από εξωτερικές εξαρτήσεις χρησιμοποιούνται test doubles. Κύριοι τύποι: mocks — ελέγχουν αν μια συγκεκριμένη μέθοδος κλήθηκε με τις σωστές παραμέτρους; stubs — επιστρέφουν προκαθορισμένες τιμές κατά την κλήση της μεθόδου; fakes — απλοποιημένη υλοποίηση ενός πραγματικού component (π.χ. InMemoryUserRepository αντί για UserRepository που λειτουργεί με DB). Η επιλογή εξαρτάται από το τι πρέπει να ελεγχθεί: κατάσταση (stub) ή αλληλεπίδραση (mock).

DoubleΤι ελέγχειΠαράδειγμα
MockΚλήση μεθόδου με σωστές παραμέτρουςuserRepository.save(user) κλήθηκε ακριβώς 1 φορά
StubΕπιστρεφόμενη τιμήrepository.findById(1) επιστρέφει User(id=1, name=“Test”)
FakeΛογική μέσω απλοποιημένης υλοποίησηςInMemoryMapUserRepository με HashMap αντί για DB
SpyΜερικό mocking πραγματικού αντικειμένουspy(repo).when(findById).thenReturn(user)

Mockito: παράδειγμα mocking σε Java/Kotlin

Το Mockito είναι το πιο δημοφιλές framework για mocking σε Java και Kotlin. Επιτρέπει τη δημιουργία mocks μέσω mock(), τη ρύθμιση επιστρεφόμενων τιμών μέσω when().thenReturn() και τον έλεγχο κλήσεων μέσω verify(). Οι σύγχρονες εκδόσεις του Mockito (5.x) υποστηρίζουν στατικά mocks (mockStatic) και απλοποιημένο συντακτικό μέσω BDDMockito (given-willReturn). Σημαντικός κανόνας: μην κάνετε mock σε ό,τι δεν είναι δικό σας — μην δημιουργείτε mocks για value objects και τυπικές βιβλιοθήκες.

kotlin
// Παράδειγμα unit test με Mockito σε Kotlin
class OrderServiceTest {

    @Mock
    private lateinit var paymentGateway: PaymentGateway

    @Mock
    private lateinit var userRepository: UserRepository

    private lateinit var orderService: OrderService

    @BeforeEach
    fun init() {
        MockitoAnnotations.openMocks(this)
        orderService = OrderService(paymentGateway, userRepository)
    }

    @Test
    fun processOrder_whenPaymentFails_shouldThrowException() {
        // given
        val user = User(id = 1, balance = 100.0)
        val order = Order(amount = 200.0)
        Mockito.`when`(paymentGateway.charge(any())).thenReturn(false)
        Mockito.`when`(userRepository.findById(1)).thenReturn(user)

        // when & then
        assert Throws<PaymentException> {
            orderService.processOrder(user.id, order)
        }

        // verify
        Mockito.verify(paymentGateway).charge(any())
    }
}

TDD: ανάπτυξη μέσω testing

TDD (Test-Driven Development) — μεθοδολογία κατά την οποία το test γράφεται πριν από την υλοποίηση του κώδικα. Ο κύκλος “Red-Green-Refactor”: γράψτε ένα test που αποτυγχάνει (Red), γράψτε τον ελάχιστο κώδικα για να περάσει το test (Green), βελτιώστε τον κώδικα χωρίς να αλλάξετε τη συμπεριφορά (Refactor). Το TDD εγγυάται ότι όλος ο κώδικας καλύπτεται από tests (coverage = 100% για τη γραμμένη λειτουργικότητα) και ότι ο κώδικας είναι testable — αν ο κώδικας είναι δύσκολο να ελεγχθεί, σημαίνει ότι η αρχιτεκτονική χρήζει βελτίωσης.

Πλεονεκτήματα του TDD

Σύμφωνα με τη διαχρονική μελέτη της IBM (2006-2026), οι ομάδες που χρησιμοποιούν TDD έχουν 40-80% λιγότερα σφάλματα σε παραγωγή σε σύγκριση με ομάδες που γράφουν tests μετά τον κώδικα. Το TDD βελτιώνει επίσης την αρχιτεκτονική: ο προγραμματιστής αναγκάζεται να σκεφτεί τον σχεδιασμό του API πριν από την υλοποίηση, οδηγώντας σε χαλαρή σύζευξη (loose coupling) και υψηλή συνοχή (high cohesion). Ένα πρόσθετο αποτέλεσμα είναι η τεκμηρίωση μέσω ζωντανού κώδικα: τα tests λειτουργούν ως προδιαγραφή συμπεριφοράς του module, η οποία είναι πάντα ενημερωμένη.

Πότε δεν είναι κατάλληλο το TDD;

Το TDD δεν είναι πάντα βέλτιστο. Τα UI components είναι δύσκολο να ελεγχθούν σε απομόνωση — γι' αυτά είναι πιο αποτελεσματικά τα snapshot tests ή το visual regression testing (Percy, Chromatic). Η δημιουργία πρωτοτύπων και η έρευνα (spike solutions) δεν απαιτούν tests. Ο legacy κώδικας χωρίς tests είναι δύσκολο να καλυφθεί μέσω TDD — εδώ χρειάζονται πρώτα characterization tests (tests που καταγράφουν την τρέχουσα συμπεριφορά πριν από το refactoring). Σε αυτές τις περιπτώσεις το TDD δεν ακυρώνεται εντελώς, αλλά προσαρμόζεται — γράφονται tests για την υπό αλλαγή λειτουργικότητα, όχι για ολόκληρο τον legacy κώδικα.

Unit testing σε mobile εφαρμογές

Η mobile ανάπτυξη έχει τις ιδιαιτερότητές της: η επιχειρηματική λογική συχνά αναμειγνύεται με τον UI κώδικα (Activity, ViewController, ViewModel), γεγονός που περιπλέκει το unit testing. Η βέλτιστη πρακτική είναι λεπτά Views, παχιά ViewModels: μεταφέρετε όλη τη λογική από τα UI components σε ξεχωριστές κλάσεις (UseCase, Repository, ViewModel) που είναι εύκολο να ελεγχθούν χωρίς emulator. Για Android και iOS υπάρχουν native frameworks unit testing που λειτουργούν σε JVM/Native χωρίς εκκίνηση συσκευής.

Unit tests σε Android (JUnit + Mockito/Robolectric)

Τα Android unit tests εκτελούνται σε τοπικό JVM χωρίς emulator, εξασφαλίζοντας ταχύτητα εκτέλεσης — ένα τυπικό test διαρκεί λιγότερο από 100ms. Το JUnit 5 είναι ο κύριος runner. Για ViewModel tests χρησιμοποιήστε το kotlinx-coroutines-test για τη δοκιμή coroutines και το Turbine για τη δοκιμή StateFlow. Το Robolectric επιτρέπει τη δοκιμή Android-εξαρτώμενων components (Context, Resources) χωρίς emulator, φορτώνοντας shadow classes. Για Compose tests χρησιμοποιήστε το Compose UI Test — αλλά αυτό είναι ήδη UI test, όχι unit.

Unit tests σε iOS (XCTest + Quick/Nimble)

Τα iOS unit tests γράφονται σε Swift με XCTest (ενσωματωμένο στο Xcode). Quick + Nimble — BDD frameworks για πιο ευανάγνωστα tests (describe/context/it). Για mocking χρησιμοποιήστε το Cuckoo (παραγωγή mocks) ή το SwiftyMocky. Το Swift υποστηρίζει protocols και dependency injection, διευκολύνοντας την αντικατάσταση εξαρτήσεων. Σημαντικό σημείο: τα iOS unit tests εκτελούνται στον simulator macOS, όχι σε πραγματική συσκευή. Tests που απαιτούν hardware λειτουργίες (κάμερα, Bluetooth) είναι integration tests.

Unit tests σε Flutter (flutter_test + Mockito)

Τα Flutter unit tests χρησιμοποιούν το πακέτο flutter_test και εκτελούνται σε Dart VM χωρίς emulator. Για mocking — το πακέτο mockito με code generator (build_runner). Τα widget tests (στο ίδιο πακέτο) δοκιμάζουν μεμονωμένα widgets, αλλά απαιτούν rendering και είναι πιο αργά — χρησιμοποιήστε τα μόνο για έλεγχο UI λογικής. Η καθαρή Dart λογική (models, repositories, blocs) δοκιμάζεται ως συνήθη Dart tests χωρίς εισαγωγή του flutter_test.

dart
// Παράδειγμα unit test σε Flutter με mockito
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';

@GenerateMocks([ApiClient])
import 'user_repository_test.mocks.dart';

void main() {
    late MockApiClient mockApi;
    late UserRepository repository;

    setUp(() {
        mockApi = MockApiClient();
        repository = UserRepository(mockApi);
    });

    test('fetchUser returns user when API succeeds', () async {
        // Προετοιμασία
        final expectedUser = User(id: 1, name: 'Test');
        when(mockApi.getUser(1))
            .thenAnswer((_) async => expectedUser);

        // Εκτέλεση
        final result = await repository.fetchUser(1);

        // Έλεγχος
        expect(result, expectedUser);
        verify(mockApi.getUser(1)).called(1);
    });
}

Βέλτιστες πρακτικές και τυπικά λάθη

Το αποτελεσματικό unit testing απαιτεί πειθαρχία. Ο βασικός κανόνας: δοκιμάζετε τη συμπεριφορά, όχι την υλοποίηση. Το test δεν πρέπει να γνωρίζει πώς υλοποιείται το module εσωτερικά (ποιες ιδιωτικές μέθοδοι καλούνται, με ποια σειρά). Αν το test είναι δεμένο με την υλοποίηση, σπάει σε κάθε refactoring και χάνει την αξία του. Το test ελέγχει το συμβόλαιο: με είσοδο X πρέπει να υπάρχει έξοδος Y. Εξαίρεση αποτελούν τα tests για αλγόριθμους κρίσιμης απόδοσης, όπου η σειρά κλήσεων είναι σημαντική.

  • Μία επαλήθευση ανά test — ένα assert ή μία ομάδα συσχετισμένων asserts ανά λογική επαλήθευση
  • Αποφύγετε την επανάληψη — χρησιμοποιήστε @BeforeEach / setUp για κοινή αρχικοποίηση, parameterized tests για διαφορετικά δεδομένα εισόδου
  • Μην ελέγχετε ιδιωτικές μεθόδους — δοκιμάστε μέσω του δημόσιου API. Αν μια ιδιωτική μέθοδος δεν καλύπτεται, σημαίνει ότι η λογική της δεν είναι ορατή από έξω
  • Καλύψτε ακραίες περιπτώσεις — κενές συλλογές, null/undefined, αρνητικοί αριθμοί, μέγιστες τιμές
  • Μη χρησιμοποιείτε Thread.sleep στα tests — κάνει τα tests αργά και ασταθή. Χρησιμοποιήστε test timeouts και coroutines

Ποιο coverage θεωρείται επαρκές;

Το 100% coverage είναι ανέφικτος και μη αναγκαίος στόχος. Σύμφωνα με το Google Testing Blog (2025), το βέλτιστο επίπεδο κάλυψης για unit tests είναι 70-80% των γραμμών κώδικα. Το 100% coverage συχνά επιτυγχάνεται μέσω δοκιμής getters, setters και constructors, που δεν προσφέρει αξία. Εστιάστε στην κρίσιμη επιχειρηματική λογική: σύνθετους υπολογισμούς, επικύρωση, διαχείριση σφαλμάτων, ακραίες περιπτώσεις. Χρησιμοποιήστε JaCoCo (Java), Coverage.py (Python), Istanbul (JS) για μέτρηση και ρυθμίστε ένα όριο στο CI — αποτυχία build σε coverage κάτω από 60%.

CI/CD και unit tests

Τα unit tests είναι το πρώτο στάδιο κάθε CI/CD pipeline. Εκτελούνται σε κάθε push στο repository, πριν από το build και το deploy. Ο μέσος χρόνος εκτέλεσης των unit tests σε ένα project δεν πρέπει να υπερβαίνει τα 5 λεπτά — αν είναι μεγαλύτερος, τα tests παύουν να είναι “γρήγορα” και οι προγραμματιστές σταματούν να τα εκτελούν τοπικά. Διαχωρίστε τα tests σε γρήγορα (unit) και αργά (integration) και εκτελέστε τα σε διαφορετικά στάδια του pipeline. Χρησιμοποιήστε παράλληλη εκτέλεση και fail-fast για επιτάχυνση.

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

Πώς διαφέρει το unit test από το integration test;

Το unit test ελέγχει ένα module σε απομόνωση, αντικαθιστώντας τις εξωτερικές εξαρτήσεις με mocks. Το integration test ελέγχει την αλληλεπίδραση μεταξύ πολλών πραγματικών components (DB, API, σύστημα αρχείων). Τα unit tests εκτελούνται σε χιλιοστά δευτερολέπτου, τα integration tests σε δευτερόλεπτα. Στην πυραμίδα testing, τα unit tests καταλαμβάνουν το 70%.

Ποιο framework να επιλέξω για unit tests;

Η επιλογή εξαρτάται από την πλατφόρμα: JUnit 5 για Java/Kotlin, XCTest για iOS/Swift, pytest για Python, Jest/Vitest για JavaScript/TypeScript, flutter_test για Flutter. Για mocking χρησιμοποιήστε Mockito (Java), Cuckoo (iOS), unittest.mock (Python) ή vitest.mock (JS). Όλα τα σύγχρονα frameworks υποστηρίζουν parameterized tests, ενσωματωμένες επαληθεύσεις και παράλληλη εκτέλεση.

Τι είναι οι αρχές F.I.R.S.T. στο testing;

Fast — το test εκτελείται σε χιλιοστά δευτερολέπτου. Isolated — δεν εξαρτάται από άλλα tests και εξωτερικά συστήματα. Repeatable — δίνει το ίδιο αποτέλεσμα σε οποιοδήποτε μηχάνημα. Self-validating — ελέγχει αυτόματα το αποτέλεσμα. Timely — γράφεται πριν ή ταυτόχρονα με τον κώδικα. Η παραβίαση έστω και μίας αρχής μειώνει την αποτελεσματικότητα του testing.

Χρειάζεται να γράφουμε unit tests για ViewModel σε Android/iOS;

Ναι, απαραίτητα. Το ViewModel περιέχει επιχειρηματική λογική — επεξεργασία συμβάντων, μετασχηματισμό δεδομένων, διαχείριση κατάστασης. Σε Android χρησιμοποιήστε kotlinx-coroutines-test για coroutines και Turbine για δοκιμή StateFlow. Σε iOS δοκιμάστε Combine Publishers ή async/await στο ViewModel. Τα ViewModel tests είναι καθαρά unit tests, που εκτελούνται σε JVM/macOS χωρίς emulator.

Πώς να δοκιμάσω κώδικα με δικτυακά αιτήματα;

Τα δικτυακά αιτήματα σε unit tests δεν εκτελούνται — αντικαθίστανται με mocks του HTTP client. Σε Android χρησιμοποιήστε MockWebServer (OkHttp) — εκκινεί ένα τοπικό HTTP server, το οποίο είναι προτιμότερο από mocks, καθώς αναπαράγει την πραγματική δικτυακή αλληλεπίδραση. Το MockWebServer παρέχει απομόνωση χωρίς απώλεια ρεαλισμού. Για iOS — OHHTTPStubs ή URLProtocol για υποκλοπή και αντικατάσταση απαντήσεων.

Σύνοψη

  • Unit testing — έλεγχος μεμονωμένων modules σε απομόνωση από εξωτερικές εξαρτήσεις με γρήγορη ανατροφοδότηση
  • Δομή AAA — Arrange (προετοιμασία), Act (ενέργεια), Assert (επαλήθευση) — το τυπικό μοτίβο test
  • Mocks και stubs — test doubles για απομόνωση: τα mocks ελέγχουν κλήσεις, τα stubs επιστρέφουν τιμές
  • TDD — ανάπτυξη μέσω testing (Red-Green-Refactor) μειώνει τα σφάλματα κατά 40-80%
  • Αρχές FIRST — Fast, Isolated, Repeatable, Self-validating, Timely — η βάση ενός ποιοτικού test
  • Εργαλεία πλατφόρμας — JUnit 5 (Android), XCTest (iOS), flutter_test (Flutter), Jest (React Native)
  • Κάλυψη 70-80% — βέλτιστο επίπεδο για κρίσιμη επιχειρηματική λογική, getters και setters δεν απαιτούν tests

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

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

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

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