Ang pagsusuri ng unit ay isang paraan ng pag-verify ng software kung saan sinusuri ang kawastuhan ng mga indibidwal na module o function ng code sa paghihiwalay mula sa natitirang sistema. Ayon kay Martin Fowler, 2026, ang mga unit test ay pundasyon ng CI/CD at refactoring, na nagbibigay ng mabilis na feedback tungkol sa paggana ng code. Modular na pagsusuri ay tumutulong na makita ang mga error sa maagang yugto ng pag-develop, na binabawasan ang gastos ng pag-aayos ng mga ito nang sampu-sampung beses.
Mga Pangunahing Punto
Ang pagsusuri ng unit ay proseso ng pagsuri ng mga indibidwal na yunit ng source code — mga function, method, class — sa paghihiwalay mula sa natitirang programa. Bawat test ay nagpapatakbo ng isang partikular na senaryo ng paggamit ng module at sinusuri kung ang resulta ay tumutugma sa inaasahan. Ang mga unit test ay isinusulat sa parehong programming language gaya ng pangunahing code at awtomatikong isinasagawa sa development environment o sa CI/CD pipeline. Hindi tulad ng integration tests, ang mga unit test ay hindi nakikipag-ugnayan sa mga totoong database, file system, o network services.
Ang pangunahing layunin ay mabilis na feedback tungkol sa kawastuhan ng code pagkatapos ng mga pagbabago. Kung ang developer ay nag-refactor ng isang method, kinukumpirma ng set ng unit test na hindi nasira ang behavior. Ayon sa Google Testing Blog (2025), ang mga proyektong may coverage ng unit test na higit sa 60% ay may 2.5 beses na mas kaunting production incidents. Mga karagdagang benepisyo: dokumentasyon ng code (ipinapakita ng test kung paano gamitin ang API), pagpapasimple ng refactoring (maaaring baguhin ang implementasyon habang pinapanatili ang behavior), at mabilis na diagnosis ng regression.
Hindi lahat ng awtomatikong test ay unit test. Mga pamantayan: isang module (class o function) ang sinusuri, ang mga external dependency ay pinalitan ng mga mock o stub, ang test ay isinasagawa sa millisecond, hindi nangangailangan ng pagpapatakbo ng server o database. Ang test na uma-access sa totoong database ay integration test. Ang test na nagbubukas ng browser ay E2E test. Ang pag-unawa sa mga hangganan sa pagitan ng mga uri ng test ay mahalaga para sa tamang pamamahagi ng pagsisikap sa piramide ng pagsusuri.
Ang mga de-kalidad na unit test ay sumusunod sa mga prinsipyo ng FIRST na binuo ni Robert C. Martin. Bawat test ay dapat na Fast (mabilis — millisecond), Isolated (nakahiwalay — hindi nakadepende sa ibang test), Repeatable (paulit-ulit — parehong resulta sa anumang makina), Self-validating (nagpapatunay sa sarili — resulta ay “passed” o “failed”, walang manual na pagsusuri), at Timely(napapanahon — isinulat bago o kasabay ng code). Ang paglabag sa alinmang prinsipyo ay nagbabawas ng halaga ng test.
Karaniwang template para sa pagsulat ng mga unit test. Arrange — paghahanda ng data at dependency: paggawa ng object, pag-configure ng mock, pagtatakda ng input parameter. Act — pagsasagawa ng sinusuri na aksyon: pagtawag ng method o function. Assert — pagsusuri ng resulta: paghahambing ng aktwal na halaga sa inaasahan. Ang paghahati sa tatlong block ay ginagawang nababasa at naiintindihan ang test. Kung ang Assert block ay nangangailangan ng komplikadong logic, malamang na masyadong marami ang sinusuri ng test nang sabay-sabay.
// Halimbawa ng unit test ayon sa AAA template sa Kotlin na may JUnit 5
class CalculatorTest {
private lateinit var calculator: Calculator
@BeforeEach
fun setUp() {
// ARRANGE — ginagawa natin ang sinusuring object
calculator = Calculator()
}
@Test
fun addition_shouldReturnCorrectSum() {
// ACT — ginagawa natin ang aksyon
val result = calculator.add(2, 3)
// ASSERT — sinusuri natin ang resulta
Assertions.assertEquals(5, result)
}
}Ang pangalan ng test ay dapat naglalarawan kung ano ang sinusuri at kung anong resulta ang inaasahan. Format: [methodName]_[scenario]_[expectedResult]. Halimbawa: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Ang magandang pangalan ng test ay pumapalit sa komento at sa pagkabigo ay agad na nagpapakita kung aling functionality ang nasira. Iwasan ang mga pangalan tulad ng test1, checkSomething, o verify — hindi sila nagbibigay ng impormasyon at nagpapahirap sa diagnosis.
Para sa paghihiwalay ng sinusuring module mula sa mga external dependency, ginagamit ang mga test double. Mga pangunahing uri: mock — sinusuri kung ang isang partikular na method ay tinawag gamit ang tamang parameter; stub — nagbabalik ng itinakdang halaga kapag tinawag ang method; fake — pinasimpleng implementasyon ng isang totoong component (hal. InMemoryUserRepository sa halip na UserRepository na gumagana sa database). Ang pagpili ay depende sa kung ano ang kailangang suriin: estado (stub) o interaksyon (mock).
| Double | Ano ang sinusuri | Halimbawa |
|---|---|---|
| Mock | Pagtawag ng method na may tamang parameter | userRepository.save(user) ay tinawag nang eksaktong 1 beses |
| Stub | Ibinabalik na halaga | repository.findById(1) ay nagbabalik ng User(id=1, name=“Test”) |
| Fake | Logic sa pamamagitan ng pinasimpleng implementasyon | InMemoryMapUserRepository na may HashMap sa halip ng database |
| Spy | Bahagyang mocking ng totoong object | spy(repo).when(findById).thenReturn(user) |
Ang Mockito ay ang pinakasikat na framework para sa mocking sa Java at Kotlin. Nagbibigay-daan sa paggawa ng mock sa pamamagitan ng mock(), pag-configure ng mga ibinabalik na halaga sa pamamagitan ng when().thenReturn(), at pagsusuri ng mga tawag sa pamamagitan ng verify(). Ang mga modernong bersyon ng Mockito (5.x) ay sumusuporta sa static mock (mockStatic) at pinasimpleng syntax sa pamamagitan ng BDDMockito (given-willReturn). Mahalagang patakaran: huwag i-mock ang hindi sa iyo — huwag gumawa ng mock para sa value object at standard library.
// Halimbawa ng unit test na may Mockito sa 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) — metodolohiya kung saan ang test ay isinusulat bago ang implementasyon ng code. Ikot ng “Red-Green-Refactor”: sumulat ng test na nabigo (Red), sumulat ng minimal na code para pumasa ang test (Green), pagbutihin ang code nang hindi binabago ang behavior (Refactor). Ginagarantiya ng TDD na ang lahat ng code ay sakop ng test (coverage = 100% para sa isinulat na functionality) at na ang code ay natetest — kung ang code ay mahirap i-test, nangangahulugan ito na ang arkitektura ay nangangailangan ng pagpapabuti.
Ayon sa pananaliksik ng IBM (2006-2026, longitudinal study), ang mga team na gumagamit ng TDD ay may 40-80% mas kaunting defect sa produksyon kumpara sa mga team na sumusulat ng test pagkatapos ng code. Pinapabuti rin ng TDD ang arkitektura: ang developer ay kailangang mag-isip tungkol sa disenyo ng API bago ang implementasyon, na humahantong sa maluwag na coupling (loose coupling) at mataas na cohesion (high cohesion). Karagdagang epekto — dokumentasyon na may buhay na code: ang mga test ay nagsisilbing specification ng behavior ng module na laging napapanahon.
Ang TDD ay hindi laging optimal. Ang mga UI component ay mahirap i-test sa paghihiwalay — para sa kanila, mas epektibo ang snapshot test o visual regression testing (Percy, Chromatic). Ang prototyping at research (spike solutions) ay hindi nangangailangan ng test. Ang legacy code na walang test ay mahirap sakop sa pamamagitan ng TDD — dito kailangan muna ang characterization tests (mga test na nagre-record ng kasalukuyang behavior bago ang refactoring). Sa mga kasong ito, ang TDD ay hindi ganap na inaalis, kundi inaayos — ang test ay isinusulat para sa binagong functionality, hindi para sa buong legacy code.
Ang mobile development ay may kanya-kanyang spec: ang business logic ay madalas na halo-halong may UI code (Activity, ViewController, ViewModel), na nagpapahirap sa unit testing. Pinakamahusay na kasanayan — manipis na view, makapal na ViewModel: ilipat ang lahat ng logic mula sa UI component patungo sa hiwalay na klase (UseCase, Repository, ViewModel) na madaling i-test nang walang emulator. Para sa Android at iOS, may mga native na unit testing framework na tumatakbo sa JVM/Native nang hindi pinapatakbo ang device.
Ang Android unit test ay isinasagawa sa lokal na JVM nang walang emulator, na tinitiyak ang bilis ng pagpapatupad — isang tipikal na test ay tumatagal ng mas mababa sa 100ms. Ang JUnit 5 ang pangunahing runner. Para sa ViewModel test, gamitin ang kotlinx-coroutines-test para sa pag-test ng coroutine at Turbine para sa pag-test ng StateFlow. Ang Robolectric ay nagbibigay-daan sa pag-test ng Android-dependent component (Context, Resources) nang walang emulator sa pamamagitan ng pag-load ng shadow class. Para sa Compose test, gamitin ang Compose UI Test — ngunit ito ay UI test na, hindi unit.
Ang iOS unit test ay isinusulat sa Swift gamit ang XCTest (integrated sa Xcode). Quick + Nimble — BDD frameworks para sa mas nababasang test (describe/context/it). Para sa mocking, gamitin ang Cuckoo (generation ng mock) o SwiftyMocky. Sinusuportahan ng Swift ang protocol at dependency injection, na nagpapadali sa pagpapalit ng dependency. Mahalagang punto: ang iOS unit test ay isinasagawa sa macOS simulator, hindi sa totoong device. Ang test na nangangailangan ng hardware functions (camera, Bluetooth) ay integration test.
Ang Flutter unit test ay gumagamit ng flutter_test package at isinasagawa sa Dart VM nang walang emulator. Para sa mocking — ang mockito package na may code generator (build_runner). Ang widget test (sa parehong package) ay sumusuri ng indibidwal na widget, ngunit nangangailangan ng rendering at mas mabagal — gamitin lamang para sa pagsusuri ng UI logic. Ang purong Dart logic (model, repository, blobs) ay nate-test tulad ng ordinaryong Dart test nang hindi ini-import ang flutter_test.
// Halimbawa ng unit test sa Flutter na may 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);
});
}Ang epektibong unit testing ay nangangailangan ng disiplina. Pangunahing patakaran: subukan ang behavior, hindi ang implementasyon. Hindi dapat malaman ng test kung paano na-implement ang module sa loob (anumang pribadong method ang tinatawag, sa anong pagkakasunud-sunod). Kung ang test ay nakatali sa implementasyon, nasisira ito sa bawat refactoring at nawawalan ng halaga. Sinusuri ng test ang kontrata: sa input X dapat may output Y. Exception — mga test para sa algorithm na may kritikal na performance, kung saan mahalaga ang pagkakasunud-sunod ng tawag.
Ang 100% coverage ay hindi maabot at hindi kinakailangang layunin. Ayon sa Google Testing Blog (2025), ang optimal na antas ng coverage para sa unit test ay 70-80% ng lines of code. Ang 100% coverage ay madalas na naaabot sa pamamagitan ng pag-test ng getter, setter, at constructor, na hindi nagdadala ng halaga. Tumutok sa kritikal na business logic: komplikadong kalkulasyon, validation, error handling, edge case. Gamitin ang JaCoCo (Java), Coverage.py (Python), Istanbul (JS) para sa pagsukat at magtakda ng threshold sa CI — pagbagsak ng build sa coverage na mas mababa sa 60%.
Ang unit test ay unang yugto ng anumang CI/CD pipeline. Isinasagawa ang mga ito sa bawat push sa repository, bago ang build at deploy. Ang average na oras ng pagpapatakbo ng unit test sa isang proyekto ay hindi dapat lumagpas sa 5 minuto — kung mas mahaba, ang test ay hindi na “mabilis” at ang developer ay humihinto sa pagpapatakbo nito nang lokal. Hatiin ang test sa mabilis (unit) at mabagal (integration) at patakbuhin ang mga ito sa iba’t ibang yugto ng pipeline. Gamitin ang parallel execution at fail-fast para sa pagpapabilis.
Mga Madalas Itanong
Sinusuri ng unit test ang isang module sa paghihiwalay, pinapalitan ang external dependency ng mock. Sinusuri ng integration test ang interaksyon sa pagitan ng maraming totoong component (database, API, file system). Ang unit test ay isinasagawa sa millisecond, ang integration test ay sa segundo. Sa piramide ng pagsusuri, ang unit test ay sumasakop ng 70%.
Ang pagpili ay depende sa platform: JUnit 5 para sa Java/Kotlin, XCTest para sa iOS/Swift, pytest para sa Python, Jest/Vitest para sa JavaScript/TypeScript, flutter_test para sa Flutter. Para sa mocking, gamitin ang Mockito (Java), Cuckoo (iOS), unittest.mock (Python), o vitest.mock (JS). Lahat ng modernong framework ay sumusuporta sa parameterized test, built-in assert, at parallel execution.
Fast — ang test ay isinasagawa sa millisecond. Isolated — hindi nakadepende sa ibang test at external system. Repeatable — nagbibigay ng parehong resulta sa anumang makina. Self-validating — awtomatikong sinusuri ang resulta. Timely — isinulat bago o kasabay ng code. Ang paglabag sa alinmang prinsipyo ay nagbabawas ng epektibidad ng pagsusuri.
Oo, sapilitan. Ang ViewModel ay naglalaman ng business logic — paghawak ng event, pag-transform ng data, pamamahala ng estado. Sa Android, gamitin ang kotlinx-coroutines-test para sa coroutine at Turbine para sa pag-test ng StateFlow. Sa iOS, i-test ang Combine Publishers o async/await sa ViewModel. Ang ViewModel test ay purong unit test na tumatakbo sa JVM/macOS nang walang emulator.
Ang network request sa unit test ay hindi isinasagawa — pinapalitan ang mga ito ng mock ng HTTP client. Sa Android, gamitin ang MockWebServer (OkHttp) — ito ay nagpapatakbo ng lokal na HTTP server, na mas gusto kaysa sa mock dahil ginagaya nito ang totoong network interaksyon. Ang MockWebServer ay nagbibigay ng paghihiwalay nang hindi nawawala ang realismo. Para sa iOS — OHHTTPStubs o URLProtocol para sa pag-intercept at pagpapalit ng tugon.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din