Bu codelab, Advanced Android in Kotlin kursunun bir parçasıdır. Bu kurstan en iyi şekilde yararlanmak için codelab'leri sırayla incelemeniz önerilir ancak bu zorunlu değildir. Kursla ilgili tüm codelab'ler Kotlin'de İleri Düzey Android codelab'leri açılış sayfasında listelenir.
Giriş
Bu ikinci test laboratuvarı, test çiftleriyle ilgilidir: Android'de ne zaman kullanılacağı ve bağımlılık ekleme, hizmet bulucu kalıbı ve kitaplıklar kullanılarak nasıl uygulanacağı. Bu süreçte şunları yazmayı öğreneceksiniz:
- Depo birimi testleri
- Parçalar ve ViewModel entegrasyon testleri
- Parça gezinme testleri
Bilmeniz gerekenler
Aşağıdaki konular hakkında bilgi sahibi olmanız gerekir:
- Kotlin programlama dili
- İlk codelab'de ele alınan test kavramları: Android'de birim testleri yazma ve çalıştırma, JUnit, Hamcrest, AndroidX test, Robolectric ve LiveData'yı test etme
- Şu temel Android Jetpack kitaplıkları:
ViewModel,LiveDatave Navigation Component - Uygulama mimarisi kılavuzu ve Android'in temelleri codelab'lerindeki kalıbı izleyen uygulama mimarisi
- Android'de eş yordamların temelleri
Neler öğreneceksiniz?
- Test stratejisi planlama
- Test çiftlerini (sahteler ve taklitler) oluşturma ve kullanma
- Birim ve entegrasyon testleri için Android'de manuel bağımlılık ekleme nasıl kullanılır?
- Hizmet Bulucu Kalıbı'nı uygulama
- Depoları, parçaları, görünüm modellerini ve Navigation bileşenini test etme
Aşağıdaki kitaplıkları ve kod kavramlarını kullanacaksınız:
Yapacaklarınız
- Test çifti ve bağımlılık ekleme kullanarak bir depo için birim testleri yazın.
- Test çifti ve bağımlılık ekleme kullanarak bir görünüm modeli için birim testleri yazın.
- Espresso kullanıcı arayüzü test çerçevesini kullanarak parçalar ve bunların görünüm modelleri için entegrasyon testleri yazın.
- Mockito ve Espresso kullanarak gezinme testleri yazın.
Bu codelab serisinde, yapılacaklar notları uygulamasıyla çalışacaksınız. Bu uygulama, tamamlanacak görevleri yazmanıza ve bunları bir listede görüntülemenize olanak tanır. Ardından bunları tamamlandı veya tamamlanmadı olarak işaretleyebilir, filtreleyebilir ya da silebilirsiniz.

Bu uygulama Kotlin ile yazılmıştır, birkaç ekranı vardır, Jetpack bileşenlerini kullanır ve Uygulama mimarisi kılavuzundaki mimariye uygundur. Bu uygulamayı nasıl test edeceğinizi öğrenerek aynı kitaplıkları ve mimariyi kullanan uygulamaları test edebilirsiniz.
Kodu İndirme
Başlamak için kodu indirin:
Alternatif olarak, kod için GitHub deposunu klonlayabilirsiniz:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_1
Aşağıdaki talimatları uygulayarak kodu inceleyin.
1. adım: Örnek uygulamayı çalıştırın
TO-DO uygulamasını indirdikten sonra Android Studio'da açıp çalıştırın. Derlenmesi gerekir. Aşağıdaki adımları uygulayarak uygulamayı keşfedin:
- Kaydırma artı düğmesiyle yeni bir görev oluşturun. Önce başlık, ardından görevle ilgili ek bilgileri girin. Yeşil onay işaretiyle kaydedin.
- Görev listesinde, yeni tamamladığınız görevin başlığını tıklayın ve açıklamanın geri kalanını görmek için bu görevin ayrıntılar ekranına bakın.
- Listede veya ayrıntılar ekranında, görevin durumunu Tamamlandı olarak ayarlamak için görevin onay kutusunu işaretleyin.
- Görevler ekranına geri dönün, filtre menüsünü açın ve görevleri Etkin ve Tamamlandı durumuna göre filtreleyin.
- Gezinme çekmecesini açın ve İstatistikler'i tıklayın.
- Genel bakış ekranına dönün ve gezinme çekmecesi menüsünden Tamamlandı durumundaki tüm görevleri silmek için Tamamlananları temizle'yi seçin.
2. adım: Örnek uygulama kodunu inceleyin
Yapılacaklar uygulaması, popüler Architecture Blueprints test ve mimari örneğini (örneğin reactive architecture sürümünü kullanarak) temel alır. Uygulama, Uygulama mimarisi kılavuzundaki mimariye uygundur. Fragments, bir depo ve Room ile ViewModels kullanır. Aşağıdaki örneklerden herhangi birini biliyorsanız bu uygulamanın mimarisi benzerdir:
- Room with a View Codelab'i
- Android Kotlin Hakkında Temel Bilgiler eğitim codelab'leri
- İleri düzey Android eğitim codelab'leri
- Android Sunflower Sample
- Kotlin ile Android Uygulamaları Geliştirme Udacity eğitim kursu
Uygulamanın genel mimarisini anlamanız, herhangi bir katmandaki mantığı derinlemesine anlamanızdan daha önemlidir.
Bulacağınız paketlerin özeti:
Paket: | |
| Görev ekleme veya düzenleme ekranı: Görev ekleme veya düzenleme için kullanıcı arayüzü katmanı kodu. |
| Veri katmanı: Görevlerin veri katmanıyla ilgilidir. Veritabanı, ağ ve depo kodunu içerir. |
| İstatistikler ekranı: İstatistikler ekranı için kullanıcı arayüzü katmanı kodu. |
| Görev ayrıntıları ekranı: Tek bir görev için kullanıcı arayüzü katmanı kodu. |
| Görevler ekranı: Tüm görevlerin listesi için kullanıcı arayüzü katmanı kodu. |
| Yardımcı sınıflar: Uygulamanın çeşitli bölümlerinde kullanılan paylaşılan sınıflar (ör. birden fazla ekranda kullanılan yenilemek için kaydırma düzeni). |
Veri katmanı (.data)
Bu uygulama, remote paketinde simüle edilmiş bir ağ oluşturma katmanı ve local paketinde bir veritabanı katmanı içerir. Bu projede, basitlik için ağ katmanı gerçek ağ istekleri oluşturmak yerine yalnızca gecikmeli bir HashMap ile simüle edilir.
DefaultTasksRepository, ağ katmanı ile veritabanı katmanı arasında koordinasyon veya aracılık yapar ve verileri kullanıcı arayüzü katmanına döndürür.
Kullanıcı arayüzü katmanı ( .addedittask, .statistics, .taskdetail, .tasks)
Kullanıcı arayüzü katmanı paketlerinin her biri, bir fragment ve bir görünüm modelinin yanı sıra kullanıcı arayüzü için gereken diğer sınıfları (ör. görev listesi için bir bağdaştırıcı) içerir. TaskActivity, tüm parçaları içeren etkinliktir.
Gezinme
Uygulamanın gezinme özelliği Navigation bileşeni tarafından kontrol edilir. Bu değer nav_graph.xml dosyasında tanımlanır. Gezinme, Event sınıfı kullanılarak görünüm modellerinde tetiklenir. Görünüm modelleri, hangi bağımsız değişkenlerin iletileceğini de belirler. Parçalar Event öğesini gözlemler ve ekranlar arasında gerçek gezinmeyi gerçekleştirir.
Bu codelab'de, test çiftlerini ve bağımlılık eklemeyi kullanarak depoları test etmeyi, modelleri ve parçaları görüntülemeyi öğreneceksiniz. Bu testlerin neler olduğunu incelemeden önce, bu testleri ne şekilde yazacağınızı belirleyecek olan mantığı anlamanız önemlidir.
Bu bölümde, Android'de geçerli olan genel testlerle ilgili bazı en iyi uygulamalar ele alınmaktadır.
Test Piramidi
Test stratejisi hakkında düşünürken üç ilgili test yönü vardır:
- Kapsam: Test, kodun ne kadarını etkiliyor? Testler tek bir yöntemde, uygulamanın tamamında veya ikisinin arasında bir yerde çalıştırılabilir.
- Hız: Test ne kadar hızlı çalışır? Test hızları milisaniyelerden birkaç dakikaya kadar değişebilir.
- Doğruluk: Test ne kadar "gerçek dünya" ile ilgili? Örneğin, test ettiğiniz kodun bir bölümünün ağ isteğinde bulunması gerekiyorsa test kodu gerçekten bu ağ isteğinde bulunuyor mu yoksa sonucu taklit mi ediyor? Test ağ ile gerçekten iletişim kuruyorsa bu, testin daha yüksek doğruluk oranına sahip olduğu anlamına gelir. Bununla birlikte, testin çalışması daha uzun sürebilir, ağ bağlantısı kesilirse hatalara neden olabilir veya kullanımı maliyetli olabilir.
Bu yönler arasında doğal olarak ödünler verilir. Örneğin, hız ve doğruluk arasında bir denge vardır. Test ne kadar hızlı olursa doğruluk o kadar düşük olur ve bunun tersi de geçerlidir. Otomatik testleri bölmenin yaygın bir yolu, bunları şu üç kategoriye ayırmaktır:
- Birim testleri: Bunlar, tek bir sınıfta (genellikle o sınıftaki tek bir yöntemde) çalışan, oldukça odaklanmış testlerdir. Bir birim testi başarısız olursa kodunuzda sorunun tam olarak nerede olduğunu öğrenebilirsiniz. Gerçek dünyada uygulamanız tek bir yöntemin veya sınıfın yürütülmesinden çok daha fazlasını içerdiğinden bu testlerin doğruluğu düşüktür. Kodunuzu her değiştirdiğinizde çalıştırılacak kadar hızlıdırlar. Bu testler genellikle yerel olarak çalıştırılan testlerdir (
testkaynak grubunda). Örnek: Görünüm modellerinde ve depolarda tek yöntemleri test etme. - Entegrasyon testleri: Bunlar, birlikte kullanıldıklarında beklendiği gibi davrandıklarından emin olmak için çeşitli sınıfların etkileşimini test eder. Entegrasyon testlerini yapılandırmanın bir yolu, tek bir özelliği (ör. görev kaydetme özelliği) test etmelerini sağlamaktır. Entegrasyon testleri, birim testlerine kıyasla daha geniş bir kod kapsamını test eder ancak tam doğruluk yerine hızlı çalışacak şekilde optimize edilir. Duruma bağlı olarak yerel olarak veya enstrümantasyon testleri olarak çalıştırılabilirler. Örnek: Tek bir parça ve görünüm modeli çiftinin tüm işlevlerini test etme.
- Uçtan uca testler (E2e): Birlikte çalışan özelliklerin kombinasyonunu test edin. Uygulamanın büyük bir bölümünü test ederler, gerçek kullanımı yakından simüle ederler ve bu nedenle genellikle yavaştırlar. En yüksek doğruluk oranına sahiptirler ve uygulamanızın bir bütün olarak gerçekten çalıştığını gösterirler. Genel olarak bu testler,
androidTestkaynak kümesindeki enstrümantasyon testleri olacaktır.
Örnek: Uygulamanın tamamını başlatıp birkaç özelliği birlikte test etme.
Bu testlerin önerilen oranı genellikle bir piramitle gösterilir. Testlerin büyük çoğunluğu birim testleridir.

Mimari ve Test
Uygulamanızı test piramidinin tüm farklı seviyelerinde test edebilme olanağınız, uygulamanızın mimarisiyle doğrudan bağlantılıdır. Örneğin, son derece kötü tasarlanmış bir uygulama, tüm mantığını tek bir yöntemin içine yerleştirebilir. Bu testler uygulamanın büyük bir bölümünü test etme eğiliminde olduğundan, bunun için uçtan uca test yazabilirsiniz. Ancak birim veya entegrasyon testleri yazmak için ne yapmanız gerekir? Kodun tamamı tek bir yerde olduğundan yalnızca tek bir birim veya özellikle ilgili kodu test etmek zordur.
Daha iyi bir yaklaşım, uygulama mantığını birden fazla yönteme ve sınıfa ayırarak her parçanın ayrı ayrı test edilmesini sağlamaktır. Mimari, kodunuzu bölüp düzenlemenin bir yoludur. Bu sayede birim ve entegrasyon testleri daha kolay yapılabilir. Test edeceğiniz YAPILACAKLAR uygulamasında belirli bir mimari kullanılıyor:
Bu derste, yukarıdaki mimarinin bölümlerini uygun şekilde izole ederek nasıl test edeceğinizi göreceksiniz:
- Öncelikle depoyu birim testi yaparsınız.
- Ardından, görünüm modelinde bir test çifti kullanırsınız. Bu, görünüm modelinin birim testi ve entegrasyon testi için gereklidir.
- Ardından, parçalar ve bunların görünüm modelleri için entegrasyon testleri yazmayı öğreneceksiniz.
- Son olarak, Navigation Component'i içeren entegrasyon testleri yazmayı öğreneceksiniz.
Uçtan uca test konusunu bir sonraki derste ele alacağız.
Bir sınıfın bir bölümü (bir yöntem veya küçük bir yöntem koleksiyonu) için birim testi yazdığınızda amacınız yalnızca o sınıftaki kodu test etmektir.
Yalnızca belirli bir sınıftaki veya sınıflardaki kodu test etmek zor olabilir. Bunu bir örnek üzerinde inceleyelim. data.source.DefaultTaskRepository sınıfını main kaynak kümesinde açın. Bu, uygulamanın deposudur ve bir sonraki adımda birim testleri yazacağınız sınıftır.
Amacınız yalnızca söz konusu sınıftaki kodu test etmektir. Ancak DefaultTaskRepository işlevini yerine getirmek için LocalTaskDataSource ve RemoteTaskDataSource gibi diğer sınıflara bağlıdır. Diğer bir deyişle, LocalTaskDataSource ve RemoteTaskDataSource, DefaultTaskRepository'nin bağımlılıklarıdır.
Bu nedenle, DefaultTaskRepository içindeki her yöntem, veri kaynağı sınıflarındaki yöntemleri çağırır. Bu yöntemler de bilgileri bir veritabanına kaydetmek veya ağ ile iletişim kurmak için diğer sınıflardaki yöntemleri çağırır.

Örneğin, DefaultTasksRepo içindeki bu yönteme göz atın.
suspend fun getTasks(forceUpdate: Boolean = false): Result<List<Task>> {
if (forceUpdate) {
try {
updateTasksFromRemoteDataSource()
} catch (ex: Exception) {
return Result.Error(ex)
}
}
return tasksLocalDataSource.getTasks()
}getTasks, deponuza yapabileceğiniz en "temel" çağrılardan biridir. Bu yöntemde, SQLite veritabanından okuma ve ağ çağrıları (updateTasksFromRemoteDataSource çağrısı) yapma işlemleri yer alır. Bu, yalnızca depo kodundan çok daha fazla kod içerir.
Deponun test edilmesinin zor olmasının daha spesifik nedenlerinden bazıları şunlardır:
- Bu depo için en basit testleri bile yapmak üzere veritabanı oluşturma ve yönetme konusunda düşünmeniz gerekir. Bu durum, "Bu test yerel mi yoksa enstrümantasyonlu mu olmalı?" ve simüle edilmiş bir Android ortamı elde etmek için AndroidX Test'i kullanıp kullanmamanız gerektiği gibi soruları gündeme getirir.
- Ağ kodu gibi kodun bazı bölümlerinin çalışması uzun sürebilir veya bazen başarısız olabilir. Bu da uzun süren, güvenilmez testlere yol açar.
- Testleriniz, test hatasıyla ilgili hangi kodun hatalı olduğunu teşhis etme özelliğini kaybedebilir. Testleriniz, depo dışı kodu test etmeye başlayabilir. Bu nedenle, örneğin, sözde "depo" birim testleriniz, veritabanı kodu gibi bağımlı kodlardan bazılarındaki bir sorun nedeniyle başarısız olabilir.
Test Çiftleri
Bunun çözümü, depoyu test ederken gerçek ağ veya veritabanı kodunu kullanmamak, bunun yerine test çifti kullanmaktır. Test çifti, özellikle test için tasarlanmış bir sınıf sürümüdür. Testlerde sınıfın gerçek sürümünün yerini alması amaçlanmıştır. Bu, dublörlerin tehlikeli sahnelerde gerçek oyuncunun yerine geçmesine benzer.
Bazı test çifti türleri şunlardır:
Sahte | Sınıfın "çalışan" bir uygulamasına sahip olan ancak testler için uygun, üretim için uygun olmayan bir şekilde uygulanan test çifti. |
Mock | Hangi yöntemlerinin çağrıldığını izleyen bir test çifti. Daha sonra, yöntemlerinin doğru şekilde çağrılıp çağrılmadığına bağlı olarak testi geçer veya testte başarısız olur. |
Stub | Hiçbir mantık içermeyen ve yalnızca döndürmesi için programladığınız şeyi döndüren bir test çifti. Bir |
Dummy | Yalnızca parametre olarak sağlamanız gerektiği durumlarda olduğu gibi, etrafta dolaşan ancak kullanılmayan bir test çifti. |
Spy | Bazı ek bilgileri de takip eden bir test çifti. Örneğin, |
Test çiftleri hakkında daha fazla bilgi için Testing on the Toilet: Know Your Test Doubles (Tuvalette Test: Test Çiftlerinizi Tanıyın) başlıklı makaleyi inceleyin.
Android'de en sık kullanılan test çiftleri Fakes ve Mocks'tur.
Bu görevde, gerçek veri kaynaklarından DefaultTasksRepository ayrılmış birim testi için FakeDataSource test çifti oluşturacaksınız.
1. adım: FakeDataSource sınıfını oluşturun
Bu adımda, LocalDataSource ve RemoteDataSource öğesinin test çifti olacak FakeDataSouce adlı bir sınıf oluşturacaksınız.
- Test kaynak kümesinde, sağ tıklayıp Yeni -> Paket'i seçin.

- İçinde kaynak paketi bulunan bir veri paketi oluşturun.
- data/source paketinde
FakeDataSourceadlı yeni bir sınıf oluşturun.

2. adım: TasksDataSource arayüzünü uygulayın
Yeni sınıfınızı FakeDataSource test çifti olarak kullanabilmek için diğer veri kaynaklarının yerine geçebilmesi gerekir. Bu veri kaynakları TasksLocalDataSource ve TasksRemoteDataSource'dır.

- Bu ikisinin de
TasksDataSourcearayüzünü nasıl uyguladığına dikkat edin.
class TasksLocalDataSource internal constructor(
private val tasksDao: TasksDao,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksDataSource { ... }
object TasksRemoteDataSource : TasksDataSource { ... }FakeDataSource,TasksDataSource'i uygulasın:
class FakeDataSource : TasksDataSource {
}Android Studio, TasksDataSource için gerekli yöntemleri uygulamadığınızdan şikayet edecektir.
- Hızlı düzeltme menüsünü kullanın ve Üyeleri uygula'yı seçin.

- Tüm yöntemleri seçip Tamam'a basın.

3. adım: getTasks yöntemini FakeDataSource'ta uygulayın
FakeDataSource, sahte adı verilen belirli bir test çifti türüdür. Sahte, sınıfın "çalışan" bir uygulamasına sahip olan ancak testler için uygun, üretim için uygun olmayan bir şekilde uygulanan bir test dublörüdür. "Çalışan" uygulama, sınıfın girişlere göre gerçekçi çıkışlar üreteceği anlamına gelir.
Örneğin, sahte veri kaynağınız ağa bağlanmaz veya bir veritabanına hiçbir şey kaydetmez. Bunun yerine yalnızca bellek içi bir liste kullanır. Bu, görevleri alma veya kaydetme yöntemlerinin beklenen sonuçları döndürmesi açısından "beklediğiniz gibi çalışır" ancak sunucuya veya veritabanına kaydedilmediği için bu uygulamayı üretimde hiçbir zaman kullanamazsınız.
FakeDataSource
- gerçek bir veritabanına veya ağa güvenmenize gerek kalmadan
DefaultTasksRepositoryiçindeki kodu test etmenizi sağlar. - testler için "yeterince gerçekçi" bir uygulama sağlar.
FakeDataSourceoluşturucusunu, boş değiştirilebilir liste varsayılan değerine sahip birMutableList<Task>?olantasksadlı birvaroluşturacak şekilde değiştirin.
class FakeDataSource(var tasks: MutableList<Task>? = mutableListOf()) : TasksDataSource { // Rest of class }
Bu, veritabanı veya sunucu yanıtı gibi davranan görevlerin listesidir. Şimdilik hedef, deponun getTasks yöntemini test etmektir. Bu işlem, veri kaynağının getTasks, deleteAllTasks ve saveTask yöntemlerini çağırır.
Bu yöntemlerin sahte bir versiyonunu yaz:
getTasksyazın:tasks,nulldeğilseSuccesssonucunu döndürün.tasksdeğerinulliseErrorsonucunu döndür.- Yazma
deleteAllTasks: Değiştirilebilir görevler listesini temizler. - Yazma
saveTask: Görevi listeye ekler.
FakeDataSource için uygulanan bu yöntemler aşağıdaki koda benzer.
override suspend fun getTasks(): Result<List<Task>> {
tasks?.let { return Success(ArrayList(it)) }
return Error(
Exception("Tasks not found")
)
}
override suspend fun deleteAllTasks() {
tasks?.clear()
}
override suspend fun saveTask(task: Task) {
tasks?.add(task)
}Gerekirse içe aktarma ifadeleri şunlardır:
import com.example.android.architecture.blueprints.todoapp.data.Result
import com.example.android.architecture.blueprints.todoapp.data.Result.Error
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.TaskBu, gerçek yerel ve uzak veri kaynaklarının işleyiş şekline benzer.
Bu adımda, az önce oluşturduğunuz sahte test çiftini kullanabilmek için manuel bağımlılık yerleştirme adı verilen bir teknik kullanacaksınız.
Asıl sorun, FakeDataSource olması ancak testlerde nasıl kullandığınızın net olmamasıdır. Yalnızca testlerde TasksRemoteDataSource ve TasksLocalDataSource yerine geçmesi gerekir. Hem TasksRemoteDataSource hem de TasksLocalDataSource, DefaultTasksRepository'nin bağımlılıklarıdır. Bu, DefaultTasksRepositories'nin çalışması için bu sınıfların gerekli olduğu veya bu sınıflara "bağlı" olduğu anlamına gelir.
Şu anda bağımlılıklar, DefaultTasksRepository sınıfının init yöntemi içinde oluşturuluyor.
DefaultTasksRepository.kt
class DefaultTasksRepository private constructor(application: Application) {
private val tasksRemoteDataSource: TasksDataSource
private val tasksLocalDataSource: TasksDataSource
// Some other code
init {
val database = Room.databaseBuilder(application.applicationContext,
ToDoDatabase::class.java, "Tasks.db")
.build()
tasksRemoteDataSource = TasksRemoteDataSource
tasksLocalDataSource = TasksLocalDataSource(database.taskDao())
}
// Rest of class
}taskLocalDataSource ve tasksRemoteDataSource öğelerini DefaultTasksRepository içinde oluşturup atadığınız için bunlar esasen sabit kodlanmış olur. Test çiftinizi kullanmanın bir yolu yoktur.
Bunun yerine, bu veri kaynaklarını sabit kodlamak yerine sınıfa sağlamanız gerekir. Bağımlılık sağlama işlemine bağımlılık yerleştirme adı verilir. Bağımlılıkları sağlamanın farklı yolları vardır. Bu nedenle, farklı türlerde bağımlılık yerleştirme vardır.
Oluşturucu Bağımlılık Ekleme, oluşturucuya ileterek test çiftini değiştirmenize olanak tanır.
Enjeksiyon yok
| Ekleme (Injection)
|
1. adım: DefaultTasksRepository'de Constructor Dependency Injection'ı kullanın
DefaultTaskRepositoryoluşturucusunu,Applicationyerine hem veri kaynaklarını hem de coroutine dağıtıcısını alacak şekilde değiştirin (testleriniz için de değiştirmeniz gerekir. Bu, coroutine'lerle ilgili üçüncü ders bölümünde daha ayrıntılı olarak açıklanmıştır).
DefaultTasksRepository.kt
// REPLACE
class DefaultTasksRepository private constructor(application: Application) { // Rest of class }
// WITH
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) { // Rest of class }- Bağımlılıkları ilettiğiniz için
inityöntemini kaldırın. Artık bağımlılık oluşturmanız gerekmez. - Eski örnek değişkenlerini de silin. Bunları oluşturucuda tanımlıyorsanız:
DefaultTasksRepository.kt
// Delete these old variables
private val tasksRemoteDataSource: TasksDataSource
private val tasksLocalDataSource: TasksDataSource
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO- Son olarak, yeni oluşturucuyu kullanmak için
getRepositoryyöntemini güncelleyin:
DefaultTasksRepository.kt
companion object {
@Volatile
private var INSTANCE: DefaultTasksRepository? = null
fun getRepository(app: Application): DefaultTasksRepository {
return INSTANCE ?: synchronized(this) {
val database = Room.databaseBuilder(app,
ToDoDatabase::class.java, "Tasks.db")
.build()
DefaultTasksRepository(TasksRemoteDataSource, TasksLocalDataSource(database.taskDao())).also {
INSTANCE = it
}
}
}
}Artık oluşturucu bağımlılığı eklemeyi kullanıyorsunuz.
2. adım: Testlerinizde FakeDataSource'u kullanın
Kodunuz artık oluşturucu bağımlılığı eklemeyi kullandığına göre, DefaultTasksRepository öğenizi test etmek için sahte veri kaynağınızı kullanabilirsiniz.
DefaultTasksRepositorysınıf adını sağ tıklayın ve Oluştur'u, ardından Test'i seçin.- Test kaynak grubunda
DefaultTasksRepositoryTestoluşturmak için talimatları uygulayın. - Yeni
DefaultTasksRepositoryTestsınıfınızın en üstüne, sahte veri kaynaklarınızdaki verileri temsil etmek için aşağıdaki üye değişkenlerini ekleyin.
DefaultTasksRepositoryTest.kt
private val task1 = Task("Title1", "Description1")
private val task2 = Task("Title2", "Description2")
private val task3 = Task("Title3", "Description3")
private val remoteTasks = listOf(task1, task2).sortedBy { it.id }
private val localTasks = listOf(task3).sortedBy { it.id }
private val newTasks = listOf(task3).sortedBy { it.id }- Üç değişken oluşturun: iki
FakeDataSourceüye değişkeni (deponuzdaki her veri kaynağı için bir tane) ve test edeceğinizDefaultTasksRepositoryiçin bir değişken.
DefaultTasksRepositoryTest.kt
private lateinit var tasksRemoteDataSource: FakeDataSource
private lateinit var tasksLocalDataSource: FakeDataSource
// Class under test
private lateinit var tasksRepository: DefaultTasksRepositoryTest edilebilir bir DefaultTasksRepository oluşturup başlatmak için bir yöntem oluşturun. Bu DefaultTasksRepository, test dublörünüz FakeDataSource'ü kullanacak.
createRepositoryadlı bir yöntem oluşturun ve bunu@Beforeile açıklama ekleyin.remoteTasksvelocalTaskslistelerini kullanarak sahte veri kaynaklarınızı oluşturun.- Yeni oluşturduğunuz iki sahte veri kaynağını ve
Dispatchers.UnconfinedkullanaraktasksRepositoryöğenizi oluşturun.
Nihai yöntem aşağıdaki koda benzemelidir.
DefaultTasksRepositoryTest.kt
@Before
fun createRepository() {
tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
// Get a reference to the class under test
tasksRepository = DefaultTasksRepository(
// TODO Dispatchers.Unconfined should be replaced with Dispatchers.Main
// this requires understanding more about coroutines + testing
// so we will keep this as Unconfined for now.
tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Unconfined
)
}3. adım: DefaultTasksRepository getTasks() testini yazın
DefaultTasksRepositoryTest yazma zamanı!
- Deponun
getTasksyöntemi için bir test yazın.getTasksişlevinitrueile çağırdığınızda (yani uzak veri kaynağından yeniden yüklenmesi gerektiğinde) yerel veri kaynağı yerine uzak veri kaynağındaki verileri döndürdüğünü kontrol edin.
DefaultTasksRepositoryTest.kt
@Test
fun getTasks_requestsAllTasksFromRemoteDataSource(){
// When tasks are requested from the tasks repository
val tasks = tasksRepository.getTasks(true) as Success
// Then tasks are loaded from the remote data source
assertThat(tasks.data, IsEqual(remoteTasks))
}getTasks: numarasını aradığınızda hata alırsınız
4. adım: runBlockingTest işlevini ekleyin
getTasks, suspend işlevi olduğundan ve bunu çağırmak için bir ortak yordam başlatmanız gerektiğinden ortak yordam hatası beklenir. Bunun için bir coroutine kapsamına ihtiyacınız vardır. Bu hatayı düzeltmek için testlerinizde başlatma eşzamanlılıklarını işlemek üzere bazı gradle bağımlılıkları eklemeniz gerekir.
testImplementationkullanarak, test kaynak grubuna eşzamanlı rutinleri test etmek için gereken bağımlılıkları ekleyin.
app/build.gradle
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"Senkronize etmeyi unutmayın.
kotlinx-coroutines-test, özellikle coroutine'leri test etmek için tasarlanmış coroutine test kitaplığıdır. Testlerinizi çalıştırmak için runBlockingTest işlevini kullanın. Bu, coroutines test kitaplığı tarafından sağlanan bir işlevdir. Bir kod bloğunu alır ve bu kod bloğunu eşzamanlı ve anında çalışan özel bir eş yordam bağlamında çalıştırır. Bu nedenle, işlemler belirlenmiş bir sırada gerçekleşir. Bu, temel olarak eş yordamlarınızın eş yordam olmayanlar gibi çalışmasını sağlar. Bu nedenle, kod testi için tasarlanmıştır.
suspend işlevini çağırırken test sınıflarınızda runBlockingTest kullanın. runBlockingTest işleyiş şekli ve eş yordamların nasıl test edileceği hakkında daha fazla bilgiyi bu serinin bir sonraki codelab'inde edineceksiniz.
- Sınıfın üstüne
@ExperimentalCoroutinesApiekleyin. Bu, sınıfta deneysel bir coroutine API'si (runBlockingTest) kullandığınızı bildiğinizi ifade eder. Aksi takdirde uyarı alırsınız. DefaultTasksRepositoryTestbölümüne geri dönün verunBlockingTestekleyerek testinizin tamamını bir kod "bloğu" olarak alın.
Bu son test, aşağıdaki koda benzer.
DefaultTasksRepositoryTest.kt
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.core.IsEqual
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
@ExperimentalCoroutinesApi
class DefaultTasksRepositoryTest {
private val task1 = Task("Title1", "Description1")
private val task2 = Task("Title2", "Description2")
private val task3 = Task("Title3", "Description3")
private val remoteTasks = listOf(task1, task2).sortedBy { it.id }
private val localTasks = listOf(task3).sortedBy { it.id }
private val newTasks = listOf(task3).sortedBy { it.id }
private lateinit var tasksRemoteDataSource: FakeDataSource
private lateinit var tasksLocalDataSource: FakeDataSource
// Class under test
private lateinit var tasksRepository: DefaultTasksRepository
@Before
fun createRepository() {
tasksRemoteDataSource = FakeDataSource(remoteTasks.toMutableList())
tasksLocalDataSource = FakeDataSource(localTasks.toMutableList())
// Get a reference to the class under test
tasksRepository = DefaultTasksRepository(
// TODO Dispatchers.Unconfined should be replaced with Dispatchers.Main
// this requires understanding more about coroutines + testing
// so we will keep this as Unconfined for now.
tasksRemoteDataSource, tasksLocalDataSource, Dispatchers.Unconfined
)
}
@Test
fun getTasks_requestsAllTasksFromRemoteDataSource() = runBlockingTest {
// When tasks are requested from the tasks repository
val tasks = tasksRepository.getTasks(true) as Success
// Then tasks are loaded from the remote data source
assertThat(tasks.data, IsEqual(remoteTasks))
}
}- Yeni
getTasks_requestsAllTasksFromRemoteDataSourcetestinizi çalıştırın ve hatanın giderildiğini doğrulayın.
Bir depoyu birim testine tabi tutmanın nasıl yapılacağını öğrendiniz. Bu sonraki adımlarda, bağımlılık eklemeyi tekrar kullanacak ve başka bir test çifti oluşturacaksınız. Bu kez, görünüm modelleriniz için birim ve entegrasyon testlerinin nasıl yazılacağını göstereceksiniz.
Birim testleri yalnızca ilgilendiğiniz sınıfı veya yöntemi test etmelidir. Bu, izolasyon içinde test olarak bilinir. Burada "biriminizi" net bir şekilde izole eder ve yalnızca bu birimin parçası olan kodu test edersiniz.
Bu nedenle, TasksViewModelTest yalnızca TasksViewModel kodunu test etmeli, veritabanında, ağda veya depodaki sınıflarda test yapmamalıdır. Bu nedenle, görünüm modelleriniz için de tıpkı deponuzda yaptığınız gibi sahte bir depo oluşturacak ve testlerinizde kullanmak üzere bağımlılık ekleme uygulayacaksınız.
Bu görevde, bağımlılık eklemeyi görünüm modellerine uygulayacaksınız.

1. Adım: TasksRepository arayüzü oluşturma
Oluşturucu bağımlılığı eklemeyi kullanmaya yönelik ilk adım, sahte ve gerçek sınıf arasında paylaşılan ortak bir arayüz oluşturmaktır.
Bu özellik pratikte nasıl görünüyor? TasksRemoteDataSource, TasksLocalDataSource ve FakeDataSource'ye bakın. Hepsinin aynı arayüzü paylaştığını göreceksiniz: TasksDataSource. Bu sayede, DefaultTasksRepository oluşturucusunda TasksDataSource aldığınızı belirtebilirsiniz.
DefaultTasksRepository.kt
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {Bu sayede FakeDataSource öğenizi değiştirebiliriz.
Ardından, veri kaynakları için yaptığınız gibi DefaultTasksRepository için bir arayüz oluşturun. DefaultTasksRepository'nın tüm herkese açık yöntemlerini (herkese açık API yüzeyi) içermelidir.
DefaultTasksRepositorysimgesini açın ve sınıf adını sağ tıklayın. Ardından Refactor -> Extract -> Interface'i (Yeniden düzenle -> Çıkar -> Arayüz) seçin.

- Ayrı dosyaya çıkar'ı seçin.

- Extract Interface (Arayüzü Çıkar) penceresinde arayüz adını
TasksRepositoryolarak değiştirin. - Arayüz oluşturacak üyeler bölümünde, iki tamamlayıcı üye ve özel yöntemler hariç tüm üyeleri işaretleyin.

- Yeniden düzenle'yi tıklayın. Yeni
TasksRepositoryarayüzü, data/source paketinde görünmelidir.

Ve DefaultTasksRepository artık TasksRepository'ı uyguluyor.
- Her şeyin hâlâ düzgün çalıştığından emin olmak için uygulamanızı (testleri değil) çalıştırın.
2. adım: Create FakeTestRepository
Arayüzü oluşturduğunuza göre artık DefaultTaskRepository test çiftini oluşturabilirsiniz.
- Test kaynak kümesinde, data/source içinde Kotlin dosyasını ve sınıfını
FakeTestRepository.ktoluşturun veTasksRepositoryarayüzünden genişletin.
FakeTestRepository.kt
class FakeTestRepository : TasksRepository {
}Arayüz yöntemlerini uygulamanız gerektiği belirtilir.
- Öneri menüsünü görene kadar imleci hatanın üzerinde tutun, ardından Üyeleri uygula'yı tıklayıp seçin.
- Tüm yöntemleri seçip Tamam'a basın.

3. Adım: FakeTestRepository yöntemlerini uygulama
Artık "uygulanmadı" yöntemleri içeren bir FakeTestRepository sınıfınız var. FakeDataSource'yı uyguladığınız yönteme benzer şekilde, yerel ve uzak veri kaynakları arasında karmaşık bir arabuluculukla uğraşmak yerine FakeTestRepository, bir veri yapısıyla desteklenir.
FakeTestRepository'nızın FakeDataSource veya benzeri bir şey kullanması gerekmediğini unutmayın. Yalnızca girişlere göre gerçekçi sahte çıkışlar döndürmesi gerekir. Görev listesini depolamak için LinkedHashMap, gözlemlenebilir görevleriniz için ise MutableLiveData kullanacaksınız.
FakeTestRepositoryiçinde hem mevcut görev listesini temsil eden birLinkedHashMapdeğişkeni hem de gözlemlenebilir görevleriniz için birMutableLiveDataekleyin.
FakeTestRepository.kt
class FakeTestRepository : TasksRepository {
var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()
private val observableTasks = MutableLiveData<Result<List<Task>>>()
// Rest of class
}Aşağıdaki yöntemleri uygulayın:
getTasks—Bu yöntem,tasksServiceDataöğesini alıptasksServiceData.values.toList()kullanarak liste haline getirmeli ve bunuSuccesssonucu olarak döndürmelidir.refreshTasks—observableTasksdeğerini,getTasks()tarafından döndürülen değerle günceller.observeTasks—runBlockingkullanarak bir eş yordam oluşturur verefreshTasks'ı çalıştırır, ardındanobservableTasks'ı döndürür.
Bu yöntemlerin kodu aşağıda verilmiştir.
FakeTestRepository.kt
class FakeTestRepository : TasksRepository {
var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()
private val observableTasks = MutableLiveData<Result<List<Task>>>()
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
return Result.Success(tasksServiceData.values.toList())
}
override suspend fun refreshTasks() {
observableTasks.value = getTasks()
}
override fun observeTasks(): LiveData<Result<List<Task>>> {
runBlocking { refreshTasks() }
return observableTasks
}
// Rest of class
}4. Adım. addTasks'e test için bir yöntem ekleyin
Test yaparken, deponuzda önceden bazı Tasks olması daha iyidir. saveTask işlevini birkaç kez çağırabilirsiniz ancak bunu kolaylaştırmak için özellikle görev eklemenize olanak tanıyan testler için bir yardımcı yöntem ekleyin.
- Bir
vararggörev alan, her biriniHashMap'ye ekleyen ve ardından görevleri yenileyenaddTasksyöntemini ekleyin.
FakeTestRepository.kt
fun addTasks(vararg tasks: Task) {
for (task in tasks) {
tasksServiceData[task.id] = task
}
runBlocking { refreshTasks() }
}Bu noktada, temel yöntemlerden birkaçı uygulanmış şekilde test için sahte bir deponuz var. Ardından, bunu testlerinizde kullanın.
Bu görevde, ViewModel içinde sahte bir sınıf kullanıyorsunuz. TasksRepository değişkenini TasksViewModel oluşturucusuna ekleyerek iki veri kaynağını oluşturucu bağımlılık ekleme yoluyla almak için oluşturucu bağımlılık eklemeyi kullanın.
Bu işlem, doğrudan oluşturmadığınız için görünüm modellerinde biraz farklıdır. Örneğin:
class TasksFragment : Fragment() {
private val viewModel by viewModels<TasksViewModel>()
// Rest of class...
}
Yukarıdaki kodda olduğu gibi, görünüm modelini oluşturan viewModel's property delegate'i kullanıyorsunuz. Görünüm modelinin oluşturulma şeklini değiştirmek için ViewModelProvider.Factory ekleyip kullanmanız gerekir. ViewModelProvider.Factory hakkında daha fazla bilgi edinmek için burayı ziyaret edebilirsiniz.
1. Adım: TasksViewModel'de ViewModelFactory oluşturma ve kullanma
Tasks ekranıyla ilgili sınıfları ve testi güncelleyerek işe başlayın.
- Aç 'ı
TasksViewModeltıklayın. TasksViewModeloluşturucusunu, sınıfın içinde oluşturmak yerineTasksRepositoryalacak şekilde değiştirin.
TasksViewModel.kt
// REPLACE
class TasksViewModel(application: Application) : AndroidViewModel(application) {
private val tasksRepository = DefaultTasksRepository.getRepository(application)
// Rest of class
}
// WITH
class TasksViewModel( private val tasksRepository: TasksRepository ) : ViewModel() {
// Rest of class
}Oluşturucuyu değiştirdiğiniz için artık TasksViewModel oluşturmak üzere bir fabrika kullanmanız gerekiyor. Fabrika sınıfını TasksViewModel ile aynı dosyaya yerleştirin. Ancak kendi dosyasına da yerleştirebilirsiniz.
TasksViewModeldosyasının en altında, sınıfın dışında, düz birTasksRepositoryalan birTasksViewModelFactoryekleyin.
TasksViewModel.kt
@Suppress("UNCHECKED_CAST")
class TasksViewModelFactory (
private val tasksRepository: TasksRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>) =
(TasksViewModel(tasksRepository) as T)
}
Bu, ViewModel öğelerinin oluşturulma şeklini değiştirmek için kullanılan standart yöntemdir. Artık fabrikanız olduğuna göre, görünüm modelinizi oluşturduğunuz her yerde kullanabilirsiniz.
- Fabrikayı kullanmak için
TasksFragmentuygulamasını güncelleyin.
TasksFragment.kt
// REPLACE
private val viewModel by viewModels<TasksViewModel>()
// WITH
private val viewModel by viewModels<TasksViewModel> {
TasksViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}- Uygulama kodunuzu çalıştırın ve her şeyin çalışmaya devam ettiğinden emin olun.
2. adım: TasksViewModelTest içinde FakeTestRepository'yi kullanma
Artık görünüm modeli testlerinizde gerçek depo yerine sahte depoyu kullanabilirsiniz.
- Aç
TasksViewModelTest. TasksViewModelTestürünündekiFakeTestRepositorymülkünü ekleyin.
TaskViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Use a fake repository to be injected into the viewmodel
private lateinit var tasksRepository: FakeTestRepository
// Rest of class
}- Üç görev içeren bir
FakeTestRepositoryoluşturmak içinsetupViewModelyöntemini güncelleyin ve ardından bu depoylatasksViewModel'ı oluşturun.
TasksViewModelTest.kt
@Before
fun setupViewModel() {
// We initialise the tasks to 3, with one active and two completed
tasksRepository = FakeTestRepository()
val task1 = Task("Title1", "Description1")
val task2 = Task("Title2", "Description2", true)
val task3 = Task("Title3", "Description3", true)
tasksRepository.addTasks(task1, task2, task3)
tasksViewModel = TasksViewModel(tasksRepository)
}- Artık AndroidX Test
ApplicationProvider.getApplicationContextkodunu kullanmadığınız için@RunWith(AndroidJUnit4::class)ek açıklamasını da kaldırabilirsiniz. - Testlerinizi çalıştırın ve hepsinin hâlâ çalıştığından emin olun.
Oluşturucu bağımlılık eklemeyi kullanarak artık DefaultTasksRepository bağımlılığını kaldırdınız ve testlerde FakeTestRepository ile değiştirdiniz.
3. Adım: Ayrıca TaskDetail Fragment ve ViewModel'i de güncelleyin.
TaskDetailFragment ve TaskDetailViewModel için aynı değişiklikleri yapın. Bu işlem, bir sonraki TaskDetail testlerini yazarken kodu hazırlayacaktır.
- Aç 'ı
TaskDetailViewModeltıklayın. - Oluşturucuyu güncelleyin:
TaskDetailViewModel.kt
// REPLACE
class TaskDetailViewModel(application: Application) : AndroidViewModel(application) {
private val tasksRepository = DefaultTasksRepository.getRepository(application)
// Rest of class
}
// WITH
class TaskDetailViewModel(
private val tasksRepository: TasksRepository
) : ViewModel() { // Rest of class }TaskDetailViewModeldosyasının en altına, sınıfın dışına birTaskDetailViewModelFactoryekleyin.
TaskDetailViewModel.kt
@Suppress("UNCHECKED_CAST")
class TaskDetailViewModelFactory (
private val tasksRepository: TasksRepository
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>) =
(TaskDetailViewModel(tasksRepository) as T)
}- Fabrikayı kullanmak için
TasksFragmentuygulamasını güncelleyin.
TasksFragment.kt
// REPLACE
private val viewModel by viewModels<TaskDetailViewModel>()
// WITH
private val viewModel by viewModels<TaskDetailViewModel> {
TaskDetailViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}- Kodunuzu çalıştırın ve her şeyin çalıştığından emin olun.
Artık TasksFragment ve TasksDetailFragment'de gerçek depo yerine FakeTestRepository kullanabilirsiniz.
Ardından, parçacık ve görünüm modeli etkileşimlerinizi test etmek için entegrasyon testleri yazacaksınız. Görünüm modeli kodunuzun kullanıcı arayüzünüzü uygun şekilde güncelleyip güncellemediğini öğreneceksiniz. Bunun için
- ServiceLocator modeli
- Espresso ve Mockito kitaplıkları
Entegrasyon testleri , birlikte kullanıldıklarında beklendiği gibi davrandıklarından emin olmak için çeşitli sınıfların etkileşimini test eder. Bu testler yerel olarak (test kaynak grubu) veya enstrümantasyon testleri (androidTest kaynak grubu) olarak çalıştırılabilir.

Bu durumda, her bir parçayı alıp parçanın ana özelliklerini test etmek için parça ve görünüm modeliyle ilgili entegrasyon testleri yazacaksınız.
1. Adım: Gradle bağımlılıkları ekleme
- Aşağıdaki Gradle bağımlılıklarını ekleyin.
app/build.gradle
// Dependencies for Android instrumented unit tests
androidTestImplementation "junit:junit:$junitVersion"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
// Testing code should not be included in the main code.
// Once https://issuetracker.google.com/128612536 is fixed this can be fixed.
implementation "androidx.fragment:fragment-testing:$fragmentVersion"
implementation "androidx.test:core:$androidXTestCoreVersion"
Bu bağımlılıklar şunlardır:
junit:junit—Temel test ifadeleri yazmak için gerekli olan JUnit.androidx.test:core—Core AndroidX test librarykotlinx-coroutines-test—Eş yordam testi kitaplığıandroidx.fragment:fragment-testing—Testlerde parça oluşturmak ve durumlarını değiştirmek için AndroidX test kitaplığı.
Bu kitaplıkları androidTest kaynak kümenizde kullanacağınız için bunları bağımlılık olarak eklemek üzere androidTestImplementation kullanın.
2. adım: TaskDetailFragmentTest sınıfı oluşturun.
TaskDetailFragment, tek bir görevle ilgili bilgileri gösterir.

Diğer parçalara kıyasla oldukça temel işlevlere sahip olduğundan TaskDetailFragment için bir parça testi yazarak başlayacaksınız.
- Aç 'ı
taskdetail.TaskDetailFragmenttıklayın. - Daha önce yaptığınız gibi
TaskDetailFragmentiçin bir test oluşturun. Varsayılan seçimleri kabul edin ve androidTest kaynak kümesine (testkaynak kümesine DEĞİL) yerleştirin.

TaskDetailFragmentTestsınıfına aşağıdaki notları ekleyin.
TaskDetailFragmentTest.kt
@MediumTest
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {
}Bu ek açıklamaların amacı şudur:
@MediumTest: Testi,@SmallTestbirim testleri ve@LargeTestbüyük uçtan uca testlere kıyasla "orta çalışma süreli" bir entegrasyon testi olarak işaretler. Bu, testleri gruplandırmanıza ve hangi boyutta test yapacağınızı seçmenize yardımcı olur.@RunWith(AndroidJUnit4::class): AndroidX Test'in kullanıldığı tüm sınıflarda kullanılır.
3. Adım: Testten parça başlatma
Bu görevde, AndroidX Testing kitaplığını kullanarak TaskDetailFragment başlatacaksınız. FragmentScenario, bir parçayı sarmalayan ve test için parçanın yaşam döngüsü üzerinde doğrudan kontrol sağlayan bir AndroidX Test sınıfıdır. Parçalar için test yazmak üzere, test ettiğiniz parça için bir FragmentScenario oluşturursunuz (TaskDetailFragment).
- Bu testi kopyalayıp
TaskDetailFragmentTestiçine yapıştırın.
TaskDetailFragmentTest.kt
@Test
fun activeTaskDetails_DisplayedInUi() {
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
}
Yukarıdaki kod:
- Görev oluşturur.
- Görev için parça bağımsız değişkenlerini temsil eden bir
Bundleoluşturur (parçaya iletilir). launchFragmentInContainerişlevi, bu paket ve bir tema ileFragmentScenariooluşturur.
Bu test henüz tamamlanmamıştır, çünkü herhangi bir iddiada bulunmamaktadır. Şimdilik testi çalıştırın ve neler olduğunu gözlemleyin.
- Bu, enstrümanlı bir testtir. Bu nedenle, emülatörün veya cihazınızın görünür olduğundan emin olun.
- Testi çalıştırın.
Birkaç şey gerçekleşir.
- Öncelikle, bu bir enstrümanlı test olduğundan test, fiziksel cihazınızda (bağlıysa) veya bir emülatörde çalışır.
- Parçayı başlatması gerekir.
- Başka bir parçaya gitmediğine veya etkinlikle ilişkili menüleri olmadığına dikkat edin. Bu, yalnızca parçadır.
Son olarak, yakından bakın ve görev verileri başarıyla yüklenmediği için parçanın "Veri yok" yazdığını fark edin.

Testinizin hem TaskDetailFragment öğesini yüklemesi (bunu yaptınız) hem de verilerin doğru şekilde yüklendiğini onaylaması gerekir. Neden veri yok? Bunun nedeni, bir görev oluşturmuş olmanız ancak bu görevi depoya kaydetmemiş olmanızdır.
@Test
fun activeTaskDetails_DisplayedInUi() {
// This DOES NOT save the task anywhere
val activeTask = Task("Active Task", "AndroidX Rocks", false)
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
}
FakeTestRepository öğesine sahipsiniz ancak parçanız için gerçek deponuzu sahte olanla değiştirmenin bir yolunu bulmanız gerekiyor. Ardından, bu adımı uygularsınız.
Bu görevde, ServiceLocator kullanarak sahte deponuzu parçanıza sağlayacaksınız. Bu sayede parçanızı ve görünüm modeli entegrasyon testlerinizi yazabilirsiniz.
Daha önce, görünüm modeline veya depoya bağımlılık sağlamanız gerektiğinde yaptığınız gibi burada oluşturucu bağımlılık eklemeyi kullanamazsınız. Oluşturucu bağımlılığı ekleme, sınıfı oluşturmanızı gerektirir. Parçalar ve etkinlikler, oluşturmadığınız ve genellikle oluşturucusuna erişemediğiniz sınıflara örneklerdir.
Parçayı oluşturmadığınız için, oluşturucu bağımlılığı ekleme yöntemini kullanarak depo test çiftini (FakeTestRepository) parçayla değiştiremezsiniz. Bunun yerine hizmet bulucu modelini kullanın. Hizmet Bulucu kalıbı, Bağımlılık Ekleme'ye alternatif bir yöntemdir. Bu, hem normal hem de test kodu için bağımlılıklar sağlamak amacıyla "Hizmet Bulucu" adlı tekil bir sınıf oluşturmayı içerir. Normal uygulama kodunda (main kaynak grubu), bu bağımlılıkların tümü normal uygulama bağımlılıklarıdır. Testler için, bağımlılıkların test çifti sürümlerini sağlamak üzere hizmet bulucuyu değiştirirsiniz.
Servis Bulucu'yu kullanmıyorsanız
| Servis Bulucu'yu kullanma
|
Bu codelab uygulamasında şunları yapın:
- Bir depoyu oluşturup saklayabilen bir hizmet bulma sınıfı oluşturun. Varsayılan olarak "normal" bir depo oluşturur.
- Kodunuzu, bir depoya ihtiyacınız olduğunda hizmet bulucuyu kullanacak şekilde yeniden düzenleyin.
- Test sınıfınızda, "normal" depoyu test çiftinizle değiştiren Service Locator üzerinde bir yöntem çağırın.
1. Adım: ServiceLocator'ı oluşturun
ServiceLocator sınıfı oluşturalım. Ana uygulama kodu tarafından kullanıldığından, uygulamanın geri kalan kodlarıyla birlikte ana kaynak kümesinde yer alır.
Not: ServiceLocator tekil bir öğe olduğundan sınıf için Kotlin object anahtar kelimesini kullanın.
- Ana kaynak kümesinin en üst düzeyinde ServiceLocator.kt dosyasını oluşturun.
ServiceLocatoradlı birobjecttanımlayın.databaseverepositoryörnek değişkenleri oluşturun ve her ikisini denullolarak ayarlayın.- Depoyu
@Volatileile açıklama ekleyin. Çünkü bu depo birden fazla iş parçacığı tarafından kullanılabilir (@Volatileile ilgili ayrıntılı açıklamayı burada bulabilirsiniz).
Kodunuz aşağıdaki gibi görünmelidir.
object ServiceLocator {
private var database: ToDoDatabase? = null
@Volatile
var tasksRepository: TasksRepository? = null
}Şu anda ServiceLocator'nın yapması gereken tek şey TasksRepository döndürmeyi bilmektir. Önceden var olan bir DefaultTasksRepository döndürülür veya gerekirse yeni bir DefaultTasksRepository oluşturulup döndürülür.
Aşağıdaki işlevleri tanımlayın:
provideTasksRepository: Mevcut bir depoyu sağlar veya yeni bir depo oluşturur. Birden fazla iş parçacığının çalıştığı durumlarda yanlışlıkla iki depo örneği oluşturmamak için bu yöntemsynchronizedaçıkthisolmalıdır.createTasksRepository: Yeni bir depo oluşturma kodu.createTaskLocalDataSourceişlevini çağırıp yeni birTasksRemoteDataSourceoluşturur.createTaskLocalDataSource—Yeni bir yerel veri kaynağı oluşturma kodu.createDataBasearanacak.createDataBase: Yeni bir veritabanı oluşturma kodu.
Tamamlanmış kod aşağıda verilmiştir.
ServiceLocator.kt
object ServiceLocator {
private var database: ToDoDatabase? = null
@Volatile
var tasksRepository: TasksRepository? = null
fun provideTasksRepository(context: Context): TasksRepository {
synchronized(this) {
return tasksRepository ?: createTasksRepository(context)
}
}
private fun createTasksRepository(context: Context): TasksRepository {
val newRepo = DefaultTasksRepository(TasksRemoteDataSource, createTaskLocalDataSource(context))
tasksRepository = newRepo
return newRepo
}
private fun createTaskLocalDataSource(context: Context): TasksDataSource {
val database = database ?: createDataBase(context)
return TasksLocalDataSource(database.taskDao())
}
private fun createDataBase(context: Context): ToDoDatabase {
val result = Room.databaseBuilder(
context.applicationContext,
ToDoDatabase::class.java, "Tasks.db"
).build()
database = result
return result
}
}2. adım: Uygulamada ServiceLocator'ı kullanma
Depoyu tek bir yerde, ServiceLocator konumunda oluşturmak için ana uygulama kodunuzda (testlerinizde değil) bir değişiklik yapacaksınız.
Depo sınıfının yalnızca bir örneğini oluşturmanız önemlidir. Bunu sağlamak için Uygulama sınıfımda hizmet bulucuyu kullanacaksınız.
- Paket hiyerarşinizin en üst düzeyinde
TodoApplicationdosyasını açın, deponuz için birvaloluşturun veServiceLocator.provideTaskRepositorykullanılarak elde edilen bir depo atayın.
TodoApplication.kt
class TodoApplication : Application() {
val taskRepository: TasksRepository
get() = ServiceLocator.provideTasksRepository(this)
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(DebugTree())
}
}
Uygulamada bir depo oluşturduğunuza göre artık DefaultTasksRepository içindeki eski getRepository yöntemini kaldırabilirsiniz.
DefaultTasksRepositorysimgesini açın ve tamamlayıcı nesneyi silin.
DefaultTasksRepository.kt
// DELETE THIS COMPANION OBJECT
companion object {
@Volatile
private var INSTANCE: DefaultTasksRepository? = null
fun getRepository(app: Application): DefaultTasksRepository {
return INSTANCE ?: synchronized(this) {
val database = Room.databaseBuilder(app,
ToDoDatabase::class.java, "Tasks.db")
.build()
DefaultTasksRepository(TasksRemoteDataSource, TasksLocalDataSource(database.taskDao())).also {
INSTANCE = it
}
}
}
}Artık getRepository kullandığınız her yerde uygulamanın taskRepository özelliğini kullanın. Bu sayede, depoyu doğrudan oluşturmak yerine ServiceLocator tarafından sağlanan depoyu alırsınız.
TaskDetailFragement'ı açın ve sınıfın üst kısmındagetRepositorygörüşmesini bulun.- Bu çağrıyı,
TodoApplication'dan depoyu alan bir çağrıyla değiştirin.
TaskDetailFragment.kt
// REPLACE this code
private val viewModel by viewModels<TaskDetailViewModel> {
TaskDetailViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}
// WITH this code
private val viewModel by viewModels<TaskDetailViewModel> {
TaskDetailViewModelFactory((requireContext().applicationContext as TodoApplication).taskRepository)
}- Aynı işlemi
TasksFragmentiçin de yapın.
TasksFragment.kt
// REPLACE this code
private val viewModel by viewModels<TasksViewModel> {
TasksViewModelFactory(DefaultTasksRepository.getRepository(requireActivity().application))
}
// WITH this code
private val viewModel by viewModels<TasksViewModel> {
TasksViewModelFactory((requireContext().applicationContext as TodoApplication).taskRepository)
}StatisticsViewModelveAddEditTaskViewModeliçin, depoyu alan koduTodoApplication'deki depoyu kullanacak şekilde güncelleyin.
TasksFragment.kt
// REPLACE this code
private val tasksRepository = DefaultTasksRepository.getRepository(application)
// WITH this code
private val tasksRepository = (application as TodoApplication).taskRepository
- Uygulamanızı (testi değil) çalıştırın.
Yalnızca yeniden düzenleme yaptığınız için uygulama sorunsuz bir şekilde çalışmaya devam eder.
3. Adım: Create FakeAndroidTestRepository
Test kaynağı grubunda zaten bir FakeTestRepository var. Varsayılan olarak test ve androidTest kaynak kümeleri arasında test sınıflarını paylaşamazsınız. Bu nedenle, androidTest kaynak kümesinde FakeTestRepository sınıfının bir kopyasını oluşturmanız ve bu kopyayı FakeAndroidTestRepository olarak adlandırmanız gerekir.
androidTestkaynak kümesini sağ tıklayın ve bir veri paketi oluşturun. Tekrar sağ tıklayın ve kaynak paketi oluşturun.- Bu kaynak paketinde
FakeAndroidTestRepository.ktadlı yeni bir sınıf oluşturun. - Aşağıdaki kodu ilgili sınıfa kopyalayın.
FakeAndroidTestRepository.kt
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
import com.example.android.architecture.blueprints.todoapp.data.Result
import com.example.android.architecture.blueprints.todoapp.data.Result.Error
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
import com.example.android.architecture.blueprints.todoapp.data.Task
import kotlinx.coroutines.runBlocking
import java.util.LinkedHashMap
class FakeAndroidTestRepository : TasksRepository {
var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()
private var shouldReturnError = false
private val observableTasks = MutableLiveData<Result<List<Task>>>()
fun setReturnError(value: Boolean) {
shouldReturnError = value
}
override suspend fun refreshTasks() {
observableTasks.value = getTasks()
}
override suspend fun refreshTask(taskId: String) {
refreshTasks()
}
override fun observeTasks(): LiveData<Result<List<Task>>> {
runBlocking { refreshTasks() }
return observableTasks
}
override fun observeTask(taskId: String): LiveData<Result<Task>> {
runBlocking { refreshTasks() }
return observableTasks.map { tasks ->
when (tasks) {
is Result.Loading -> Result.Loading
is Error -> Error(tasks.exception)
is Success -> {
val task = tasks.data.firstOrNull() { it.id == taskId }
?: return@map Error(Exception("Not found"))
Success(task)
}
}
}
}
override suspend fun getTask(taskId: String, forceUpdate: Boolean): Result<Task> {
if (shouldReturnError) {
return Error(Exception("Test exception"))
}
tasksServiceData[taskId]?.let {
return Success(it)
}
return Error(Exception("Could not find task"))
}
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
if (shouldReturnError) {
return Error(Exception("Test exception"))
}
return Success(tasksServiceData.values.toList())
}
override suspend fun saveTask(task: Task) {
tasksServiceData[task.id] = task
}
override suspend fun completeTask(task: Task) {
val completedTask = Task(task.title, task.description, true, task.id)
tasksServiceData[task.id] = completedTask
}
override suspend fun completeTask(taskId: String) {
// Not required for the remote data source.
throw NotImplementedError()
}
override suspend fun activateTask(task: Task) {
val activeTask = Task(task.title, task.description, false, task.id)
tasksServiceData[task.id] = activeTask
}
override suspend fun activateTask(taskId: String) {
throw NotImplementedError()
}
override suspend fun clearCompletedTasks() {
tasksServiceData = tasksServiceData.filterValues {
!it.isCompleted
} as LinkedHashMap<String, Task>
}
override suspend fun deleteTask(taskId: String) {
tasksServiceData.remove(taskId)
refreshTasks()
}
override suspend fun deleteAllTasks() {
tasksServiceData.clear()
refreshTasks()
}
fun addTasks(vararg tasks: Task) {
for (task in tasks) {
tasksServiceData[task.id] = task
}
runBlocking { refreshTasks() }
}
}
4. Adım. ServiceLocator'ınızı testlere hazırlama
Tamam, test yaparken ServiceLocator simgesini kullanarak test çiftlerini değiştirme zamanı. Bunu yapmak için ServiceLocator kodunuza bazı kodlar eklemeniz gerekir.
- Aç 'ı
ServiceLocator.kttıklayın. tasksRepositoryiçin ayarlayıcıyı@VisibleForTestingolarak işaretleyin. Bu ek açıklama, ayarlayıcının herkese açık olmasının nedeninin test olduğunu ifade etmenin bir yoludur.
ServiceLocator.kt
@Volatile
var tasksRepository: TasksRepository? = null
@VisibleForTesting setTestinizi tek başına veya bir grup testle birlikte çalıştırıyor olmanız fark etmeksizin testleriniz tam olarak aynı şekilde çalışmalıdır. Bu, testlerinizin birbirine bağlı davranışlar içermemesi gerektiği (yani testler arasında nesne paylaşımından kaçınılması) anlamına gelir.
ServiceLocator tekil bir öğe olduğundan testler arasında yanlışlıkla paylaşılabilir. Bunu önlemek için testler arasında ServiceLocator durumunu düzgün şekilde sıfırlayan bir yöntem oluşturun.
Anydeğeriylelockadlı bir örnek değişken ekleyin.
ServiceLocator.kt
private val lock = Any()- Veritabanını temizleyen ve hem depoyu hem de veritabanını null olarak ayarlayan, teste özel bir
resetRepositoryyöntemi ekleyin.
ServiceLocator.kt
@VisibleForTesting
fun resetRepository() {
synchronized(lock) {
runBlocking {
TasksRemoteDataSource.deleteAllTasks()
}
// Clear all data to avoid test pollution.
database?.apply {
clearAllTables()
close()
}
database = null
tasksRepository = null
}
}5. Adım: ServiceLocator'ınızı kullanma
Bu adımda ServiceLocator kullanırsınız.
- Aç 'ı
TaskDetailFragmentTesttıklayın. lateinit TasksRepositorydeğişkeni bildirin.- Her testten önce
FakeAndroidTestRepositoryoluşturmak ve her testten sonra temizlemek için bir kurulum ve bir yıkım yöntemi ekleyin.
TaskDetailFragmentTest.kt
private lateinit var repository: TasksRepository
@Before
fun initRepository() {
repository = FakeAndroidTestRepository()
ServiceLocator.tasksRepository = repository
}
@After
fun cleanupDb() = runBlockingTest {
ServiceLocator.resetRepository()
}
activeTaskDetails_DisplayedInUi()işlev gövdesinirunBlockingTestiçine alın.- Parçayı başlatmadan önce
activeTasköğesini depoya kaydedin.
repository.saveTask(activeTask)Son test aşağıdaki kod gibi görünür.
TaskDetailFragmentTest.kt
@Test
fun activeTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
repository.saveTask(activeTask)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
}@ExperimentalCoroutinesApiile sınıfın tamamına not ekleyin.
İşlem tamamlandığında kod aşağıdaki gibi görünür.
TaskDetailFragmentTest.kt
@MediumTest
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {
private lateinit var repository: TasksRepository
@Before
fun initRepository() {
repository = FakeAndroidTestRepository()
ServiceLocator.tasksRepository = repository
}
@After
fun cleanupDb() = runBlockingTest {
ServiceLocator.resetRepository()
}
@Test
fun activeTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
repository.saveTask(activeTask)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
}
}
activeTaskDetails_DisplayedInUi()testini çalıştırın.
Eskisi gibi, bu kez de parçayı görmeniz gerekir. Ancak bu kez, depoyu doğru şekilde ayarladığınız için görev bilgileri gösterilir.

Bu adımda, ilk entegrasyon testinizi tamamlamak için Espresso kullanıcı arayüzü testi kitaplığını kullanacaksınız. Kodunuzu, kullanıcı arayüzünüz için onaylamalar içeren testler ekleyebileceğiniz şekilde yapılandırmış olmanız gerekir. Bunun için Espresso test kitaplığını kullanırsınız.
Espresso'nun yardımıyla:
- Görünümlerle etkileşimde bulunma (ör. düğmeleri tıklama, çubuğu kaydırma veya ekranda aşağı kaydırma)
- Belirli görünümlerin ekranda olduğunu veya belirli bir durumda olduğunu (ör. belirli bir metni içerdiğini ya da bir onay kutusunun işaretli olduğunu) onaylayın.
1. Adım: Gradle Bağımlılığına Dikkat Edin
Android projelerinde varsayılan olarak bulunduğundan ana Espresso bağımlılığına zaten sahip olursunuz.
app/build.gradle
dependencies {
// ALREADY in your code
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
// Other dependencies
}androidx.test.espresso:espresso-core: Bu temel Espresso bağımlılığı, yeni bir Android projesi oluşturduğunuzda varsayılan olarak eklenir. Çoğu görünüm ve bu görünümlerdeki işlemler için temel test kodunu içerir.
2. adım: Animasyonları devre dışı bırakma
Espresso testleri gerçek bir cihazda çalıştırıldığından doğası gereği araç testleridir. Karşılaşılan sorunlardan biri animasyonlardır: Bir animasyon gecikirse ve bir görünümün ekranda olup olmadığını test etmeye çalışırsanız ancak animasyon devam ediyorsa Espresso testi yanlışlıkla başarısız edebilir. Bu durum, Espresso testlerinin güvenilir olmamasına neden olabilir.
Espresso kullanıcı arayüzü testi için animasyonları devre dışı bırakmanız önerilir (böylece testiniz daha hızlı çalışır):
- Test cihazınızda Ayarlar > Geliştirici seçenekleri'ne gidin.
- Şu üç ayarı devre dışı bırakın: Pencere animasyonu ölçeği, Geçiş animasyonu ölçeği ve Animatör süre ölçeği.

3. Adım: Espresso testine bakma
Espresso testi yazmadan önce bazı Espresso kodlarına göz atın.
onView(withId(R.id.task_detail_complete_checkbox)).perform(click()).check(matches(isChecked()))Bu ifade, task_detail_complete_checkbox kimlikli onay kutusu görünümünü bulur, tıklar ve ardından işaretlendiğini onaylar.
Espresso ifadelerinin çoğu dört bölümden oluşur:
onViewonView, Espresso ifadesini başlatan statik bir Espresso yöntemine örnektir. onView en yaygın olanlardan biridir ancak onData gibi başka seçenekler de vardır.
2. ViewMatcher
withId(R.id.task_detail_title_text)withId, kimliğine göre görüntülenen bir ViewMatcher örneğidir. Dokümanlarda inceleyebileceğiniz başka görünüm eşleştiriciler de vardır.
3. ViewAction
perform(click())perform yöntemi ViewAction sürer. ViewAction, görünümde yapılabilecek bir işlemdir. Örneğin, burada görünümü tıklama işlemi yapılıyor.
check(matches(isChecked()))check Bu işlem ViewAssertion sürer. ViewAssertions check or asserts something about the view. En sık kullanacağınız ViewAssertion, matches onaylamasıdır. Onaylamayı tamamlamak için başka bir ViewMatcher kullanın. Bu durumda isChecked kullanın.

Bir Espresso ifadesinde her zaman hem perform hem de check öğesini çağırmadığınızı unutmayın. check kullanarak yalnızca bir iddiada bulunan ifadeler veya perform kullanarak yalnızca ViewAction işlemi yapabilirsiniz.
- Aç 'ı
TaskDetailFragmentTest.kttıklayın. activeTaskDetails_DisplayedInUitestini güncelleyin.
TaskDetailFragmentTest.kt
@Test
fun activeTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add active (incomplete) task to the DB
val activeTask = Task("Active Task", "AndroidX Rocks", false)
repository.saveTask(activeTask)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
// THEN - Task details are displayed on the screen
// make sure that the title/description are both shown and correct
onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_title_text)).check(matches(withText("Active Task")))
onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
// and make sure the "active" checkbox is shown unchecked
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(not(isChecked())))
}
Gerekirse içe aktarma ifadeleri şunlardır:
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import org.hamcrest.core.IsNot.not// THENyorumundan sonraki her şey Espresso'yu kullanır. Test yapısını vewithIdkullanımını inceleyin ve ayrıntılar sayfasının nasıl görünmesi gerektiğiyle ilgili iddialarda bulunup bulunamayacağınızı kontrol edin.- Testi çalıştırın ve geçtiğini onaylayın.
4. Adım. İsteğe bağlı: Kendi Espresso testinizi yazın
Şimdi kendiniz bir test yazın.
completedTaskDetails_DisplayedInUiadlı yeni bir test oluşturun ve bu iskelet kodunu kopyalayın.
TaskDetailFragmentTest.kt
@Test
fun completedTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add completed task to the DB
// WHEN - Details fragment launched to display task
// THEN - Task details are displayed on the screen
// make sure that the title/description are both shown and correct
}- Önceki teste bakarak bu testi tamamlayın.
- Çalıştır'ı tıklayın ve testin başarılı olduğunu onaylayın.
Tamamlanan completedTaskDetails_DisplayedInUi şu koda benzemelidir.
TaskDetailFragmentTest.kt
@Test
fun completedTaskDetails_DisplayedInUi() = runBlockingTest{
// GIVEN - Add completed task to the DB
val completedTask = Task("Completed Task", "AndroidX Rocks", true)
repository.saveTask(completedTask)
// WHEN - Details fragment launched to display task
val bundle = TaskDetailFragmentArgs(completedTask.id).toBundle()
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
// THEN - Task details are displayed on the screen
// make sure that the title/description are both shown and correct
onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_title_text)).check(matches(withText("Completed Task")))
onView(withId(R.id.task_detail_description_text)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_description_text)).check(matches(withText("AndroidX Rocks")))
// and make sure the "active" checkbox is shown unchecked
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isDisplayed()))
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(isChecked()))
}Bu son adımda, sahte adı verilen farklı bir test çifti türünü ve Mockito adlı test kitaplığını kullanarak Navigation bileşenini nasıl test edeceğinizi öğreneceksiniz.
Bu codelab'de sahte olarak adlandırılan bir test çifti kullandınız. Sahteler, test çiftlerinin birçok türünden biridir. Navigation bileşenini test etmek için hangi test çiftini kullanmalısınız?
Navigasyonun nasıl gerçekleştiğini düşünün. TasksFragment bölümündeki görevlerden birine basarak görev ayrıntıları ekranına gittiğinizi düşünün.

Aşağıda, TasksFragment içinde yer alan ve basıldığında görev ayrıntıları ekranına giden kod verilmiştir.
TasksFragment.kt
private fun openTaskDetails(taskId: String) {
val action = TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment(taskId)
findNavController().navigate(action)
}
Gezinme, navigate yöntemi çağrıldığı için gerçekleşir. Bir onaylama ifadesi yazmanız gerekiyorsa TaskDetailFragment öğesine gidip gitmediğinizi test etmenin basit bir yolu yoktur. Gezinme, TaskDetailFragment başlatmanın dışında net bir çıkış veya durum değişikliğiyle sonuçlanmayan karmaşık bir eylemdir.
navigate yönteminin doğru işlem parametresiyle çağrıldığını onaylayabilirsiniz. Sahte test çifti tam olarak bunu yapar: Belirli yöntemlerin çağrılıp çağrılmadığını kontrol eder.
Mockito, test çiftleri oluşturmak için kullanılan bir çerçevedir. API'de ve adında "mock" kelimesi kullanılsa da bu, yalnızca sahte veriler oluşturmak için değildir. Ayrıca, sahte ve casus işlevler de oluşturabilir.
Mockito'yu kullanarak, navigate yönteminin doğru şekilde çağrıldığını onaylayabilecek bir sahte NavigationController oluşturacaksınız.
1. Adım: Gradle bağımlılıkları ekleme
- Gradle bağımlılıklarını ekleyin.
app/build.gradle
// Dependencies for Android instrumented unit tests
androidTestImplementation "org.mockito:mockito-core:$mockitoVersion"
androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:$dexMakerVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
org.mockito:mockito-core: Bu, Mockito bağımlılığıdır.dexmaker-mockito: Bu kitaplık, Android projesinde Mockito'yu kullanmak için gereklidir. Mockito'nun çalışma zamanında sınıflar oluşturması gerekir. Android'de bu işlem dex bayt kodu kullanılarak yapılır. Bu nedenle bu kitaplık, Mockito'nun Android'de çalışma zamanında nesneler oluşturmasını sağlar.androidx.test.espresso:espresso-contrib: Bu kitaplık,DatePickerveRecyclerViewgibi daha gelişmiş görünümler için test kodu içeren harici katkılardan (adının nedeni budur) oluşur. Ayrıca, erişilebilirlik kontrolleri ve daha sonra ele alınacakCountingIdlingResourceadlı sınıfı da içerir.
2. adım: Create TasksFragmentTest
TasksFragmentadlı kişiyi aç.TasksFragmentsınıf adını sağ tıklayın ve Oluştur, ardından Test'i seçin. androidTest kaynak kümesinde bir test oluşturun.- Bu kodu
TasksFragmentTestbölümüne kopyalayın.
TasksFragmentTest.kt
@RunWith(AndroidJUnit4::class)
@MediumTest
@ExperimentalCoroutinesApi
class TasksFragmentTest {
private lateinit var repository: TasksRepository
@Before
fun initRepository() {
repository = FakeAndroidTestRepository()
ServiceLocator.tasksRepository = repository
}
@After
fun cleanupDb() = runBlockingTest {
ServiceLocator.resetRepository()
}
}Bu kod, yazdığınız TaskDetailFragmentTest koduna benziyor. FakeAndroidTestRepository oluşturur ve kaldırır. Görev listesindeki bir görevi tıkladığınızda sizi doğru TaskDetailFragment'ya yönlendirdiğini test etmek için bir gezinme testi ekleyin.
- Testi ekleyin
clickTask_navigateToDetailFragmentOne.
TasksFragmentTest.kt
@Test
fun clickTask_navigateToDetailFragmentOne() = runBlockingTest {
repository.saveTask(Task("TITLE1", "DESCRIPTION1", false, "id1"))
repository.saveTask(Task("TITLE2", "DESCRIPTION2", true, "id2"))
// GIVEN - On the home screen
val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
}
- Sahte oluşturmak için Mockito'nun
mockişlevini kullanın.
TasksFragmentTest.kt
val navController = mock(NavController::class.java)Mockito'da sahte oluşturmak için sahte oluşturmak istediğiniz sınıfı iletin.
Ardından, NavController öğenizi parçayla ilişkilendirmeniz gerekir. onFragment, parçanın kendisinde yöntem çağırmanıza olanak tanır.
- Yeni sahte verilerinizi parçanın
NavControlleryapın.
scenario.onFragment {
Navigation.setViewNavController(it.view!!, navController)
}- Kodu,
RecyclerViewiçinde "TITLE1" metnini içeren öğeyi tıklayacak şekilde ekleyin.
// WHEN - Click on the first list item
onView(withId(R.id.tasks_list))
.perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText("TITLE1")), click()))RecyclerViewActions, espresso-contrib kitaplığının bir parçasıdır ve RecyclerView'da Espresso işlemleri yapmanıza olanak tanır.
navigateöğesinin doğru bağımsız değişkenle çağrıldığını doğrulayın.
// THEN - Verify that we navigate to the first detail screen
verify(navController).navigate(
TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment( "id1")Mockito'nun verify yöntemi, bu işlemi sahte bir işlem haline getirir. Sahte navController öğesinin belirli bir yöntemi (navigate) bir parametreyle (kimliği "id1" olan actionTasksFragmentToTaskDetailFragment) çağırdığını doğrulayabilirsiniz.
Testin tamamı şu şekilde görünür:
@Test
fun clickTask_navigateToDetailFragmentOne() = runBlockingTest {
repository.saveTask(Task("TITLE1", "DESCRIPTION1", false, "id1"))
repository.saveTask(Task("TITLE2", "DESCRIPTION2", true, "id2"))
// GIVEN - On the home screen
val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
val navController = mock(NavController::class.java)
scenario.onFragment {
Navigation.setViewNavController(it.view!!, navController)
}
// WHEN - Click on the first list item
onView(withId(R.id.tasks_list))
.perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText("TITLE1")), click()))
// THEN - Verify that we navigate to the first detail screen
verify(navController).navigate(
TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment( "id1")
)
}- Testinizi çalıştırın.
Özetle, gezinmeyi test etmek için şunları yapabilirsiniz:
NavControllersahtesi oluşturmak için Mockito'yu kullanın.- Bu sahte
NavControlleröğesini parçaya ekleyin. - navigate işlevinin doğru işlem ve parametrelerle çağrıldığını doğrulayın.
3. Adım: İsteğe bağlı, write clickAddTaskButton_navigateToAddEditFragment
Kendiniz bir gezinme testi yazıp yazamayacağınızı görmek için bu görevi deneyin.
clickAddTaskButton_navigateToAddEditFragmentdüğmesini tıkladığınızdaAddEditTaskFragmentsayfasına yönlendirildiğinizi kontrol eden testi yazın.
Yanıt aşağıda verilmiştir.
TasksFragmentTest.kt
@Test
fun clickAddTaskButton_navigateToAddEditFragment() {
// GIVEN - On the home screen
val scenario = launchFragmentInContainer<TasksFragment>(Bundle(), R.style.AppTheme)
val navController = mock(NavController::class.java)
scenario.onFragment {
Navigation.setViewNavController(it.view!!, navController)
}
// WHEN - Click on the "+" button
onView(withId(R.id.add_task_fab)).perform(click())
// THEN - Verify that we navigate to the add screen
verify(navController).navigate(
TasksFragmentDirections.actionTasksFragmentToAddEditTaskFragment(
null, getApplicationContext<Context>().getString(R.string.add_task)
)
)
}Başladığınız kod ile son kod arasındaki farkı görmek için burayı tıklayın.
Tamamlanmış codelab'in kodunu indirmek için aşağıdaki git komutunu kullanabilirsiniz:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_2
Alternatif olarak, depoyu Zip dosyası olarak indirebilir, dosyayı açıp Android Studio'da açabilirsiniz.
Bu codelab'de manuel bağımlılık ekleme ve hizmet bulucunun nasıl ayarlanacağı, ayrıca Android Kotlin uygulamalarınızda sahte ve taklitlerin nasıl kullanılacağı ele alındı. Özellikle:
- Test etmek istediğiniz özellikler ve test stratejiniz, uygulamanızda kullanacağınız test türlerini belirler. Birim testleri odaklanmış ve hızlıdır. Entegrasyon testleri, programınızın bölümleri arasındaki etkileşimi doğrular. Uçtan uca testler özellikleri doğrular, en yüksek doğruluğa sahiptir, genellikle ölçülür ve çalıştırılması daha uzun sürebilir.
- Uygulamanızın mimarisi, test etmenin ne kadar zor olduğunu etkiler.
- TDD (Test Odaklı Geliştirme), önce testleri yazdığınız, ardından testleri geçecek özelliği oluşturduğunuz bir stratejidir.
- Uygulamanızın belirli kısımlarını test için izole etmek üzere test çiftlerini kullanabilirsiniz. Test çifti, özellikle test için tasarlanmış bir sınıf sürümüdür. Örneğin, bir veri tabanından veya internetten veri alıyormuş gibi davranıyorsunuz.
- Gerçek bir sınıfı (ör. bir depo veya ağ katmanı) test sınıfıyla değiştirmek için bağımlılık ekleme'yi kullanın.
- Kullanıcı arayüzü bileşenlerini başlatmak için enstrümantasyonlu test (
androidTest) kullanın. - Örneğin bir parçayı başlatmak için oluşturucu bağımlılığı ekleme özelliğini kullanamadığınızda genellikle bir hizmet bulucu kullanabilirsiniz. Hizmet Bulucu deseni, Bağımlılık Ekleme'ye alternatif bir yöntemdir. Bu, hem normal hem de test kodu için bağımlılıklar sağlamak amacıyla "Hizmet Bulucu" adlı tekil bir sınıf oluşturmayı içerir.
Udacity kursu:
Android geliştirici belgeleri:
- Uygulama mimarisi kılavuzu
runBlockingverunBlockingTestFragmentScenario- Espresso
- Mockito
- JUnit4
- AndroidX Test Kitaplığı
- AndroidX Architecture Components Core Test Library
- Kaynak kümeleri
- Komut satırından test etme
Videolar:
Diğer:
Bu kurstaki diğer codelab'lerin bağlantıları için Advanced Android in Kotlin codelab'lerinin açılış sayfasına bakın.




