Το unit testing είναι μια μέθοδος ελέγχου λογισμικού κατά την οποία ελέγχεται η ορθότητα λειτουργίας μεμονωμένων modules ή συναρτήσεων κώδικα σε απομόνωση από το υπόλοιπο σύστημα. Σύμφωνα με τα δεδομένα του Martin Fowler, 2026, τα unit tests αποτελούν το θεμέλιο του CI/CD και του refactoring, παρέχοντας γρήγορη ανατροφοδότηση σχετικά με τη λειτουργικότητα του κώδικα. Το modular testing βοηθά στον εντοπισμό σφαλμάτων στα πρώτα στάδια ανάπτυξης, μειώνοντας το κόστος διόρθωσής τους δεκάδες φορές.
Βασικά σημεία
Το unit testing είναι η διαδικασία ελέγχου μεμονωμένων μονάδων (unit) του πηγαίου κώδικα — συναρτήσεων, μεθόδων, κλάσεων — σε απομόνωση από το υπόλοιπο πρόγραμμα. Κάθε test εκτελεί ένα συγκεκριμένο σενάριο χρήσης του module και ελέγχει αν το αποτέλεσμα αντιστοιχεί στο αναμενόμενο. Τα unit tests γράφονται στην ίδια γλώσσα προγραμματισμού με τον βασικό κώδικα και εκτελούνται αυτόματα στο περιβάλλον ανάπτυξης ή στο CI/CD pipeline. Σε αντίθεση με τα integration tests, τα unit tests δεν αλληλεπιδρούν με πραγματικές βάσεις δεδομένων, σύστημα αρχείων ή δικτυακές υπηρεσίες.
Ο κύριος στόχος είναι η γρήγορη ανατροφοδότηση σχετικά με την ορθότητα του κώδικα μετά από αλλαγές. Αν ένας προγραμματιστής κάνει refactor σε μια μέθοδο, το σύνολο των unit tests επιβεβαιώνει ότι η συμπεριφορά δεν έχει σπάσει. Σύμφωνα με το Google Testing Blog (2025), τα projects με κάλυψη unit tests άνω του 60% έχουν 2,5 φορές λιγότερα περιστατικά σε παραγωγή. Πρόσθετα πλεονεκτήματα: τεκμηρίωση κώδικα (τα tests δείχνουν πώς να χρησιμοποιείται το API), απλοποίηση του refactoring (η υλοποίηση μπορεί να αλλάξει διατηρώντας τη συμπεριφορά) και γρήγορη διάγνωση regressions.
Δεν είναι κάθε αυτόματο test unit test. Κριτήρια: ελέγχεται ένα module (κλάση ή συνάρτηση), οι εξωτερικές εξαρτήσεις αντικαθίστανται με mocks ή stubs, το test εκτελείται σε χιλιοστά του δευτερολέπτου, δεν απαιτεί εκκίνηση server ή βάσης δεδομένων. Ένα test που προσπελαύνει πραγματική βάση δεδομένων είναι integration test. Ένα test που ανοίγει browser είναι E2E test. Η κατανόηση των ορίων μεταξύ των τύπων tests είναι σημαντική για τη σωστή κατανομή προσπαθειών στην πυραμίδα testing.
Τα ποιοτικά unit tests ακολουθούν τις αρχές FIRST, όπως διατυπώθηκαν από τον Robert C. Martin. Κάθε test πρέπει να είναι Fast (γρήγορο — χιλιοστά δευτερολέπτου), Isolated (απομονωμένο — δεν εξαρτάται από άλλα tests), Repeatable (αναπαραγώγιμο — ίδιο αποτέλεσμα σε οποιοδήποτε μηχάνημα), Self-validating (αυτοεπαληθευόμενο — αποτέλεσμα “passed” ή “failed”, χωρίς χειροκίνητο έλεγχο) και Timely (έγκαιρο — γραμμένο πριν ή ταυτόχρονα με τον κώδικα). Η παραβίαση οποιασδήποτε αρχής μειώνει την αξία του test.
Το τυπικό μοτίβο για τη σύνταξη unit tests. Arrange — προετοιμασία δεδομένων και εξαρτήσεων: δημιουργία αντικειμένων, ρύθμιση mocks, καθορισμός παραμέτρων εισόδου. Act — εκτέλεση της υπό δοκιμή ενέργειας: κλήση μεθόδου ή συνάρτησης. Assert — έλεγχος αποτελέσματος: σύγκριση πραγματικής τιμής με την αναμενόμενη. Ο διαχωρισμός σε τρία blocks κάνει το test αναγνώσιμο και κατανοητό. Αν το block Assert απαιτεί σύνθετη λογική, το test πιθανώς ελέγχει πάρα πολλά πράγματα ταυτόχρονα.
// Παράδειγμα 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)
}
}
Το όνομα του test πρέπει να περιγράφει τι ελέγχεται και ποιο αποτέλεσμα αναμένεται. Μορφή: [methodName]_[scenario]_[expectedResult]. Παράδειγμα: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Ένα καλό όνομα test αντικαθιστά το σχόλιο και σε περίπτωση αποτυχίας υποδεικνύει αμέσως ποια λειτουργικότητα έχει σπάσει. Αποφύγετε ονόματα όπως test1, checkSomething ή verify — δεν μεταφέρουν πληροφορία και δυσχεραίνουν τη διάγνωση.
Για την απομόνωση του υπό δοκιμή 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 είναι το πιο δημοφιλές framework για mocking σε Java και Kotlin. Επιτρέπει τη δημιουργία mocks μέσω mock(), τη ρύθμιση επιστρεφόμενων τιμών μέσω when().thenReturn() και τον έλεγχο κλήσεων μέσω verify(). Οι σύγχρονες εκδόσεις του Mockito (5.x) υποστηρίζουν στατικά mocks (mockStatic) και απλοποιημένο συντακτικό μέσω BDDMockito (given-willReturn). Σημαντικός κανόνας: μην κάνετε mock σε ό,τι δεν είναι δικό σας — μην δημιουργείτε mocks για value objects και τυπικές βιβλιοθήκες.
// Παράδειγμα 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 (Test-Driven Development) — μεθοδολογία κατά την οποία το test γράφεται πριν από την υλοποίηση του κώδικα. Ο κύκλος “Red-Green-Refactor”: γράψτε ένα test που αποτυγχάνει (Red), γράψτε τον ελάχιστο κώδικα για να περάσει το test (Green), βελτιώστε τον κώδικα χωρίς να αλλάξετε τη συμπεριφορά (Refactor). Το TDD εγγυάται ότι όλος ο κώδικας καλύπτεται από tests (coverage = 100% για τη γραμμένη λειτουργικότητα) και ότι ο κώδικας είναι testable — αν ο κώδικας είναι δύσκολο να ελεγχθεί, σημαίνει ότι η αρχιτεκτονική χρήζει βελτίωσης.
Σύμφωνα με τη διαχρονική μελέτη της IBM (2006-2026), οι ομάδες που χρησιμοποιούν TDD έχουν 40-80% λιγότερα σφάλματα σε παραγωγή σε σύγκριση με ομάδες που γράφουν tests μετά τον κώδικα. Το TDD βελτιώνει επίσης την αρχιτεκτονική: ο προγραμματιστής αναγκάζεται να σκεφτεί τον σχεδιασμό του API πριν από την υλοποίηση, οδηγώντας σε χαλαρή σύζευξη (loose coupling) και υψηλή συνοχή (high cohesion). Ένα πρόσθετο αποτέλεσμα είναι η τεκμηρίωση μέσω ζωντανού κώδικα: τα tests λειτουργούν ως προδιαγραφή συμπεριφοράς του module, η οποία είναι πάντα ενημερωμένη.
Το TDD δεν είναι πάντα βέλτιστο. Τα UI components είναι δύσκολο να ελεγχθούν σε απομόνωση — γι' αυτά είναι πιο αποτελεσματικά τα snapshot tests ή το visual regression testing (Percy, Chromatic). Η δημιουργία πρωτοτύπων και η έρευνα (spike solutions) δεν απαιτούν tests. Ο legacy κώδικας χωρίς tests είναι δύσκολο να καλυφθεί μέσω TDD — εδώ χρειάζονται πρώτα characterization tests (tests που καταγράφουν την τρέχουσα συμπεριφορά πριν από το refactoring). Σε αυτές τις περιπτώσεις το TDD δεν ακυρώνεται εντελώς, αλλά προσαρμόζεται — γράφονται tests για την υπό αλλαγή λειτουργικότητα, όχι για ολόκληρο τον legacy κώδικα.
Η mobile ανάπτυξη έχει τις ιδιαιτερότητές της: η επιχειρηματική λογική συχνά αναμειγνύεται με τον UI κώδικα (Activity, ViewController, ViewModel), γεγονός που περιπλέκει το unit testing. Η βέλτιστη πρακτική είναι λεπτά Views, παχιά ViewModels: μεταφέρετε όλη τη λογική από τα UI components σε ξεχωριστές κλάσεις (UseCase, Repository, ViewModel) που είναι εύκολο να ελεγχθούν χωρίς emulator. Για Android και iOS υπάρχουν native frameworks unit testing που λειτουργούν σε JVM/Native χωρίς εκκίνηση συσκευής.
Τα 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.
Τα 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.
Τα 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.
// Παράδειγμα 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 για αλγόριθμους κρίσιμης απόδοσης, όπου η σειρά κλήσεων είναι σημαντική.
Το 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%.
Τα unit tests είναι το πρώτο στάδιο κάθε CI/CD pipeline. Εκτελούνται σε κάθε push στο repository, πριν από το build και το deploy. Ο μέσος χρόνος εκτέλεσης των unit tests σε ένα project δεν πρέπει να υπερβαίνει τα 5 λεπτά — αν είναι μεγαλύτερος, τα tests παύουν να είναι “γρήγορα” και οι προγραμματιστές σταματούν να τα εκτελούν τοπικά. Διαχωρίστε τα tests σε γρήγορα (unit) και αργά (integration) και εκτελέστε τα σε διαφορετικά στάδια του pipeline. Χρησιμοποιήστε παράλληλη εκτέλεση και fail-fast για επιτάχυνση.
Συχνές ερωτήσεις
Το unit test ελέγχει ένα module σε απομόνωση, αντικαθιστώντας τις εξωτερικές εξαρτήσεις με mocks. Το integration test ελέγχει την αλληλεπίδραση μεταξύ πολλών πραγματικών components (DB, API, σύστημα αρχείων). Τα unit tests εκτελούνται σε χιλιοστά δευτερολέπτου, τα integration tests σε δευτερόλεπτα. Στην πυραμίδα testing, τα unit tests καταλαμβάνουν το 70%.
Η επιλογή εξαρτάται από την πλατφόρμα: 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, ενσωματωμένες επαληθεύσεις και παράλληλη εκτέλεση.
Fast — το test εκτελείται σε χιλιοστά δευτερολέπτου. Isolated — δεν εξαρτάται από άλλα tests και εξωτερικά συστήματα. Repeatable — δίνει το ίδιο αποτέλεσμα σε οποιοδήποτε μηχάνημα. Self-validating — ελέγχει αυτόματα το αποτέλεσμα. Timely — γράφεται πριν ή ταυτόχρονα με τον κώδικα. Η παραβίαση έστω και μίας αρχής μειώνει την αποτελεσματικότητα του testing.
Ναι, απαραίτητα. Το 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 για υποκλοπή και αντικατάσταση απαντήσεων.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης