Unit тестването е метод за проверка на софтуер, при който се тества коректността на работата на отделни модули или функции на кода в изолация от останалата система. Според Martin Fowler, 2026, юнит тестовете са основата на CI/CD и рефакторинга, осигурявайки бърза обратна връзка за работоспособността на кода. Модулното тестване помага да се откриват грешки в ранните етапи на разработката, намалявайки цената на тяхното коригиране десетки пъти.
Най-важното
Unit тестването е процес на проверка на отделни единици (unit) от изходния код — функции, методи, класове — в изолация от останалата част на програмата. Всеки тест изпълнява конкретен сценарий на използване на модула и проверява дали резултатът съответства на очаквания. Unit тестовете се пишат на същия език за програмиране като основния код и се изпълняват автоматично в средата за разработка или в CI/CD пайплайна. За разлика от интеграционните тестове, юнит тестовете не взаимодействат с реални бази данни, файлова система или мрежови услуги.
Основната цел е бърза обратна връзка за коректността на кода след промени. Ако разработчикът рефакторира метод, наборът от unit тестове потвърждава, че поведението не се е счупило. Според Google Testing Blog (2025), проекти с покритие над 60% с юнит тестове имат 2.5 пъти по-малко инциденти в production. Допълнителни предимства: документация на кода (тестовете показват как да се използва API), улеснен рефакторинг (можете да променяте реализацията, запазвайки поведението) и бърза диагностика на регресии.
Не всеки автоматичен тест е unit тест. Критерии: тества се един модул (клас или функция), външните зависимости са заменени с мокове или стабове, тестът се изпълнява за милисекунди и не изисква стартиране на сървър или база данни. Тест, който се обръща към реална база данни, е интеграционен тест. Тест, който отваря браузър, е E2E тест. Разбирането на границите между видовете тестове е важно за правилното разпределение на усилията в пирамидата на тестването.
Качествените unit тестове следват принципите FIRST, формулирани от Robert C. Martin. Всеки тест трябва да бъде Fast (бърз — милисекунди), Isolated (изолиран — не зависи от други тестове), Repeatable (възпроизводим — еднакъв резултат на всяка машина), Self-validating (самопроверяващ се — резултат „passed" или „failed", без ръчна проверка) и Timely (навременен — написан преди или едновременно с кода). Нарушаването на който и да е принцип намалява стойността на теста.
Стандартен шаблон за писане на unit тестове. Arrange — подготовка на данните и зависимостите: създаване на обекти, настройка на мокове, задаване на входни параметри. Act — изпълнение на тестваното действие: извикване на метод или функция. Assert — проверка на резултата: сравнение на фактическата стойност с очакваната. Разделянето на три блока прави теста четим и разбираем. Ако блокът Assert изисква сложна логика, тестът вероятно проверява твърде много наведнъж.
// Пример за unit тест по шаблона 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)
}
}
Името на теста трябва да описва какво се проверява и какъв резултат се очаква. Формат: [methodName]_[scenario]_[expectedResult]. Пример: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Доброто име на теста замества коментара и при падане веднага посочва коя функционалност е нарушена. Избягвайте имена като test1, checkSomething или verify — те не носят информация и затрудняват диагностиката.
За изолация на тествания модул от външни зависимости се използват тестови дубльори (test doubles). Основни видове: мокове (mocks) — проверяват, че даден метод е извикан с правилните параметри; стабове (stubs) — връщат зададени стойности при извикване на метод; фейкове (fakes) — опростена реализация на реален компонент (например InMemoryUserRepository вместо UserRepository, който работи с БД). Изборът зависи от това какво трябва да се провери: състояние (стаб) или взаимодействие (мок).
| Дубльор | Какво проверява | Пример |
|---|---|---|
| Мок | Извикване на метод с правилни параметри | userRepository.save(user) е извикан точно 1 път |
| Стаб | Връщаната стойност | repository.findById(1) връща User(id=1, name="Test") |
| Фейк | Логика чрез опростена реализация | InMemoryMapUserRepository с HashMap вместо БД |
| Шпионин | Частичен мокинг на реален обект | spy(repo).when(findById).thenReturn(user) |
Mockito е най-популярната рамка за мокинг в Java и Kotlin. Позволява създаване на мокове чрез mock(), настройване на връщаните стойности чрез when().thenReturn() и проверка на извикванията чрез verify(). Съвременните версии на Mockito (5.x) поддържат статични мокове (mockStatic) и опростен синтаксис чрез BDDMockito (given-willReturn). Важно правило: не моквайте това, което не е ваше — не създавайте мокове за value-обекти и стандартни библиотеки.
// Пример за unit тест с 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) е методология, при която тестът се пише преди реализацията на кода. Цикълът „Red-Green-Refactor": напишете тест, който се проваля (Red), напишете минимален код за преминаване на теста (Green), подобрете кода без промяна на поведението (Refactor). TDD гарантира, че целият код е покрит с тестове (coverage = 100% за написаната функционалност) и че кодът е тестваем — ако кодът е труден за тестване, значи архитектурата изисква подобрение.
Според изследването IBM (2006-2026, лонгитюдно проучване), екипите, използващи TDD, допускат с 40-80% по-малко дефекти в production в сравнение с екипите, които пишат тестове след кода. TDD също подобрява архитектурата: разработчикът е принуден да мисли за дизайна на API преди реализацията, което води до слаба свързаност (loose coupling) и висока кожерентност (high cohesion). Допълнителен ефект — документация чрез жив код: тестовете служат като спецификация на поведението на модула, която винаги е актуална.
TDD не винаги е оптимален. UI компонентите са трудни за тестване в изолация — за тях по-ефективни са snapshot тестовете или визуалното регресионно тестване (Percy, Chromatic). Прототипирането и изследванията (spike solutions) не изискват тестове. Legacy кодът без тестове трудно се покрива чрез TDD — тук първо са необходими characterization tests (тестове, фиксиращи текущото поведение преди рефакторинг). В тези случаи TDD не се отменя напълно, а се адаптира — пишат се тестове за променящата се функционалност, а не за целия legacy код.
Мобилната разработка има своя специфика: бизнес логиката често е смесена с UI кода (Activity, ViewController, ViewModel), което усложнява unit тестването. Най-добрата практика — тънки View, дебели ViewModel: изнасяйте цялата логика от UI компонентите в отделни класове (UseCase, Repository, ViewModel), които лесно се тестват без емулатор. За Android и iOS съществуват нативни рамки за unit тестване, работещи на JVM/Native без стартиране на устройството.
Android unit тестовете се изпълняват на локална JVM без емулатор, което осигурява скорост на изпълнение — типичният тест отнема под < 100ms. JUnit 5 е основният runner. За ViewModel тестове използвайте kotlinx-coroutines-test за тестване на корутини и Turbine за тестване на StateFlow. Robolectric позволява тестване на зависими от Android компоненти (Context, Resources) без емулатор, зареждайки shadow класове. За Compose тестове използвайте Compose UI Test — но това вече са UI тестове, не unit.
iOS unit тестовете се пишат на Swift с XCTest (вграден в Xcode). Quick + Nimble — BDD рамки за по-четими тестове (describe/context/it). За мокинг използвайте Cuckoo (генериране на мокове) или SwiftyMocky. Swift поддържа протоколи и dependency injection, което улеснява замяната на зависимости. Ключов момент: iOS unit тестовете се изпълняват на macOS симулатор, а не на реално устройство. Тестове, изискващи хардуерни функции (камера, Bluetooth), са интеграционни тестове.
Flutter unit тестовете използват пакета flutter_test и се изпълняват на Dart VM без емулатор. За мокинг — пакетът mockito с генератор на код (build_runner). Widget тестовете (в същия пакет) тестват отделни уиджети, но изискват рендериране и работят по-бавно — използвайте ги само за проверка на UI логиката. Чистата Dart логика (модели, репозитории, блобове) се тества като обикновени Dart тестове без импортиране на flutter_test.
// Пример за unit тест на 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 {
// Arrange
final expectedUser = User(id: 1, name: 'Test');
when(mockApi.getUser(1))
.thenAnswer((_) async => expectedUser);
// Act
final result = await repository.fetchUser(1);
// Assert
expect(result, expectedUser);
verify(mockApi.getUser(1)).called(1);
});
}
Ефективното unit тестване изисква дисциплина. Главното правило: тествайте поведението, а не реализацията. Тестът не трябва да знае как модулът е реализиран вътрешно (какви частни методи се извикват, в какъв ред). Ако тестът е обвързан с реализацията, той се чупи при всеки рефакторинг и губи стойност. Тестът проверява договора: при вход X трябва да има изход Y. Изключение — тестове за алгоритми с критична производителност, където последователността на извикванията е важна.
100% coverage е недостижима и ненужна цел. Според Google Testing Blog (2025), оптималното ниво на покритие за unit тестове е 70-80% от редовете код. 100% coverage често се постига чрез тестване на гетъри, сетъри и конструктори, което не носи стойност. Фокусирайте се върху критичната бизнес логика: сложни изчисления, валидация, обработка на грешки, edge cases. Използвайте JaCoCo (Java), Coverage.py (Python), Istanbul (JS) за измерване и задайте праг в CI — падане на сборката при coverage под 60%.
Unit тестовете са първият етап на всеки CI/CD пайплайн. Те се изпълняват при всеки push в хранилището, преди сборката и деплоя. Средното време за изпълнение на unit тестовете в проекта не трябва да надвишава 5 минути — ако е по-дълго, тестовете престават да бъдат „бързи" и разработчиците спират да ги пускат локално. Разделяйте тестовете на бързи (unit) и бавни (интеграционни) и ги изпълнявайте в различни етапи на пайплайна. Използвайте parallel execution и fail-fast за ускоряване.
Често задавани въпроси
Unit тестът проверява един модул в изолация, заменяйки външните зависимости с мокове. Интеграционният тест проверява взаимодействието между няколко реални компонента (БД, API, файлова система). Unit тестовете се изпълняват за милисекунди, интеграционните — за секунди. В пирамидата на тестването unit тестовете заемат 70%.
Изборът зависи от платформата: JUnit 5 за Java/Kotlin, XCTest за iOS/Swift, pytest за Python, Jest/Vitest за JavaScript/TypeScript, flutter_test за Flutter. За мокинг използвайте Mockito (Java), Cuckoo (iOS), unittest.mock (Python) или vitest.mock (JS). Всички съвременни рамки поддържат параметризирани тестове, вградени assertions и паралелно изпълнение.
Fast — тестът се изпълнява за милисекунди. Isolated — не зависи от други тестове и външни системи. Repeatable — дава еднакъв резултат на всяка машина. Self-validating — проверява резултата автоматично. Timely — написан преди или синхронно с кода. Нарушаването на поне един принцип намалява ефективността на тестването.
Да, задължително. ViewModel съдържа бизнес логиката — обработка на събития, трансформация на данни, управление на състоянието. В Android използвайте kotlinx-coroutines-test за корутини и Turbine за тестване на StateFlow. В iOS тествайте Combine Publishers или async/await в ViewModel. ViewModel тестовете са чисти unit тестове, работещи на JVM/macOS без емулатор.
Мрежовите заявки в unit тестовете не се изпълняват — те се заменят с мокове на HTTP клиента. В Android използвайте MockWebServer (OkHttp) — той стартира локален HTTP сървър, което е за предпочитане пред моковете, тъй като възпроизвежда реално мрежово взаимодействие. MockWebServer осигурява изолация без загуба на реалистичност. За iOS — OHHTTPStubs или URLProtocol за прихващане и подмяна на отговори.
Изводи
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също