Test Doubles — ay mga bagay na pamalit na ginagamit sa mga unit test sa halip ng mga tunay na dependencies. Ang terminong ito ay ipinakilala ni Gerard Meszaros sa aklat na “xUnit Test Patterns” (2007) bilang isang pangkalahatang konsepto para sa Mock, Stub, Fake, Spy at Dummy. Ayon kay Martin Fowler (2024), pinapayagan ng Test Doubles ang paghihiwalay ng sinusubok na bahagi mula sa kapaligiran nito, na ginagawang deterministiko, mabilis at malaya sa mga panlabas na serbisyo ang mga test.
Mga Pangunahing
Test Doubles — ay isang termino mula sa industriya ng sasakyan (stunt double, “double” para sa mga aktor) na dinala sa pag-develop ng software. Kung paanong pinapalitan ng isang stunt double ang isang aktor sa isang delikadong eksena, pinapalitan ng Test Double ang isang tunay na bahagi sa isang test scenario. Ito ay kinakailangan kapag ang tunay na dependency ay hindi available, mabagal, hindi deterministiko, o may mga side effect.
Sinasaklaw ng konsepto ng Test Double ang limang tiyak na uri, bawat isa ay lumulutas ng kaniyang sariling gawain. Ang tipolohiya ni Meszaros ay kanonikal at ginagamit sa lahat ng modernong gabay sa pagsubok. Ang pagkakaiba sa pagitan ng mga uri ay nasa antas ng kontrol at beripikasyon: mula sa simpleng pagpuno ng mga parameter (Dummy) hanggang sa kumpletong pagsusuri ng pagkakasunod-sunod ng mga tawag (Mock).
Ang pangunahing layunin ng Test Doubles ay paghihiwalay ng sinusubok na modyul. Sa mobile development, ang mga tunay na dependency ay mga API server, database, file system, sensor ng device, mga serbisyo ng system (LocationManager, Camera, Bluetooth). Ang direktang paggamit ng mga bahaging ito ay ginagawang mabagal, marupok at umaasa sa kapaligiran ang mga test. Ayon sa Google Testing Blog (2023), ang maayos na nakahiwalay na mga unit test ay tumatakbo sa loob ng millisecond, at ang mga integration test ay sa mga segundo at minuto.
Ang klasipikasyon ni Gerard Meszaros ay may kasamang limang uri ng Test Doubles, na nagkakaiba sa pag-uugali at layunin ng paggamit. Ang pag-unawa sa pagkakaiba sa pagitan ng mga ito ay batayan ng tamang unit testing.
Dummy — ay isang bagay na ipinapasa sa sinusubok na metodo, ngunit hindi kailanman ginagamit. Ang Dummy ay kailangan lamang upang matugunan ang lagda ng metodo. Sa Kotlin, ito ay madalas na null, emptyList() o isang bagay na may mga pamalit. Ang Dummy ay hindi dapat maglaman ng anumang lohika — kung ito ay tawagin, ang test ay dapat mabigo.
Fake — ay isang pinasimple ngunit gumaganang implementasyon ng isang interface. Hindi tulad ng Mock at Stub, ang Fake ay naglalaman ng tunay na lohika ng negosyo, ngunit sa pinasimpleng anyo. Ang klasikong halimbawa — InMemoryUserRepository, na nag-iimbak ng data sa HashMap sa halip ng database. Ang Fake ay ginagamit kapag kailangan subukan ang lohika na umaasa sa estado, ngunit walang overhead ng tunay na imprastraktura.
| Uri | Layunin | Halimbawa |
|---|---|---|
| Dummy | Punan ang parameter | null, walang laman na bagay |
| Fake | Gumaganang pinasimpleng implementasyon | InMemoryRepository |
| Stub | Magbalik ng nakapirming halaga | when(api.getUser()).thenReturn(user) |
| Spy | Talaan ng mga tawag para sa pagsusuri | verify(spy).save(user) |
| Mock | Suriin ang interaksyon | verify(mock).sendEmail(email) |
Stub ay nagbabalik ng mga paunang natukoy na halaga sa mga tiyak na tawag. Hindi sinusuri ng Stub kung ito ay tinawag — nagbibigay lamang ito ng data. Sa Mockito, ang Stub ay nilikha sa pamamagitan ng when(method).thenReturn(value). Ang Stub ay perpekto para sa pagsubok kapag ang dependency ay kailangang magbalik ng isang tiyak na halaga, ngunit ang mismong katotohanan ng tawag ay hindi mahalaga.
Spy — ay isang pambalot sa paligid ng isang tunay na bagay na nagtatala ng lahat ng tawag para sa susunod na beripikasyon. Hindi tulad ng Mock, ang Spy ay nagdedelegate ng mga tawag sa tunay na bagay, ngunit pinapayagan ang pagsusuri na nangyari ang mga ito. Sa Mockito, ang Spy ay nilikha sa pamamagitan ng spy(realObject). Ang Spy ay kapaki-pakinabang para sa bahagyang mocking kapag gusto mong gumamit ng isang tunay na bagay ngunit suriin ang ilang mga tawag.
Mock — ay isang bagay na may mga paunang natukoy na inaasahan sa tawag. Sinusuri ng Mock kung ang mga tiyak na metodo ay tinawag na may mga tiyak na argumento at sa isang tiyak na pagkakasunud-sunod. Hindi tulad ng Stub, ang Mock ay nakatuon sa beripikasyon ng pag-uugali, hindi sa pagbabalik ng data. Ang Mock ay ang pinakamakapangyarihan at pinakamadalas gamitin na uri ng Test Double sa mobile development.
Ang pagkakaiba sa pagitan ng Mock at Stub ay madalas na nagdudulot ng kalituhan kahit sa mga may karanasang developer. Ang pangunahing pagkakaiba ay sa layunin: sinusuri ng Stub ang estado (state verification), sinusuri ng Mock ang pag-uugali (behavior verification).
Sinasagot ng Stub ang tanong: “ibinalik ba ng code ang tamang resulta?”. Sinasagot ng Mock ang tanong: “tinawag ba ng code ang tamang mga metodo na may tamang mga argumento?”. Sa mobile development, ang Stub ay ginagamit kapag mahalaga ang resulta (halimbawa, data mula sa repository), at ang Mock kapag mahalaga ang mga side effect (halimbawa, pagpapadala ng email, pagsulat sa database).
// Stub: pagsusuri ng estado
every { repository.getUsers() } returns listOf(user)
val result = useCase.getUsers()
assertEquals(1, result.size)
// Mock: pagsusuri ng pag-uugali
every { analytics.logEvent("purchase") } returns Unit
useCase.purchase(item)
verify { analytics.logEvent("purchase") }
Mga praktikal na halimbawa ng lahat ng limang uri ng Test Doubles sa Kotlin gamit ang MockK — ang pinakasikat na library ng mocking para sa mga proyektong Android.
class InMemoryUserRepository : UserRepository {
private val store = mutableMapOf<String, User>()
override fun save(user: User) {
store[user.email] = user
}
override fun findByEmail(email: String): User? {
return store[email]
}
}
class RegisterUseCaseTest {
private val api = mockk<AuthApi>()
private val repo = spyk(InMemoryUserRepository())
private val useCase = RegisterUseCase(api, repo)
fun `register user successfully`() = runTest {
// Stub: nagbabalik ng nakapirming tugon ng API
coEvery { api.register("test@test.com") } returns AuthResult.Success("token123")
val result = useCase.execute("test@test.com")
// Verify: sinusuri na ang user ay nai-save
verify { repo.save(any()) }
assertTrue(result.isSuccess())
}
}
data class Logger(val appContext: Context, val format: FormatType)
fun `test logger with dummy context`() {
// Dummy: Ang Context ay hindi ginagamit sa loob ng Logger
val dummyContext = mockk<Context>()
val logger = Logger(dummyContext, FormatType.JSON)
assertEquals(FormatType.JSON, logger.format)
}
Ang pagpili ng uri ng Test Double ay depende sa kung ano ang eksaktong sinusubukan: estado, pag-uugali o integrasyon. Sa mobile development sa Android at iOS, ang mga sumusunod na rekomendasyon ay nabuo.
Sa pagsubok ng ViewModel, gumamit ng Mock para sa mga dependency na gumagawa ng mga side effect (mga repository, analytics, nabigasyon) at Stub para sa mga dependency na nagbabalik ng data (mga kliyente ng API, ContentProvider). Ito ay nagpapahintulot na suriin kung tama ang paghawak ng ViewModel sa parehong matagumpay at error na mga senaryo.
Sa antas ng Repository, mas gusto ang Fake (mga implementasyon ng database na in-memory) at Stub (mga nakapirming tugon ng API). Pinapahintulutan ng Fake ang pagsubok ng lohika ng caching at offline na mode nang walang konfigurasyon ng SQLite. Ginagaya ng Stub ang iba't ibang HTTP status: 200, 404, 500, timeout.
Ang hindi tamang paggamit ng Test Doubles — isa sa mga pinakakaraniwang sanhi ng marupok na mga test na nasisira sa bawat refactoring.
Ang pinakakaraniwang pagkakamali — ang pag-mock ng lahat. Kung ang bawat dependency sa isang test ay pinalitan ng Mock, ang test ay hihinto sa pagsusuri ng tunay na pag-uugali. Ang Mock ay dapat lamang para sa panlabas na mga dependency (network, database, file system, mga serbisyo ng system). Ang mga panloob na bahagi ng aplikasyon (Value Object, data class, mga simpleng utility) ay hindi dapat palitan.
Ang pangalawang pagkakamali — paglikha ng Mock nang hindi tinutukoy ang mga inaasahan. Kung ang isang metodo ay tinawag nang walang every / when, ang Mock ay nagbabalik ng default na halaga (null, 0, false). Ito ay maaaring humantong sa maling positibong mga test kapag ang Mock ay tahimik na nagbabalik ng null, at ang test ay nagbibigay-kahulugan dito bilang tamang pag-uugali.
Ang pangatlong pagkakamali — pagsusuri ng bawat tawag ng bawat Mock. Verify ay dapat gamitin lamang para sa mga tawag na kritikal mula sa pananaw ng lohika ng negosyo. Ang labis na beripikasyon ay ginagawang marupok ang mga test: ang pagbabago sa pagkakasunud-sunod ng mga tawag sa production code ay sumisira ng mga test nang hindi binabago ang pag-uugali.
Mga Madalas Itanong
Stub ay nagbabalik ng data at sinusuri ang estado (kung ano ang ibinalik), habang Mock ay sinusuri ang pag-uugali (anong mga metodo ang tinawag). Stub = “ibalik ang X”, Mock = “suriin na ang Y ay tinawag na may argumentong Z”. Sa tunay na mga test, ang isang bagay ay madalas na gumaganap bilang parehong Stub at Mock nang sabay.
Fake ay mas gusto kaysa Mock kapag sinusuri ang lohika na umaasa sa estado: caching, offline mode, mga transaksyon. Ang Fake (in-memory implementasyon) ay nagpapahintulot sa pagsubok ng mga senaryong ito nang walang marupok na mga tawag sa verify. Ang Mock ay mas angkop para sa pagsusuri ng pagpapadala ng data: analytics, push notification, email.
Para sa mga proyektong Android sa Kotlin, inirerekomenda ang MockK. Sinusuportahan nito ang mga coroutine, suspend function, sealed class at extension function nang walang karagdagang konfigurasyon. Para sa mga proyektong Java, ang pamantayan ay nananatiling Mockito — ang pinakasikat na library na may malawak na dokumentasyon.
Para sa pagsubok ng Kotlin Flow, gamitin ang library na Turbine kasabay ng MockK. Pinapasimple ng Turbine ang pagsusuri ng emisyon ng Flow: maaari mong suriin ang pagkakasunud-sunod ng mga halaga, pagkumpleto ng daloy at mga eksepsiyon. Ang Stub para sa Flow ay nagbabalik ng flowOf(value), sinusuri ng Mock kung ang Flow ay nakolekta.
Oo, ngunit sa antas ng mga tugon ng API, hindi ng mga bahagi ng UI. Ang mga library na MockWebServer (OkHttp) at WireMock ay nagpapahintulot sa pagpapalit ng mga tugon ng HTTP sa mga UI test. Ang mga bahagi ng UI mismo (Compose, SwiftUI Views) ay hindi dapat palitan — ang kanilang pag-uugali ay sinusuri sa pamamagitan ng mga screenshot test at Espresso.
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