Eşzamanlı yürütme işlemleri eşzamansız olabileceği ve birden fazla iş parçacığında gerçekleşebileceği için, eş yordamları kullanan birim testi kodları biraz daha dikkatli olmayı gerektirir. Bu kılavuzda, askıya alma işlevlerinin nasıl test edilebileceği, bilmeniz gereken test yapıları ve eş yordamların kullanıldığı kodunuzu nasıl test edilebilir hale getireceğiniz açıklanmaktadır.
Bu kılavuzda kullanılan API'ler kotlinx.coroutines.test kitaplığının bir parçasıdır. Bu API'lere erişebilmek için projenize bir test bağımlılığı olarak yapıyı eklediğinizden emin olun.
dependencies {
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
}
Testlerde askıya alma işlevlerini çağırma
Testlerde askıya alma işlevlerini çağırmak için eş yordam içinde olmanız gerekir. JUnit test işlevlerinin kendisi askıya alınmadığından, yeni bir eş yordam başlatmak için testlerinizin içinde bir eş yordam oluşturucu çağırmanız gerekir.
runTest, test için tasarlanmış bir eş yordam oluşturucudur. Eş yordam içeren tüm testleri sarmalamak için bunu kullanın. Eş yordamların yalnızca doğrudan test gövdesinde değil, aynı zamanda testte kullanılan nesneler tarafından da başlatılabileceğini unutmayın.
suspend fun fetchData(): String { delay(1000L) return "Hello world" } @Test fun dataShouldBeHelloWorld() = runTest { val data = fetchData() assertEquals("Hello world", data) }
Genel olarak, test başına bir runTest çağrınız olmalıdır ve bir ifade gövdesi kullanmanız önerilir.
Testinizin kodunu runTest içinde sarmalamak temel askıya alma işlevlerini test etmek için işe yarar ve eş yordamlardaki gecikmeleri otomatik olarak atlayarak yukarıdaki testin bir saniyeden çok daha hızlı tamamlanmasını sağlar.
Bununla birlikte, test edilen kodunuzda ne olduğuna bağlı olarak dikkate alınması gereken başka noktalar da vardır:
- Kodunuz,
runTesttarafından oluşturulan üst düzey test koordini dışında yeni eş yordamlar oluşturduğunda, uygunTestDispatcheröğesini seçerek bu yeni eş yordamların nasıl planlanacağını kontrol etmeniz gerekir. - Kodunuz, eş yordam yürütmeyi diğer sevk görevlilerine taşırsa (örneğin,
withContextkullanarak)runTestgenellikle çalışmaya devam eder, ancak artık gecikmeler atlanmaz ve kod birden fazla iş parçacığında çalıştırıldığından testler daha az tahmin edilebilir olur. Bu nedenlerle, testlerde gerçek sevk görevlilerinin yerine test görev dağıtıcıları eklemeniz gerekir.
Test Görevlileri
TestDispatchers, test amaçlı CoroutineDispatcher uygulamalarıdır. Test sırasında yeni eş yordamlar oluşturulursa yeni eş yordamların yürütülmesini öngörülebilir hale getirmek için TestDispatchers kullanmanız gerekir.
Kullanılabilir iki TestDispatcher uygulaması vardır: StandardTestDispatcher ve UnconfinedTestDispatcher, yeni başlatılan eş yordamların farklı programlarını gerçekleştirir. Bu ikisi de sanal zamanı kontrol etmek ve testte çalışan eş yordamları yönetmek için bir TestCoroutineScheduler kullanır.
Bir testte kullanılan yalnızca bir planlayıcı örneği olmalıdır ve tüm TestDispatchers arasında paylaşılanlar olmalıdır. Paylaşım planlayıcıları hakkında bilgi edinmek için TestDispatcher Ekleme bölümüne bakın.
Üst düzey test koordinini başlatmak için runTest, her zaman TestDispatcher kullanacak olan bir CoroutineScope uygulaması olan bir TestScope oluşturur. Belirtilmezse TestScope, varsayılan olarak bir StandardTestDispatcher oluşturur ve üst düzey test koordinini çalıştırmak için bunu kullanır.
runTest, TestScope sevk görevlisi tarafından kullanılan planlayıcıda sıraya alınan eş yordamları takip eder ve ilgili planlayıcı üzerinde bekleyen çalışma olduğu sürece geri dönmez.
StandartTest Görevlisi
StandardTestDispatcher üzerinde yeni eş yordamlar başlattığınızda, test iş parçacığının ücretsiz olduğu her durumda çalıştırılmak üzere temel planlayıcıda sıraya alınırlar. Bu yeni eş yordamların çalışmasına izin vermek için test iş parçacığını getirmeniz gerekir (diğer eş yordamların kullanılabilmesi için serbest bırakın). Bu sıraya alma davranışı, yeni eş yordamların test sırasında çalışma şekli üzerinde size hassas bir kontrol sağlar ve üretim kodundaki eş yordamların programlanmasına benzer.
Üst düzey test eş yordamının yürütülmesi sırasında test iş parçacığı hiç oluşturulmazsa yeni eş yordamlar yalnızca test eş yordamı tamamlandıktan sonra (ancak runTest döndürmeden önce) çalışır:
@Test fun standardTest() = runTest { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ❌ Fails }
Sıraya alınan eş yordamların çalışmasını sağlamak için test eş yordamını oluşturmanın çeşitli yolları vardır. Bu çağrıların tümü, geri dönmeden önce diğer eş yordamların test ileti dizisinde çalıştırılmasına izin verir:
advanceUntilIdle: Sırada hiçbir şey kalmayıncaya kadar planlayıcıdaki diğer tüm eş yordamları çalıştırır. Bu, beklemedeki tüm eş yordamların çalıştırılmasına izin vermek için iyi bir varsayılan seçenektir ve çoğu test senaryosunda çalışır.advanceTimeBy: Sanal zamanı belirli bir miktar kadar ilerletir ve bu noktadan önce çalışacak şekilde planlanmış eş yordamları sanal zamanda çalıştırır.runCurrent: Geçerli sanal zamanda planlanmış eş yordamlar çalıştırır.
Önceki testi düzeltmek amacıyla advanceUntilIdle, bekleyen iki eş yordamın onaylamaya devam etmeden önce işlerini yapmasına izin vermek için kullanılabilir:
@Test fun standardTest() = runTest { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } advanceUntilIdle() // Yields to perform the registrations assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes }
Serbest Test Görevlisi
UnconfinedTestDispatcher üzerinde yeni eş yordamlar başlatıldığında, istekle mevcut ileti dizisinde başlatılır. Bu sayede, eş yordam oluşturucunun geri dönmesini beklemeden hemen çalışmaya başlayacaklar. Yeni eş yordamların çalıştırılmasına izin vermek için test iş parçacığını manuel olarak oluşturmanız gerekmediğinden çoğu durumda bu gönderme davranışı daha basit test koduyla sonuçlanır.
Ancak bu davranış, test dışı sevk görevlileriyle üretimde göreceğinizden farklıdır. Testiniz eşzamanlılığa odaklanıyorsa bunun yerine StandardTestDispatcher özelliğini kullanmayı tercih edin.
Bu sevk görevlisini, varsayılan test yerine runTest konumundaki üst düzey test eş yordamı için kullanmak istiyorsanız bir örnek oluşturun ve bunu parametre olarak iletin. Bu işlem, sevk görevlisini TestScope kaynağından devraldığı için runTest içinde oluşturulan yeni eş yordamların istekli bir şekilde yürütülmesini sağlar.
@Test fun unconfinedTest() = runTest(UnconfinedTestDispatcher()) { val userRepo = UserRepository() launch { userRepo.register("Alice") } launch { userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes }
Bu örnekte, lansman çağrıları yeni eş yordamlarını UnconfinedTestDispatcher saatinde heyecanla başlatacaktır; yani her lansman çağrısı yalnızca kayıt tamamlandıktan sonra geri gelecektir.
UnconfinedTestDispatcher adlı kanalın yeni eş yordamları heyecanla başlatacağını, ancak bu, onları da sabırsızlıkla tamamlayacağı anlamına gelmez. Yeni eş yordam askıya alınırsa diğer eş yordamlar yürütmeye devam eder.
Örneğin, bu testte kullanıma sunulan yeni eş yordam Aylin'i kaydeder ancak delay çağrıldığında askıya alınır. Bu işlem, üst düzey koordinasyonun onaylamaya devam etmesini sağlar ve Bülent henüz kaydedilmemiş olduğundan test başarısız olur:
@Test fun yieldingTest() = runTest(UnconfinedTestDispatcher()) { val userRepo = UserRepository() launch { userRepo.register("Alice") delay(10L) userRepo.register("Bob") } assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ❌ Fails }
Test sevk görevlileri ekleme
Test edilen kod, ileti dizilerini değiştirmek (withContext ile) veya yeni eş yordamlar başlatmak için sevk görevlilerini kullanabilir. Kod, paralel olarak birden fazla iş parçacığında yürütüldüğünde testler güvenilir olmayabilir. Onaylama işlemlerini doğru zamanda gerçekleştirmek veya kontrolünüzün olmadığı arka plan iş parçacıklarında çalışan görevlerin tamamlanmasını beklemek zor olabilir.
Testlerde, bu sevk görevlilerini TestDispatchers örnekleriyle değiştirin. Bunun bazı avantajları vardır:
- Kod, tek test iş parçacığı üzerinde çalışarak testleri daha deterministik hale getirir
- Yeni eş yordamların nasıl planlanıp yürütüleceğini kontrol edebilirsiniz
- TestDispatcher araçları, gecikmeleri otomatik olarak atlayan ve manuel olarak ilerlemenizi sağlayan sanal zaman için bir planlayıcı kullanır.
Bağımlılık yerleştirme yöntemini kullanarak
görev dağıtıcıların yerini alacak gerçek sevk görevlilerini
testler. Bu örneklerde CoroutineDispatcher ekleyeceğiz, ancak dilerseniz
en geniş kapsamlı ifade
CoroutineContext
Böylece testler sırasında daha da fazla esneklik elde edebilirsiniz.
Eş yordamların başlatıldığı sınıflara CoroutineScope da ekleyebilirsiniz.
(Kapsam ekleme bölümünde açıklandığı gibi) ilgili görev dağıtıcı yerine
bölümüne ekleyin.
TestDispatchers, örneklendirildiğinde varsayılan olarak yeni bir planlayıcı oluşturur. runTest içinde TestScope öğesinin testScheduler özelliğine erişebilir ve bunu yeni oluşturulan TestDispatchers öğelerine aktarabilirsiniz. Böylece sanal zamana dair anlayışları paylaşılacak ve advanceUntilIdle gibi yöntemler, testin tamamlanması için tüm test görev dağıtıcılarında eş yordamlar çalıştırır.
Aşağıdaki örnekte, initialize yönteminde IO sevk aracını kullanarak yeni bir eş yordam oluşturan ve çağrıyı fetchData yönteminde IO sevk görevlisine geçiren bir Repository sınıfı görebilirsiniz:
// Example class demonstrating dispatcher use cases class Repository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { private val scope = CoroutineScope(ioDispatcher) val initialized = AtomicBoolean(false) // A function that starts a new coroutine on the IO dispatcher fun initialize() { scope.launch { initialized.set(true) } } // A suspending function that switches to the IO dispatcher suspend fun fetchData(): String = withContext(ioDispatcher) { require(initialized.get()) { "Repository should be initialized first" } delay(500L) "Hello world" } }
Testlerde, IO sevk görevlisinin yerine bir TestDispatcher uygulaması ekleyebilirsiniz.
Aşağıdaki örnekte, depoya StandardTestDispatcher yerleştiriyoruz ve initialize ürününde başlayan yeni eş yordamın devam etmeden önce tamamlandığından emin olmak için advanceUntilIdle kodunu kullanıyoruz.
fetchData, test iş parçacığında çalıştırılacağı ve test sırasında içerdiği gecikmeyi atlanacağı için TestDispatcher üzerinde çalıştırmanın avantajlarından da faydalanır.
class RepositoryTest { @Test fun repoInitWorksAndDataIsHelloWorld() = runTest { val dispatcher = StandardTestDispatcher(testScheduler) val repository = Repository(dispatcher) repository.initialize() advanceUntilIdle() // Runs the new coroutine assertEquals(true, repository.initialized.get()) val data = repository.fetchData() // No thread switch, delay is skipped assertEquals("Hello world", data) } }
TestDispatcher üzerinde başlatılan yeni eş yordamlar, yukarıda initialize ile gösterildiği gibi manuel olarak gelişmiş olabilir. Bununla birlikte, üretim kodunda bunun mümkün veya istenen bir durum olmadığını unutmayın. Bunun yerine, bu yöntem askıya alınacak (sıralı yürütme için) veya bir Deferred değeri döndürecek (eş zamanlı yürütme için) şekilde yeniden tasarlanmalıdır.
Örneğin, yeni bir koordinat başlatmak ve bir Deferred oluşturmak için async aracını kullanabilirsiniz:
class BetterRepository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { private val scope = CoroutineScope(ioDispatcher) fun initialize() = scope.async { // ... } }
Böylece, hem testlerde hem de üretim kodunda bu kodu tamamladıktan sonra güvenle await yapabilirsiniz:
@Test fun repoInitWorks() = runTest { val dispatcher = StandardTestDispatcher(testScheduler) val repository = BetterRepository(dispatcher) repository.initialize().await() // Suspends until the new coroutine is done assertEquals(true, repository.initialized.get()) // ... }
Eş yordamlar, planlayıcıyı paylaştığı bir TestDispatcher üzerindeyse runTest, geri dönmeden önce beklemedeki eş yordamların tamamlanmasını bekler. Ayrıca, diğer görev dağıtıcılarda olsalar bile üst düzey test eş yordamının alt öğeleri olan eş yordamları da bekler (dispatchTimeoutMs parametresi tarafından belirtilen, varsayılan olarak 60 saniye olan bir zaman aşımına kadar).
Ana sevk görevlisini ayarlama
Yerel birim testlerinde, Android kullanıcı arayüzü iş parçacığını sarmalayan Main görev dağıtıcısı, bu testler Android cihazda değil yerel bir JVM'de yürütüldüğünden kullanılamayacaktır. Test edilen kodunuz ana iş parçacığına referans veriyorsa birim testleri sırasında bir istisna oluşturur.
Bazı durumlarda, Main sevk görevlisini diğer görev dağıtıcılarla aynı şekilde yerleştirebilirsiniz (önceki bölümde açıklandığı gibi). Böylece, testlerde onu bir TestDispatcher ile değiştirebilirsiniz. Ancak, viewModelScope gibi bazı API'ler arka planda sabit kodlu bir Main görev dağıtıcısı kullanır.
Aşağıda, verileri yükleyen bir eş yordam başlatmak için viewModelScope kullanan bir ViewModel uygulaması örneği verilmiştir:
class HomeViewModel : ViewModel() { private val _message = MutableStateFlow("") val message: StateFlow<String> get() = _message fun loadMessage() { viewModelScope.launch { _message.value = "Greetings!" } } }
Main sevk görevlisini her durumda TestDispatcher ile değiştirmek için Dispatchers.setMain ve Dispatchers.resetMain işlevlerini kullanın.
class HomeViewModelTest { @Test fun settingMainDispatcher() = runTest { val testDispatcher = UnconfinedTestDispatcher(testScheduler) Dispatchers.setMain(testDispatcher) try { val viewModel = HomeViewModel() viewModel.loadMessage() // Uses testDispatcher, runs its coroutine eagerly assertEquals("Greetings!", viewModel.message.value) } finally { Dispatchers.resetMain() } } }
Main sevk görevlisi bir TestDispatcher ile değiştirildiyse yeni oluşturulan tüm TestDispatchers, Main görev dağıtıcısının planlayıcısını otomatik olarak kullanacaktır. Buna, runTest tarafından oluşturulan StandardTestDispatcher görev dahildir (başka bir görev dağıtıcı geçmiyorsa).
Bu, test sırasında yalnızca tek bir planlayıcının kullanılmasını kolaylaştırır. Bunun işe yaraması için Dispatchers.setMain çağrısından sonra diğer tüm TestDispatcher örneklerini oluşturduğunuzdan emin olun.
Her testte Main sevk görevlisinin yerini alan kodun yinelenmesini önlemek için yaygın olarak kullanılan bir kalıp, kodu bir JUnit test kuralına ayıklamaktır:
// Reusable JUnit4 TestRule to override the Main dispatcher class MainDispatcherRule( val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(), ) : TestWatcher() { override fun starting(description: Description) { Dispatchers.setMain(testDispatcher) } override fun finished(description: Description) { Dispatchers.resetMain() } } class HomeViewModelTestUsingRule { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun settingMainDispatcher() = runTest { // Uses Main’s scheduler val viewModel = HomeViewModel() viewModel.loadMessage() assertEquals("Greetings!", viewModel.message.value) } }
Bu kural uygulaması varsayılan olarak bir UnconfinedTestDispatcher kullanır, ancak Main sevk görevlisinin belirli bir test sınıfında hızlı bir şekilde yürütülmemesi gerekiyorsa parametre olarak StandardTestDispatcher eklenebilir.
Test gövdesinde bir TestDispatcher örneğine ihtiyaç duyduğunuzda, istenen türde olduğu sürece kuraldaki testDispatcher öğesini yeniden kullanabilirsiniz. Testte kullanılan TestDispatcher türünü açıkça belirtmek isterseniz veya Main için kullanılandan farklı bir TestDispatcher türüne ihtiyacınız varsa runTest içinde yeni bir TestDispatcher oluşturabilirsiniz. Main sevk görevlisi TestDispatcher olarak ayarlandığından, yeni oluşturulan her TestDispatchers planlayıcısını otomatik olarak paylaşır.
class DispatcherTypesTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun injectingTestDispatchers() = runTest { // Uses Main’s scheduler // Use the UnconfinedTestDispatcher from the Main dispatcher val unconfinedRepo = Repository(mainDispatcherRule.testDispatcher) // Create a new StandardTestDispatcher (uses Main’s scheduler) val standardRepo = Repository(StandardTestDispatcher()) } }
Testin dışında sevk görevlileri oluşturma
Bazı durumlarda, test yönteminin dışında bir TestDispatcher aracının kullanılabilir olması gerekebilir. Örneğin, test sınıfındaki bir özellik başlatılırken:
class ExampleRepository(private val ioDispatcher: CoroutineDispatcher) { /* ... */ } class RepositoryTestWithRule { private val repository = ExampleRepository(/* What TestDispatcher? */) @get:Rule val mainDispatcherRule = MainDispatcherRule() @Test fun someRepositoryTest() = runTest { // Test the repository... // ... } }
Main sevk görevlisini önceki bölümde gösterildiği gibi değiştiriyorsanız Main sevk görevlisi değiştirildikten sonra oluşturulan TestDispatchers, planlayıcısını otomatik olarak paylaşır.
Ancak test sınıfının özellikleri olarak oluşturulan TestDispatchers veya test sınıfındaki özelliklerin ilk kullanıma hazırlanması sırasında oluşturulan TestDispatchers için bu durum geçerli değildir. Bunlar, Main sevk görevlisi değiştirilmeden önce başlatılır. Bu nedenle yeni planlayıcılar oluştururlar.
Testinizde yalnızca bir planlayıcı olduğundan emin olmak için önce MainDispatcherRule özelliğini oluşturun. Ardından, sınıf düzeyindeki diğer özelliklerin başlatıcılarında gerektiğinde sevk görevlisini (veya farklı türde bir TestDispatcher öğesine ihtiyacınız varsa planlayıcısını) yeniden kullanın.
class RepositoryTestWithRule { @get:Rule val mainDispatcherRule = MainDispatcherRule() private val repository = ExampleRepository(mainDispatcherRule.testDispatcher) @Test fun someRepositoryTest() = runTest { // Takes scheduler from Main // Any TestDispatcher created here also takes the scheduler from Main val newTestDispatcher = StandardTestDispatcher() // Test the repository... } }
Testte oluşturulan runTest ve TestDispatchers işlemlerinin yine de Main sevk görevlisinin planlayıcısını otomatik olarak paylaşacağını unutmayın.
Main sevk görevlisini değiştirmiyorsanız sınıfın özelliği olarak ilk TestDispatcher öğenizi (yeni bir planlayıcı oluşturur) oluşturun. Ardından, bu planlayıcıyı hem özellik olarak hem de test kapsamında her runTest çağrısına ve oluşturulan her yeni TestDispatcher öğesine manuel olarak iletin:
class RepositoryTest { // Creates the single test scheduler private val testDispatcher = UnconfinedTestDispatcher() private val repository = ExampleRepository(testDispatcher) @Test fun someRepositoryTest() = runTest(testDispatcher.scheduler) { // Take the scheduler from the TestScope val newTestDispatcher = UnconfinedTestDispatcher(this.testScheduler) // Or take the scheduler from the first dispatcher, they’re the same val anotherTestDispatcher = UnconfinedTestDispatcher(testDispatcher.scheduler) // Test the repository... } }
Bu örnekte, ilk sevk görevlisinin planlayıcısı runTest adresine iletilir. Bu işlem, söz konusu planlayıcıyı kullanarak TestScope için yeni bir StandardTestDispatcher oluşturur. Ayrıca, sevk görevlisini doğrudan runTest hattına geçerek ilgili sevk görevlisinde test eş yordamını çalıştırabilirsiniz.
Kendi TestScope'unuzu oluşturma
TestDispatchers ürününde olduğu gibi, test gövdesinin dışında bir TestScope öğesine erişmeniz gerekebilir. runTest, arka planda otomatik olarak bir TestScope oluştururken runTest ile kullanmak üzere kendi TestScope öğenizi de oluşturabilirsiniz.
Bu işlemi yaparken, oluşturduğunuz TestScope üzerinde runTest adlı kişiyi çağırdığınızdan emin olun:
class SimpleExampleTest { val testScope = TestScope() // Creates a StandardTestDispatcher @Test fun someTest() = testScope.runTest { // ... } }
Yukarıdaki kod, dolaylı olarak TestScope için bir StandardTestDispatcher ve yeni bir planlayıcı oluşturur. Bu nesnelerin tümü açıkça oluşturulabilir. Bu özellik, bağımlı ekleme kurulumlarıyla entegre etmeniz gerektiğinde yararlı olabilir.
class ExampleTest { val testScheduler = TestCoroutineScheduler() val testDispatcher = StandardTestDispatcher(testScheduler) val testScope = TestScope(testDispatcher) @Test fun someTest() = testScope.runTest { // ... } }
Kapsam yerleştirme
sırasında kontrol etmeniz gereken, eş yordamlar oluşturan bir sınıfınız varsa
söz konusu sınıfa eş yordamlı bir kapsam ekleyebilir, bunu
Testlerde TestScope.
Aşağıdaki örnekte UserState sınıfı bir UserRepository öğesine bağlıdır
yeni kullanıcılar kaydettirmek ve kayıtlı kullanıcıların listesini getirmek için kullanılır. Bu çağrılar
UserRepository işlevi, işlev çağrılarını askıya alıyor. UserState, yerleştirilen
registerUser işlevi içinde yeni bir eş yordam başlatmak için CoroutineScope tuşuna basın.
class UserState( private val userRepository: UserRepository, private val scope: CoroutineScope, ) { private val _users = MutableStateFlow(emptyList<String>()) val users: StateFlow<List<String>> = _users.asStateFlow() fun registerUser(name: String) { scope.launch { userRepository.register(name) _users.update { userRepository.getAllUsers() } } } }
Bu sınıfı test etmek için, ders oluştururken runTest sınıfından TestScope notunu geçebilirsiniz.
UserState nesnesi:
class UserStateTest { @Test fun addUserTest() = runTest { // this: TestScope val repository = FakeUserRepository() val userState = UserState(repository, scope = this) userState.registerUser("Mona") advanceUntilIdle() // Let the coroutine complete and changes propagate assertEquals(listOf("Mona"), userState.users.value) } }
Test işlevinin dışına, örneğin bir özellik olarak oluşturulan bir test için Kendi TestScope'unuzu oluşturma.