Bu codelab, Kotlin'deki Gelişmiş Android kursuna dahildir. Codelab'ler sırasında sırayla çalıştığınızda bu kurstan en yüksek değeri elde edersiniz ancak zorunlu değildir. Tüm kurs codelab'leri Kotlin Codelab'de Gelişmiş Android açılış sayfasında listelenmiştir.
Giriş
Bu ikinci test codelab'i test etme aşamasındadır: testlerin Android'de ne zaman kullanılacağı ve bunların bağımlılığı yerleştirme, Hizmet Bulucu kalıbı ve kitaplıklar kullanılarak nasıl uygulanacağı. Bunu yaparak şunları yazmayı öğreneceksiniz:
- Depo birimi testleri
- Parçalar ve görüntüleme modeli entegrasyon testleri
- Parçalı gezinme testleri
Bilmeniz gerekenler
Aşağıdaki konular hakkında bilgi sahibi olmalısınız:
- Kotlin programlama dili
- İlk codelab'de açıklanan test kavramları: JUnit, Hamcrest, AndroidX testi, Robolectric'i kullanarak ve LiveData'yı test ederek Android'de birim testleri yazma ve çalıştırma
- Şu temel Android Jetpack kitaplıkları:
ViewModel
,LiveData
ve Gezinme Bileşeni - Uygulama mimarisi, Uygulama mimarisi rehberi ve Android Fundamentals codelab'deki kalıbı takip eder
- Android'de coroutine'lerle ilgili temel bilgiler
Neler öğreneceksiniz?
- Test stratejisini planlama
- İkili testler (sahteler ve sahteler) nasıl oluşturulur ve kullanılır?
- Android'de birim ve entegrasyon testleri için manuel bağımlılık ekleme nasıl kullanılır?
- Hizmet Bulucu Deseni nasıl uygulanır?
- Depoları, parçaları, modelleri görüntüleme ve Gezinme bileşenini test etme
Aşağıdaki kitaplıkları ve kod kavramlarını kullanabilirsiniz:
Yapacaklarınız
- İki kez test ve bağımlı yerleştirme kullanarak depo için birim testleri yazın.
- İkili test ve bağımlı enjeksiyonu kullanarak görüntüleme modeli için birim testleri yazma.
- Espresso kullanıcı arayüzü test çerçevesini kullanarak parçalar ve görünüm modelleri için entegrasyon testleri yazın.
- Mockito ve Espresso kullanarak navigasyon testleri yazın.
Bu codelab serisinde TO-DO Notes uygulamasıyla çalışacaksınız. Uygulama, tamamlamak için görevleri not etmenize ve bir listede göstermenize olanak sağlar. Ardından, tamamlandı olarak işaretleyebilir, filtreleyebilir veya silebilirsiniz.
Bu uygulama Kotlin'de yazılmıştır, birkaç ekranı vardır ve Jetpack bileşenlerini kullanır ve Uygulama mimarisi kılavuzundaki mimariyi kullanır. Bu uygulamanın nasıl test edileceğini öğrenerek aynı kitaplık ve mimariyi kullanan uygulamaları test edebileceksiniz.
Kodu İndirme
Başlamak için kodu indirin:
Alternatif olarak, kod için Github veri deposunu klonlayabilirsiniz:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_1
Biraz zaman ayırarak aşağıdaki talimatları uygulayın.
1. Adım: Örnek uygulamayı çalıştırın
Yapılacaklar uygulamasını indirdikten sonra Android Studio'da açın ve çalıştırın. İçerik derlenmelidir. Aşağıdakileri yaparak uygulamayı keşfedin:
- Artı kayan işlem düğmesini kullanarak yeni bir görev oluşturun. Önce bir başlık girin, ardından görev hakkında ek bilgiler girin. Kodu yeşil onay FAB'ı ile kaydedin.
- Görevler listesinde, yeni tamamladığınız görevin başlığını tıklayın ve açıklamanın geri kalanını görmek için söz konusu görevin ayrıntı ekranına bakın.
- Görevin listede veya ayrıntı ekranında durumunu Tamamlandı olarak ayarlamak için ilgili onay kutusunu işaretleyin.
- Görevler ekranına 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 geri dönün ve gezinme çekmecesi menüsünden Tamamlandı durumuna sahip tüm görevleri silmek için Tamamlananları temizle'yi seçin
2. Adım: Örnek uygulama kodunu keşfedin
Yapılacaklar listesi, popüler Mimari Şemalar testine ve mimari örneğine dayalıdır (örneğin, reaktif mimari sürümü kullanılır). Uygulama, Uygulama mimarisi kılavuzundaki mimariyi kullanır. Parçalı Görünüm Modelleri, depo ve Oda kullanır. Aşağıdaki örneklerden herhangi birini biliyorsanız bu uygulamanın benzer bir mimarisi vardır:
- Viewlablab manzaralı oda
- Android Kotlin Fundamentals eğitim codelab'leri
- Gelişmiş Android eğitim codelab'leri
- Android Ayçiçeği Örneği
- Kotlin Udacity eğitim kursuyla Android Uygulamaları Geliştirme
Uygulamanın genel mimarisini anlamanız, herhangi bir katmandaki mantığı derinlemesine anlamanızdan daha önemlidir.
Göreceğiniz paketlerin özeti aşağıda verilmiştir:
Paket: | |
| Görev ekranı ekleme veya düzenleme: Görev eklemek veya düzenlemek için kullanıcı arayüzü katman kodu. |
| Veri katmanı: Görevlerin veri katmanıyla ilgilidir. Veritabanı, ağı ve depo kodunu içerir. |
| İstatistik ekranı: İstatistik 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ı dersler: Uygulamanın çeşitli bölümlerinde kullanılan paylaşılan sınıflar (ör. birden fazla ekranda kullanılan kaydırma yenileme düzeni için). |
Veri katmanı (.data)
Bu uygulama, yerel pakette, veritabanı (simüle edilmiş ağ katmanı) ve uzaktan paket halinde bir veritabanı katmanı içerir. Kolaylık sağlaması açısından bu projede ağ katmanı, gerçek ağ istekleri göndermek yerine gecikmeli olarak yalnızca bir HashMap
ile simüle edilir.
DefaultTasksRepository
, ağ iletişimi katmanı ile veritabanı katmanı arasında koordinasyon sağlar veya arabuluculuk yapar ve verileri kullanıcı arayüzü katmanına iletir.
Kullanıcı arayüzü katmanı ( .addedittask, .statistics, .taskdetail, .tasks)
Kullanıcı arayüzü katman paketlerinin her biri, parça ve görünüm modelinin yanı sıra kullanıcı arayüzü için gerekli olan diğer sınıfları (görev listesi için bağdaştırıcı gibi) içerir. TaskActivity
, tüm parçaları içeren etkinliktir.
Gezinme
Uygulamada gezinme, Gezinme bileşeni tarafından kontrol edilir. nav_graph.xml
dosyasında tanımlanır. Gezinme modelleri, 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
'ları gözlemler ve ekranlar arasında gerçek gezinmeyi gerçekleştirirler.
Bu codelab'de, test çiftlerini ve bağımlılık yerleştirmeyi kullanarak depoları test etmeyi, modelleri ve parçaları görüntülemeyi öğreneceksiniz. Bunların ne olduğunu öğrenmeden önce, bu testleri nasıl ve nasıl yazacağınızı belirleyen gerekçeyi anlamanız önemlidir.
Bu bölümde, Android için geçerli olduğundan, genel anlamda en iyi test uygulamaları yer almaktadır.
Test Piramidi
Bir test stratejisini değerlendirirken, üç ilgili test aşaması vardır:
- Kapsam: Testin ne kadarı koda dokunur? Testler tek bir yöntemde, uygulamanın tamamında veya arada bir yerde çalıştırılabilir.
- Hız - Test ne kadar hızlı çalışıyor? Test hızları milisaniye-birkaç dakika arasında değişebilir.
- Gerçeklik - Test ne kadar "gerçek dünya"? Örneğin, test ettiğiniz kodun bir kısmının ağ isteğinde bulunması gerekiyorsa test kodu bu ağ isteğini gerçekten yapıyor mu yoksa sahte mi? Test ağ ile gerçekten konuşuyorsa bu, ağ kalitesinin daha yüksek olduğu anlamına gelir. Bunun karşılığında, testin daha uzun sürmesi, ağın çalışmaması halinde hatalarla sonuçlanabilir veya kullanımı pahalı olabilir.
Bu özellikler arasında doğal dengeler vardır. Örneğin, hız ve doğruluk önemlidir. Test ne kadar hızlı olursa genellikle o kadar düşük kaliteli olur ve bunun tersi de geçerlidir. Otomatik testleri bölmenin yaygın yollarından biri şu üç kategoridir:
- Birim testleri: Bu testler, tek bir sınıfta (genellikle o sınıfta tek bir yöntem) çalıştırılan son derece odaklanmış testlerdir. Birim testi başarısız olursa sorunun tam olarak kodunuzun neresinde olduğunu bilebilirsiniz. Gerçek dünyada uygulamanız bir yöntemin veya sınıfın çalıştırılmasından çok daha fazlasını içerir. Bunlar, kodunuzu her değiştirdiğinizde çalışacak kadar hızlıdır. Çoğu zaman yerel olarak testler yapılır (
test
kaynak grubunda). Örnek: Modelleri ve depoları tek tek test etme. - Entegrasyon testleri - Bunlar, birlikte kullanıldıklarında beklendiği gibi davrandıklarından emin olmak için birkaç sınıfın etkileşimini test eder. Entegrasyon testlerini yapılandırmanın bir yolu, test ekiplerine görev kaydetme gibi tek bir özelliği test etmelerini sağlamaktır. Kod testleri, birim testlerinden daha geniş bir kapsamı test eder, ancak tam doğruluk için hızlı çalışacak şekilde optimize edilirler. Duruma bağlı olarak, yerel olarak veya enstrümantasyon testleri olarak çalıştırılabilirler. Örnek: Tek bir parçanın ve model çiftinin tüm işlevlerini test etme.
- Uçtan uca testler (E2e): Birlikte çalışan özellik kombinasyonunu test edin. Uygulamanın büyük bir kısmını test ederler, gerçek kullanımı yakından simüle ederler ve bu nedenle genellikle yavaştırlar. En yüksek kaliteye sahiptirler ve size uygulamanızın bir bütün olarak çalıştığını söyler. Genel olarak, bu testler enstrümanlı testler olacaktır (
androidTest
kaynak kümesinde)
Örnek: Uygulamanın tamamını başlatıp birkaç özelliği birlikte test etme.
Bu testlerin önerilen oranı genellikle bir piramitle temsil edilir. Testlerin büyük çoğunluğu birim testlerdir.
Mimari ve Test
Uygulamanızı test piramidinin tüm farklı düzeylerinde test edebilme beceriniz doğal olarak uygulamanızın mimarisine bağlıdır. Örneğin, son derece kötü mimariye sahip bir uygulama, tüm mantığını tek bir yönteme yerleştirebilir. Bu testler, uygulamanın büyük bir kısmını test etmeye meyilli olduğundan, bunun için uçtan uca test yazabilirsiniz. Peki ya yazma birimi veya entegrasyon testleri? Tüm kodu tek bir yerde toplayarak yalnızca tek bir birim veya özellikle ilgili kodu test etmek zordur.
Daha iyi bir yaklaşım, uygulama mantığını birden çok yönteme ve sınıfa ayırmak ve her parçanın izole olarak test edilmesini sağlamaktır. Mimari, kodunuzu ayırmanın ve düzenlemenin bir yoludur ve böylece birim ve entegrasyon testlerini kolaylaştırır. Test edeceğiniz Yapılacaklar uygulaması belirli bir mimariyi izler:
Bu kursta, yukarıdaki mimarinin parçalarını uygun izolasyonla nasıl test edeceğinizi göreceksiniz:
- Öncelikle depoda birim test edeceksiniz.
- Daha sonra, görünüm modelinde bir testi iki kez kullanırsınız: Bu, birim testi ve model entegrasyon testi için gereklidir.
- Daha sonra parçalar ve görünüm modelleri için entegrasyon testleri yazmayı öğreneceksiniz.
- Son olarak, Gezinme bileşenini içeren entegrasyon testleri yazmayı öğreneceksiniz.
Uçtan uca test, bir sonraki derste ele alınacaktır.
Bir sınıfın bir bölümü için (bir yöntem veya küçük bir yöntem koleksiyonu) birim testi yazdığınızda hedefiniz bu sınıftaki kodu test etmektir.
Yalnızca belirli bir sınıf veya sınıftaki kodları test etmek zor olabilir. Bunu bir örnek üzerinde inceleyelim. main
kaynak kümesinde data.source.DefaultTaskRepository
sınıfını açın. Bu, uygulamanın havuzudur ve sonraki birim için birim testi yazacağınız sınıftır.
Hedefiniz yalnızca söz konusu sınıftaki kodu test etmektir. Bununla birlikte, DefaultTaskRepository
, çalışması için LocalTaskDataSource
ve RemoteTaskDataSource
gibi diğer sınıflara bağlıdır. Diğer bir deyişle LocalTaskDataSource
ve RemoteTaskDataSource
, DefaultTaskRepository
için bağlılıklardır.
Bu nedenle, DefaultTaskRepository
'teki her yöntem, veri kaynağı sınıflarındaki yöntemleri çağırır. Bu da diğer sınıflardaki arama yöntemlerinin bilgileri veritabanına kaydetmesi veya ağla iletişim kurması anlamına gelir.
Ö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
, kod deponuzda yapabileceğiniz en temel görüşmelerden biri. Bu yöntem, SQLite veritabanından okuma ve ağ çağrıları (updateTasksFromRemoteDataSource
çağrısı) yapılmasını içerir. Bu, yalnızca kod deposu kodundan çok daha fazla kod içerir.
Kod deposunu test etmenin zor olmasının bazı nedenleri aşağıda açıklanmıştır:
- Bu veri havuzu için en basit testleri yapmak üzere bir veritabanı oluşturma ve yönetme üzerinde düşünmeniz gerekir. Bu, "Yerel mi yoksa enstrümantasyonlu mu?" testi gibi sorular ortaya çıkar ve simüle edilmiş bir Android ortamı elde etmek için AndroidX Testi'ni kullanmanız gerekip gerekmediğini gösterir.
- Kodun ağ iletişimi kodu gibi bazı bölümlerinin çalıştırılması uzun sürebilir veya bazen başarısız olabilir. Bu da uzun süreli, güvenilir olmayan testler oluşturur.
- Testleriniz, test hatası nedeniyle hangi kodun hatalı olduğunu teşhis etme yeteneğini kaybedebilir. Testleriniz, kod deposu olmayan kodu test etmeye başlayabilir. Bu nedenle, örneğin, beklenen kod birimi birimi testleriniz veritabanı kodu gibi bazı bağımlı kodlarda bir sorun nedeniyle başarısız olabilir.
Çiftler
Bunun çözümü, kod deposunu test ederken gerçek ağ veya veritabanı kodunu kullanmayın. Bunun yerine bir test testi kullanın. Test çift, test için özel olarak tasarlanmış bir sınıf sürümüdür. Testlerde bir sınıfın gerçek sürümünün yerini alması amaçlanmıştır. Akrobasi uzantıları, akrobatik hareketlerde uzmanlaşmış ve tehlikeli eylemler için asıl aktörü değiştiren bir oyuncuya benzer.
Aşağıda, çift test türleri verilmiştir:
Sahte | Sınıfın "çalışıyor" olduğu ancak testler için iyi ama üretime uygun olmayan bir şekilde uygulanan test testi. |
Örnek | Hangi yöntemlerin çağrıldığını izleyen bir test testi. Daha sonra, yöntemlerin doğru şekilde çağrılıp çağrıldığına bağlı olarak testi geçer veya başarısız olur. |
Stub | Mantık içermeyen ve yalnızca döndürülecek şekilde programladığınız şeyi döndüren test çifti. |
Sahte | Geçilen ancak kullanılmayan bir parametre çifti (örneğin, bir parametre olarak sağlamanız gerekiyorsa). Bir |
Casusluk | Bazı ek bilgileri izleyen bir test testi de yapılır. Örneğin, bir |
İkili testler hakkında daha fazla bilgi için Tuvalette Test Etme: İkili Testinizi Bilin başlıklı makaleyi inceleyin.
Android'de en yaygın kullanılan test çiftleri Sahteler ve Örnekler'dir.
Bu görevde gerçek veri kaynaklarından ayrıştırılan birim testi DefaultTasksRepository
için iki kez FakeDataSource
testi oluşturacaksınız.
1. Adım: FakeDataSource sınıfını oluşturun
Bu adımda, FakeDataSouce
adı verilen bir sınıf oluşturacaksınız. Bu sınıf, LocalDataSource
ve RemoteDataSource
sayısının iki katı olacak.
- test kaynak kümesinde sağ tıklayın Yeni -> Paketi'ni seçin.
- İçinde source paketi bulunan bir veri paketi oluşturun.
- data/source paketinde
FakeDataSource
adlı yeni bir sınıf oluşturun.
2. Adım: TasksDataSource Arayüzünü Uygulayın
Yeni sınıfınızı (FakeDataSource
) test çift olarak kullanabilmeniz için diğer veri kaynaklarının yerini alması gerekir. Bu veri kaynakları TasksLocalDataSource
ve TasksRemoteDataSource
'dır.
- Bunların ikisinin de
TasksDataSource
arayü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
uygulamasınıTasksDataSource
uygulayın:
class FakeDataSource : TasksDataSource {
}
Android Studio, TasksDataSource
için gerekli yöntemleri uygulamadığınızdan şikayet edecektir.
- Hızlı düzeltme menüsünü kullanarak Üyeleri uygula'yı seçin.
- Tüm yöntemleri seçin ve Tamam'a basın.
3. Adım: FaTaskDataSource'ta getTasks yöntemini uygulayın
FakeDataSource
, sahte adı verilen belirli bir test türüdür. Sahteler, sınıfında "çalışan" uygulama anlamına gelen bir test çiftliğidir. Ancak bu test, testler için uygun olsa da üretime uygun olmayan bir şekilde uygulanır. "Çalışma" uygulaması, sınıfın girdiler halinde gerçekçi çıkışlar üreteceği anlamına gelir.
Örneğin, sahte veri kaynağınız ağa bağlanmaz veya veritabanına herhangi bir şey kaydetmez. Yalnızca bellekte bir liste kullanılır. Bu, beklendiği gibi bu tür görevleri almak veya kaydetmek için beklediğiniz sonuçları verecek, ancak sunucuya veya veritabanına kaydedilmediğinden bu uygulamayı hiçbir zaman üretimde kullanamazsınız.
Bir FakeDataSource
- Bu sayede, gerçek bir veritabanı veya ağa ihtiyaç duymadan kodu
DefaultTasksRepository
içinde test edebilirsiniz. - testler için gerçeğe yetecek kadar uygulama sağlamaktadır.
FakeDataSource
oluşturucuyu,tasks
boş değerli bir varsayılan liste değeri olanMutableList<Task>?
olan birvar
oluşturmak için değiştirin.
class FakeDataSource(var tasks: MutableList<Task>? = mutableListOf()) : TasksDataSource { // Rest of class }
Bu, veritabanı veya sunucu yanıtı olan görevlerin listesidir. Şu an için hedef, depolama alanı getTasks
yöntemini test etmektir. Bu işlem, veri kaynağı getTasks
, deleteAllTasks
ve saveTask
yöntemlerini çağırır.
Aşağıdaki yöntemlerin sahte bir sürümünü yazın:
getTasks
yazın:tasks
null
iseSuccess
sonucunu döndürün.tasks
null
iseError
sonucunu döndürün.deleteAllTasks
yazın: değiştirilebilir görevler listesini temizleyin.saveTask
yazın: Görevi listeye ekleyin.
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 beyanları ş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.Task
Bu, gerçek yerel ve uzak veri kaynaklarının işleyişine benzer.
Bu adımda, manuel bağımlılık ekleme adlı bir teknik kullanırsınız. Böylece, oluşturduğunuz sahte test ikilisini kullanabilirsiniz.
Ana sorun, FakeDataSource
olmasıdır ancak bu özelliği testlerde nasıl kullandığınız anlaşılmıyor. Yalnızca testlerde TasksRemoteDataSource
ve TasksLocalDataSource
yerine kullanılması gerekir. Hem TasksRemoteDataSource
hem de TasksLocalDataSource
, DefaultTasksRepository
bağımlılığıdır. Diğer bir deyişle, DefaultTasksRepositories
sınıfların çalışması için bu sınıflarda "veya" gerektirir.
Şu anda, bağımlılar init
DefaultTasksRepository
yönteminin içinde oluşturulmaktadır.
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
}
DefaultTasksRepository
içinde taskLocalDataSource
ve tasksRemoteDataSource
oluşturup atadığınız için bunlar sabit kodludur. Testinizin yerine geçmek mümkün değildir.
Bunun yerine, bu veri kaynaklarını sabit kodlamak yerine sınıfa sağlamak istersiniz. Bağımlılık sağlama, bağımlılık ekleme olarak bilinir. Bağımlılık sağlamanın farklı yolları, dolayısıyla farklı türde enjeksiyon türleri vardır.
Oluşturucu Bağımlılık Ekleme, testi oluşturucuya ileterek testi değiştirmenize olanak tanır.
Enjeksiyon yok | Enjeksiyon |
1. Adım: DefaultTasksRepository'de Yapıcı Bağımlı Enjeksiyonu kullanın
DefaultTaskRepository
oluşturucusunuApplication
kullanarak alma işlemini hem veri kaynaklarını hem de eş yordam distribütörünü alacak şekilde değiştirin. Bu testi de testleriniz için 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ıları ilettiğiniz için
init
yöntemini kaldırın. Artık bağımlılık oluşturmanız gerekmez. - Ayrıca, eski örnek değişkenlerini de silin. Bunları oluşturucuda tanımlarsını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
getRepository
yö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 yapıcı bağımlı enjeksiyonu kullanıyorsunuz!
2. Adım: Testlerinizde FakeDataSource'u kullanın
Kodunuz artık yapıcı bağımlı enjeksiyonu kullandığına göre DefaultTasksRepository
verinizi test etmek için sahte veri kaynağınızı kullanabilirsiniz.
DefaultTasksRepository
sınıf adını sağ tıklayın ve Oluştur'u, ardından Test'i seçin.- Test kaynak grubunda
DefaultTasksRepositoryTest
oluşturmak için talimatları uygulayın. - Yeni
DefaultTasksRepositoryTest
sınıfınızın üst kısmında, sahte veri kaynaklarınızdaki verileri temsil etmesi 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, iki
FakeDataSource
üye değişkeni (Deponuz için her veri kaynağı için bir tane) veDefaultTasksRepository
için bir değişken test edin.
DefaultTasksRepositoryTest.kt
private lateinit var tasksRemoteDataSource: FakeDataSource
private lateinit var tasksLocalDataSource: FakeDataSource
// Class under test
private lateinit var tasksRepository: DefaultTasksRepository
Test edilebilir bir DefaultTasksRepository
ayarlamak ve başlatmak için bir yöntem oluşturun. Bu DefaultTasksRepository
testinizi (FakeDataSource
) kullanacaktır.
createRepository
adlı bir yöntem oluşturun ve bu yöntemi@Before
ile ekleyin.remoteTasks
velocalTasks
listelerini kullanarak sahte veri kaynaklarınızı örneklendirin.- Kısa süre önce oluşturduğunuz iki sahte veri kaynağını ve
Dispatchers.Unconfined
özelliğini kullanaraktasksRepository
cihazınızı örnekleyin.
Son 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() Testi Yazma
DefaultTasksRepository
testi yazma zamanı!
- Deponun
getTasks
yöntemi için bir test yazın.true
ilegetTasks
öğesini çağırdığınızda (uzaktan veri kaynağından yeniden yüklenmesi gerektiği) kontrol ederek, uzak veri kaynağından (yerel veri kaynağı yerine) veri döndürdüğünden emin olun.
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:
numaralı telefonu aradığınızda bir hata mesajı alacaksınız
4. Adım: RunBlockTest'i ekleyin
Coroutine hatasının olması beklenen bir durumdur. getTasks
suspend
işlevidir ve çağırmak için eş yordam başlatmanız gerekir. Bunun için eş yordamı kapsamı gerekiyor. Bu hatayı çözmek için, testlerinizde başlatma eş yordamlarını işlemek için bazı gradle bağımlılıkları eklemeniz gerekir.
- Kortinleri test etmek için gereken bağımlılıkları
testImplementation
kullanarak test kaynağı grubuna ekleyin.
uygulama/derleme.gradle
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
Senkronizasyonu unutmayın
kotlinx-coroutines-test
, özellikle coroutine'leri test etmek için tasarlanmış coroutine'ler test kitaplığıdır. Testlerinizi çalıştırmak için runBlockingTest
işlevini kullanın. Bu, eş yordam test kitaplığı tarafından sağlanan bir işlevdir. Bir kod bloğunu alır ve ardından bu kod bloğunu eş zamanlı ve anında çalışan özel bir eş zamanlı bağlamda çalıştırır. Bu da eylemlerin belirleyici bir sırada gerçekleşeceği anlamına gelir. Bu esasen eş yordamlarınız coroutine'ler gibi çalışır. Bu nedenle, kodu test etmek için kullanılır.
Bir suspend
işlevini çağırırken test sınıflarınızda runBlockingTest
kullanın. Bu serideki bir sonraki codelab'de runBlockingTest
adlı çocuğun nasıl çalıştığı ve eş yordamların nasıl test edileceği hakkında daha fazla bilgi edineceksiniz.
@ExperimentalCoroutinesApi
öğesini sınıfın üst kısmına ekleyin. Bu, sınıfta deneysel bir eş yordam API'si (runBlockingTest
) kullandığınızı bildiğinizi ifade eder. Bu özellik olmadan bir uyarı alırsınız.DefaultTasksRepositoryTest
'yirunBlockingTest
'e ekleyin. Böylece, testinizin tamamını kod bloğu olarak alır
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_requestsAllTasksFromRemoteDataSource
testinizi çalıştırıp çalıştığını onaylayın ve hata oluştu.
Kod deposunu birim testinde nasıl yapacağınızı gördünüz. Sonraki adımlarda, bağımlı ekleme özelliğini tekrar kullanıp bir çift test daha oluşturacaksınız. Bu kez, görüntüleme modelleriniz için birim ve entegrasyon testlerinin nasıl yazılacağını göstermiş olursunuz.
Birim testleri yalnızca ilgilendiğiniz sınıfı veya yöntemi test etmelidir. Bu, izolasyonda test olarak bilinir. Burada, biriminizi açıkça ayırır ve yalnızca bu birimin parçası olan kodu test edersiniz.
Bu nedenle TasksViewModelTest
, yalnızca TasksViewModel
kodunu test etmelidir. Veritabanı, ağ veya depo sınıflarında test etmemelidir. Bu nedenle, görünüm modellerinizde olduğu gibi kod deponuzda olduğu gibi sahte bir veri havuzu oluşturur ve testlerinizde kullanmak için bağımlılık yerleştirmeyi uygularsınız.
Bu görevde modelleri görüntülemek için bağımlılık yerleştirme uygularsınız.
1. Adım: Görevler Deposu Arayüzü Oluşturma
Yapıcı bağımlılık yerleştirmenin ilk adımı, sahte ve gerçek sınıf arasında paylaşılan ortak bir arayüz oluşturmaktır.
Bu, pratikte nasıl görünüyor? TasksRemoteDataSource
, TasksLocalDataSource
ve FakeDataSource
öğelerine bakın ve hepsinin aynı arayüzü paylaştığına dikkat edin: TasksDataSource
. Bu, DefaultTasksRepository
oluşturucusunda TasksDataSource
ile aldığınızı söylemenizi sağlar.
DefaultTasksRepository.kt
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
Bu sayede FakeDataSource
ödülünüzü değiştirebiliriz.
Ardından, veri kaynaklarında yaptığınız gibi DefaultTasksRepository
için bir arayüz oluşturun. DefaultTasksRepository
ürününün herkese açık tüm yöntemlerini (herkese açık API yüzeyi) içermelidir.
DefaultTasksRepository
öğesini açın ve sınıf adını sağ tıklayın. Ardından, Yeniden Düzenleme -> Ayıklama -> Arayüzü'nü seçin.
- Dosyayı çıkarmak için ayıkla'yı seçin.
- Ayıklama Arayüzü penceresinde arayüz adını
TasksRepository
olarak değiştirin. - Forma katılan üyeler bölümünde, iki tamamlayıcı üye ve gizli yöntemler hariç tüm üyeleri işaretleyin.
- Yeniden düzenle'yi tıklayın. Yeni
TasksRepository
arayüzü, veri/kaynak paketinde görünecektir.
DefaultTasksRepository
artık TasksRepository
özelliğini uyguluyor.
- Her şeyin çalışır durumda olduğundan emin olmak için uygulamanızı (testleri değil) çalıştırın.
2. Adım: FakeTestRepository Oluşturma
Artık arayüze sahip olduğunuza göre DefaultTaskRepository
test programını iki kez oluşturabilirsiniz.
- Test kaynağı grubunda, data/source dosyasında Kotlin dosyasını ve
FakeTestRepository.kt
sınıfını oluşturun veTasksRepository
arayüzünden genişletin.
FakeTestRepository.kt
class FakeTestRepository : TasksRepository {
}
Arayüz yöntemlerini uygulamanız gerektiği bildirilir.
- Öneri menüsünü görene kadar fareyle hatanın üzerine gelin ve ardından Üyeleri uygula'yı seçip seçin.
- Tüm yöntemleri seçin ve Tamam'a basın.
3. Adım: FakeTestRepository yöntemlerini uygulama
Artık"Uygulanmamış"yöntemlerle bir FakeTestRepository
sınıfınız var. FakeDataSource
uygulamasına benzer şekilde, FakeTestRepository
, yerel ve uzak veri kaynakları arasında karmaşık bir uyumlulaştırma işlemiyle ilgilenmek yerine bir veri yapısıyla desteklenir.
FakeTestRepository
cihazınızın FakeDataSource
veya benzer bir özelliği kullanması gerekmediğini unutmayın. Yalnızca girdiler sağlanarak gerçekçi sahte çıkışlar döndürülmelidir. Görev listesini depolamak için bir LinkedHashMap
, gözlemlenebilir görevleriniz için bir MutableLiveData
kullanacaksınız.
FakeTestRepository
öğesinde, hem mevcut görev listesini temsil eden birLinkedHashMap
değişkeni hem de gözlemlenebilir görevleriniz için birMutableLiveData
ekleyin.
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
değerini alıptasksServiceData.values.toList()
kullanarak bir listeye dönüştürmeli ve daha sonra bunuSuccess
sonucu olarak döndürmelidir.refreshTasks
-observableTasks
değerini,getTasks()
tarafından döndürülen değer olarak günceller.observeTasks
:runBlocking
kullanılarak bir eş yordam oluşturulur,refreshTasks
çalıştırılır, ardındanobservableTasks
döndürülür.
Aşağıda, bu yöntemlerin kodu 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. addTask'lere test için bir yöntem ekleyin
Test sırasında, zaten deponuzda Tasks
olması daha iyidir. saveTask
numaralı telefonu birkaç kez arayabilirsiniz. Ancak bu işlemi kolaylaştırmak için özel olarak görev eklemenize olanak tanıyan testlere yardımcı bir yöntem ekleyin.
vararg
görevden oluşan, her biriniHashMap
'ye ekleyen ve ardından görevleri yenileyenaddTasks
yöntemini ekleyin.
FakeTestRepository.kt
fun addTasks(vararg tasks: Task) {
for (task in tasks) {
tasksServiceData[task.id] = task
}
runBlocking { refreshTasks() }
}
Bu noktada, birkaç temel yöntem kullanılarak test yapmak için sahte bir kod deponuz olur. Ardından, bunu testlerinizde kullanın.
Bu görevde ViewModel
içinde sahte bir sınıf kullanıyorsunuz. Yapıcı bağımlılık yerleştirme aracılığıyla, iki veri kaynağını TasksViewModel
ve oluşturucu oluşturucuya TasksRepository
değişkeni ekleyerek almak için kullanın.
Doğrudan oluşturmadığınız için bu işlem, 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
mülk yetkisini kullanıyorsunuz. Görünüm modelinin oluşturulma şeklini değiştirmek için ViewModelProvider.Factory
eklemeniz ve kullanmanız gerekir. ViewModelProvider.Factory
hakkında bilginiz yoksa buradan daha fazla bilgi edinebilirsiniz.
1. Adım: TasksViewModel'de bir ViewModelFactory oluşturma ve kullanma
Dersleri güncellemek ve Tasks
ekranı ile ilgili testlere başlamak gerekir.
TasksViewModel
uygulamasını açın.TasksRepository
sınıfında yapmak içinTasksViewModel
oluşturucusunu sınıf içinde oluşturmak yerine 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ğinizden, TasksViewModel
öğesini oluşturmak için artık bir fabrika kullanmanız gerekiyor. Fabrika sınıfını TasksViewModel
ile aynı dosyaya yerleştirin, ancak kendi dosyasına da koyabilirsiniz.
TasksViewModel
dosyasının alt kısmına, sınıfın dışında, düzTasksRepository
ifadesini alan birTasksViewModelFactory
ekleyin.
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)
}
ViewModel
ürününün oluşturulma şeklini değiştirmenin standart yöntemi budur. Fabrikaya sahip olduğunuza göre, görünüm modelinizi oluşturduğunuz her yerde bu cihazı kullanabilirsiniz.
- Fabrikayı kullanmak için
TasksFragment
uygulaması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ıp her şeyin düzgün şekilde çalıştığından emin olun.
2. Adım: TasksViewModelTest içinde FakeTestRepository kullanma
Artık görünüm modeli testlerinizde gerçek kod deposunu kullanmak yerine sahte veri deposunu kullanabilirsiniz.
TasksViewModelTest
uygulamasını açın.TasksViewModelTest
içine birFakeTestRepository
mülkü 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örevle
FakeTestRepository
yapmak içinsetupViewModel
yöntemini güncelleyin, ardındantasksViewModel
kodunu bu kod deposuyla 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)
}
- AndroidX Testi
ApplicationProvider.getApplicationContext
kodunu artık kullanmadığınız için@RunWith(AndroidJUnit4::class)
ek açıklamasını da kaldırabilirsiniz. - Testlerinizi çalıştırın, tüm testlerin çalışmaya devam ettiğinden emin olun.
Yapıcı bağımlılık ekleme yöntemini kullanarak DefaultTasksRepository
öğesini bağımlı olarak kaldırmış ve testlerde FakeTestRepository
ile değiştirmiştiniz.
3. Adım: Görev Ayrıntıları Parçası ve Görünüm Modelini de Güncelle
TaskDetailFragment
ve TaskDetailViewModel
için tam olarak aynı değişiklikleri yapın. Bu işlem, daha sonra TaskDetail
testi yazdığınızda kodu hazırlayacaktır.
TaskDetailViewModel
uygulamasını açı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 }
TaskDetailViewModel
dosyasının altına, sınıfın dışında birTaskDetailViewModelFactory
ekleyin.
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
TasksFragment
uygulaması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
içinde gerçek depo yerine FakeTestRepository
kullanabilirsiniz.
Parça ve görüntüleme 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 öğrenebilirsiniz. Bunun için:
- ServiceLocator deseni
- Espresso ve Mockito kütüphaneleri
Entegrasyon testleri, birlikte kullanıldıklarında beklendiği gibi davrandıklarından emin olmak için birkaç sınıfın etkileşimini test eder. Bu testler yerel olarak (test
kaynak kümesi) veya enstrümantasyon testleri (androidTest
kaynak kümesi) olarak çalıştırılabilir.
Bu durumda, her parçayı alıp parça ve görünüm modeli için entegrasyon testlerini yazacaksınız ve parçanın ana özelliklerini test edeceksiniz.
1. Adım: Grad Bağımlıları Ekle
- Aşağıdaki gradle bağımlılıklarını ekleyin.
uygulama/derleme.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
: JUnit, temel test ifadeleri yazmak için gereklidir.androidx.test:core
- Temel AndroidX test kitaplığıkotlinx-coroutines-test
- eş yordamlar test kitaplığıandroidx.fragment:fragment-testing
: Testlerde parçalar oluşturup bunların durumunu değiştirmek için AndroidX test kitaplığı.
Bu kitaplıkları androidTest
kaynak kümenizde kullanacağınız için bağımlılık olarak eklemek üzere androidTestImplementation
kullanın.
2. Adım: TaskDetailFragmentTest sınıfı oluştur
TaskDetailFragment
, tek bir görevle ilgili bilgileri gösterir.
TaskDetailFragment
için parça parçası testi yaparak başlarsınız. Bu test, diğer parçalara kıyasla oldukça temel işlevlere sahiptir.
taskdetail.TaskDetailFragment
uygulamasını açın.- Daha önce yaptığınız gibi
TaskDetailFragment
için bir test oluşturun. Varsayılan seçenekleri kabul edin ve androidTest kaynak grubuna (test
kaynak grubu değil) yerleştirin.
TaskDetailFragmentTest
sınıfına aşağıdaki ek açıklamaları ekleyin.
TaskDetailFragmentTest.kt
@MediumTest
@RunWith(AndroidJUnit4::class)
class TaskDetailFragmentTest {
}
Bu ek açıklamanın amacı:
@MediumTest
: Testi "orta çalışma zamanı" entegrasyon testi olarak işaretler (@SmallTest
birim testleri ve@LargeTest
büyük uçtan uca testlere kıyasla). Bu, hangi boyutta testin yürütüleceğini gruplandırmanıza ve seçmenize yardımcı olur.@RunWith(AndroidJUnit4::class)
- AndroidX Testi kullanan tüm sınıflarda kullanılır.
3. Adım: Bir test parçasını başlatma
Bu görevde AndroidX Test kitaplığını kullanarak TaskDetailFragment
başlatacaksınız. FragmentScenario
, AndroidX Testi'nin parçalarından birini saran ve test için parçanın yaşam döngüsü üzerinde doğrudan kontrol sağlayan bir sınıftır. Parçalar için test yazmak üzere, test ettiğiniz parça (TaskDetailFragment
) için bir FragmentScenario
oluşturursunuz.
- Bu testi
TaskDetailFragmentTest
ürününe kopyalayı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:
- Bir görev oluşturur.
- Parçaya aktarılacak görev için parça bağımsız değişkenlerini temsil eden bir
Bundle
oluşturur. launchFragmentInContainer
işlevi, bu paket ve temaya sahip birFragmentScenario
oluşturur.
Henüz bir şey iddia etmediğinden bu test henüz tamamlanmadı. Şimdilik testi çalıştırın ve neler olduğunu gözlemleyin.
- Bu bir enstrümantasyon testidir, bu yüzden emülatör veya cihazınızın görünür olduğundan emin olun.
- Testi çalıştırın.
Birkaç şey olacak.
- İlk olarak, bu cihazın göstergeli bir test olması nedeniyle test fiziksel cihazınızda (bağlıysa) veya bir emülatörde çalıştırılır.
- Parçayı başlatmalıdır.
- Sitenin başka bir parçada nasıl gezindiğine veya etkinlikle ilişkili menülere nasıl sahip olmadığına dikkat edin. Bu yalnızca parçadır.
Son olarak, yakından bakın ve parçanın görev verilerini başarıyla yüklemediğinden "Veri yok" yazdığına dikkat edin.
Hem testinizin hem de TaskDetailFragment
öğesini yükleyip (yani bunu yaptığınız) verilerin doğru şekilde yüklendiğini iddia etmesi gerekir. Neden hiç veri yok? Bunun nedeni, bir görev oluşturmuş olmanız, ancak bunu 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)
}
Bu FakeTestRepository
mevcut ancak gerçek deponuzu parçanız için sahte kod deposuyla değiştirmeniz gerekir. Bunu bir sonraki adımda yapacaksınız.
Bu görevde sahte deponuzu ServiceLocator
kullanarak parçanıza sağlarsınız. Bu işlem, parçanızı yazmanıza ve model entegrasyon testlerini görüntülemenize olanak tanır.
Daha önce yaptığınız gibi, görünüm modeline veya kod deposuna bir bağımlılık sağlamanız gerektiğinde oluşturucu yapıcı enjeksiyonu kullanamazsınız. Yapıcı bağımlı enjeksiyonu için sınıf oluşturmanız gerekir. Parçalar ve etkinlikler, sizin oluşturmadığınız ve genellikle oluşturucuya erişemediğiniz sınıfların örnekleridir.
Parçayı oluşturmadığınız için, kod deposu test (FakeTestRepository
) parçasını parçayla değiştirmek için oluşturucu bağımlı enjeksiyonu kullanamazsınız. Bunun yerine, Hizmet Bulucu kalıbını kullanın. Hizmet Bulucu modeli, Bağımlı Enjeksiyonu için alternatiftir. Amacı, hem normal hem de test kodu için bağımlılık sağlamak olan "Service Locator" adlı tekli sınıf oluşturmayı içerir. Normal uygulama kodunda (main
kaynak kümesi) bu bağımlılıkların tümü normal uygulama bağımlılıklarıdır. Testlerde, Hizmet Bulucu'yu bağımlıların test çift sürümlerini sağlayacak şekilde değiştirirsiniz.
Hizmet Bulucu kullanılmıyor | Hizmet Bulucu Kullanma |
Bu codelab uygulaması için aşağıdakileri yapın:
- Kod deposu oluşturabilen ve depolayabilen bir Hizmet Bulucu sınıfı oluşturun. Varsayılan olarak "normal" bir depo oluşturur.
- Kod deposuna ihtiyacınız olduğunda Hizmet Bulucu'yu kullanabilmeniz için kodunuzu yeniden düzenleyin.
- Test sınıfınızda, Hizmet Bulucu'da depoyu test deposuyla değiştiren normal bir yöntem arayın.
1. Adım: ServiceLocator oluşturma
ServiceLocator
dersi yapalım. Ana uygulama kodu tarafından kullanıldığı için uygulama kodunun geri kalanıyla birlikte ana kaynak kümesinde yer alacaktır.
Not: ServiceLocator
tek parçadır. Bu nedenle sınıf için Kotlin object
anahtar kelimesini kullanın.
- Ana kaynak grubunun en üst düzeyinde ServiceLocator.kt dosyasını oluşturun.
ServiceLocator
adlı birobject
tanımlayın.database
verepository
örnek değişkenlerini oluşturun ve her ikisini denull
olarak ayarlayın.- Depoya birden çok ileti dizisi tarafından kullanılabileceği için
@Volatile
ile depoya ek açıklama ekleyin (@Volatile
ayrıntılı olarak burada açıklanmıştır).
Kodunuz aşağıda gösterildiği gibi olmalıdır.
object ServiceLocator {
private var database: ToDoDatabase? = null
@Volatile
var tasksRepository: TasksRepository? = null
}
Şu anda ServiceLocator
yapmanız gereken tek şey bir TasksRepository
iade işlemini nasıl yapacağınızı bilmektir. Bu işlem, mevcut bir DefaultTasksRepository
öğesini iade eder veya gerekirse yeni bir DefaultTasksRepository
sağlayıp iade eder.
Aşağıdaki işlevleri tanımlayın:
provideTasksRepository
: Mevcut bir depoyu sağlar veya yeni bir depo oluşturur. Bu yöntem, birden fazla ileti dizisinin çalıştığı durumlarda, yanlışlıkla iki kod deposu örneği oluşturmaktan kaçınmak içinthis
üzerindesynchronized
olmalıdır.createTasksRepository
: Yeni bir kod deposu oluşturma kodu.createTaskLocalDataSource
numaralı telefonu arayıp yeni birTasksRemoteDataSource
oluşturacak.createTaskLocalDataSource
: Yeni bir yerel veri kaynağı oluşturmak için kod.createDataBase
aranacak.createDataBase
: Yeni veritabanı oluşturmak için kullanılan kod.
Tamamlanan kod aşağıdadır.
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: ServiceLocator'ı Uygulamada Kullanma
Depoyu tek bir yerde (ServiceLocator
) oluşturmak için ana uygulama kodunuzda (testleriniz değil) değişiklik yapacaksınız.
Kod deposu 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 Bulucu'yu kullanacaksınız.
- Paket hiyerarşinizin en üst düzeyinde,
TodoApplication
uygulamasını açın ve veri deponuz için birval
oluşturun veServiceLocator.provideTaskRepository
kullanı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 eski getRepository
yöntemini DefaultTasksRepository
bölümünden kaldırabilirsiniz.
DefaultTasksRepository
nesnesini 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
adresini kullandığınız her yerde, uygulamanın taskRepository
uygulamasını kullanın. Bu, kod deposunu doğrudan yapmak yerine, ServiceLocator
tarafından sağlanan depoyu almanızı sağlar.
TaskDetailFragement
uygulamasını açın ve sınıfın üst kısmındagetRepository
çağrısını bulun.- Bu aramayı,
TodoApplication
deposundan gelen bir aramayla 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ısını
TasksFragment
iç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)
}
StatisticsViewModel
veAddEditTaskViewModel
için, kod deposunu alan koduTodoApplication
tarihinden itibaren 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üzenlediğiniz için uygulama aynı şekilde sorunsuz bir şekilde çalışmalıdır.
3. Adım: FakeAndroidTestRepository Oluşturma
Test kaynağı grubunda zaten bir FakeTestRepository
var. Test sınıflarını varsayılan olarak test
ve androidTest
kaynak grupları arasında paylaşamazsınız. Dolayısıyla androidTest
kaynak grubunda yinelenen bir FakeTestRepository
sınıfı oluşturmanız ve bunu FakeAndroidTestRepository
olarak adlandırmanız gerekir.
androidTest
kaynak grubunu sağ tıklayın ve bir veri paketi oluşturun. Tekrar sağ tıklayın ve bir kaynak paketi oluşturun.- Bu kaynak paketinde
FakeAndroidTestRepository.kt
adlı 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 aracını testler için hazırlama
Tamam, test sırasında testlerin yerini almak için ServiceLocator
kullanma zamanı. Bunu yapmak için ServiceLocator
kodunuza bazı kodlar eklemeniz gerekir.
ServiceLocator.kt
uygulamasını açın.tasksRepository
için seter'ı@VisibleForTesting
olarak işaretleyin. Bu ek açıklama, ayarlayıcının herkese açık olmasının testten kaynaklandığını belirtmenin bir yoludur.
ServiceLocator.kt
@Volatile
var tasksRepository: TasksRepository? = null
@VisibleForTesting set
Testinizi ister tek başına ister test grubunda yapın, testleriniz tam olarak aynı şekilde çalışacaktır. Bu, testlerinizin birbirine bağlı olmayan davranışlara sahip olmaması gerektiği anlamına gelir (yani testler arasında nesne paylaşmaktan kaçınmanız gerekir).
ServiceLocator
bir tekil olduğu için testler arasında yanlışlıkla paylaşılma olasılığı vardır. Bunu önlemeye yardımcı olmak için testler arasındaki ServiceLocator
durumunu uygun şekilde sıfırlayan bir yöntem oluşturun.
Any
değerini kullanaraklock
adlı bir örnek değişkeni ekleyin.
ServiceLocator.kt
private val lock = Any()
- Veritabanını temizleyen ve hem depo hem de veritabanını boş olarak ayarlayan
resetRepository
adlı, teste özel bir yöntem 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ılır.
TaskDetailFragmentTest
uygulamasını açın.lateinit TasksRepository
değişkeni tanımlayın.- Her testten önce bir
FakeAndroidTestRepository
ayarlamak ve her testten sonra cihazı temizlemek için bir kurulum ve ayırma 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()
fonksiyonunun işlev gövdesinirunBlockingTest
içinde sarmalayın.- Parçayı başlatmadan önce depoya
activeTask
kaydedin.
repository.saveTask(activeTask)
Son test, aşağıdaki koda benzer.
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)
}
@ExperimentalCoroutinesApi
ile tüm sınıfa ek açıklama ekleyin.
İşlem tamamlandığında kod şöyle görünecektir.
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.
Tıpkı daha önce olduğu gibi, bu bölümü hariç tutmalısınız. Çünkü kod deposunu doğru şekilde oluşturduğunuz için şimdi görev bilgilerini gösteriyor.
Bu adımda, ilk entegrasyon testinizi tamamlamak için Espresso kullanıcı arayüzü test kitaplığını kullanacaksınız. Kodunuzu, kullanıcı arayüzünüz için onaylamalarla testler gönderecek şekilde yapılandırdınız. Bunu yapmak için Espresso test kitaplığını kullanın.
Espresso'nun sunduğu yardım:
- Düğmeleri tıklamak, çubuğu kaydırmak veya ekranı aşağı kaydırmak gibi görünümlerle etkileşimde bulunun.
- Belirli görünümlerde veya belirli bir durumda (örneğin, belirli bir metni içeren ya da bir onay kutusunun işaretli olduğu vb.) olduğunu iddia etme.
1. Adım: Not Grad Bağımlılığı
Varsayılan olarak Android projelerine dahil edildiği için ana Espresso bağımlılığınız zaten var.
uygulama/derleme.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 dahil edilir. Raporda çoğu görüntüleme ve işlem için temel test kodu yer alır.
2. Adım: Animasyonları kapatma
Espresso testleri gerçek bir cihazda çalıştırıldığı için doğası gereği enstrümantasyon testleridir. Ortaya çıkan sorunlardan biri, animasyonlardan oluşuyor: Bir animasyonun gevşeyip ekranda hâlâ bir animasyon olup olmadığını test etmeye çalışıyorsanız bu işlem Espresso'nun yanlışlıkla başarısız olmasına yol açabilir. Bu durum, Espresso testlerinin güvenilir olmamasına neden olabilir.
Espresso kullanıcı arayüzü testlerinde en iyi uygulama, animasyonları kapatmaktır (testiniz daha hızlı tamamlanır!):
- Test cihazınızda Ayarlar ve 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 göz atın
Espresso testi yazmadan önce birkaç Espresso koduna göz atın.
onView(withId(R.id.task_detail_complete_checkbox)).perform(click()).check(matches(isChecked()))
Bu ifade, task_detail_complete_checkbox
kimliğine sahip onay kutusu görünümünü bulup tıklar ve ardından işaretli olduğunu doğrular.
Espresso ifadelerinin çoğunluğu dört bölümden oluşur:
onView
onView
, Espresso ifadesi başlatan statik bir Espresso yöntemi örneğidir. onView
en yaygın seçeneklerden biridir, ancak onData
gibi başka seçenekler de mevcuttur.
2. ViewMatcher ise
withId(R.id.task_detail_title_text)
withId
, kimliğine göre görüntüleme alan bir ViewMatcher
örneğidir. Belgelerde bulabileceğiniz başka görünüm eşleştiriciler vardır.
3. ViewAction
perform(click())
ViewAction
süren perform
yöntemi. ViewAction
, görünüme yapılabilecek bir şeydir (örneğin, burada görünümü tıklar).
check(matches(isChecked()))
check
ViewAssertion
kullanır. ViewAssertion
, görünümle ilgili bir şeyi kontrol eder veya bununla ilgili bir iddiada bulunur. Kullanacağınız en yaygın ViewAssertion
matches
onayıdır. Doğrulama işlemini tamamlamak için bu örnekte başka bir ViewMatcher
kullanın isChecked
.
Espresso ifadesinde her zaman perform
ve check
işaretlemediğinizi unutmayın. Yalnızca check
kullanarak iddiada bulunan ifadeler kullanabilir veya perform
kullanarak yalnızca ViewAction
yapabilirsiniz.
TaskDetailFragmentTest.kt
uygulamasını açın.activeTaskDetails_DisplayedInUi
testini 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 beyanlarını aşağıda görebilirsiniz:
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
// THEN
yorumundan sonraki her şey Espresso'yu kullanıyor. Test yapısını vewithId
kullanımını inceleyin ve ayrıntı sayfasının nasıl görünmesi gerektiğine dair iddialarda bulunmak için kontrol edin.- Testi çalıştırın ve başarılı olduğunu doğrulayın.
4. Adım. İsteğe bağlı, kendi Espresso Testinizi yazın
Şimdi kendiniz bir test yazın.
completedTaskDetails_DisplayedInUi
adında 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 test geçişlerini onaylayın.
Tamamlanan completedTaskDetails_DisplayedInUi
bu 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, Gezinme bileşeninin, bir örnek çift olarak adlandırılan farklı bir test türünü ve Mockito test kitaplığını kullanarak nasıl test edileceğini öğreneceksiniz.
Bu codelab'de, sahte adı verilen bir test çift kullandınız. Sahtekarlıklar, birçok test çiftinden biridir. Gezinme bileşenini test etmek için hangi test çiftini kullanmanız gerekir?
Gezinmenin nasıl gerçekleştiğini düşünün. Bir görev ayrıntıları ekranına gitmek için TasksFragment
görevlerinden birine bastığınızı düşünün.
TasksFragment
cihazında bu tuşa basıldığında görev ayrıntıları ekranına giden kod.
TasksFragment.kt
private fun openTaskDetails(taskId: String) {
val action = TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment(taskId)
findNavController().navigate(action)
}
Gezinme yöntemi navigate
yöntemine yapılan bir çağrı nedeniyle gerçekleşmiş. Bir iddia beyanı yazmanız gerekiyorsa TaskDetailFragment
uygulamasına gidip gitmediğinizi test etmenin kolay bir yolu yok. Gezinme, TaskDetailFragment
öğesini başlatmanın ötesinde net bir çıkış veya durum değişikliğine yol açmayan karmaşık bir işlemdir.
navigate
yönteminin doğru işlem parametresiyle çağrıldığını iddia edebilirsiniz. Örnek testte tam olarak bu şekilde yapılır ve belirli yöntemlerin çağrılıp çağrılmadığını kontrol eder.
Mockito testler için bir çerçevedir. API'de ve adda sahte kelime kullanılmış olsa da yalnızca taklit yapmak için kullanılmaz. Ayrıca koçanlara ve baharatlara neden olabilir.
Mockito'yu, gezinme yönteminin doğru şekilde çağrıldığını iddia edebilecek bir örnek NavigationController
yapmak için kullanacaksınız.
1. Adım: Grad Bağımlıları Ekle
- Gradle bağımlılıklarını ekleyin.
uygulama/derleme.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, Mockito'yu bir Android projesinde kullanmak için gereklidir. Mockito'nun çalışma zamanında sınıf oluşturması gerekiyor. Bu işlem Android'de dex bayt kodu kullanılarak gerçekleştirilir. Bu nedenle bu kitaplık, Mockito'nun Android'deki çalışma zamanında nesneler oluşturabilmesini sağlar.androidx.test.espresso:espresso-contrib
- Bu kitaplık,DatePicker
veRecyclerView
gibi daha gelişmiş görüntülemeler için test kodu içeren harici katkılardan (dolayısıyla addan) oluşur. Erişilebilirlik kontrollerini ve daha sonra ele alınanCountingIdlingResource
adlı sınıfı da içerir.
2. Adım: TasksFragmentTest Oluşturma
TasksFragment
'yi açın.TasksFragment
sınıf adını sağ tıklayın ve Oluştur'u, ardından Test'i seçin. androidTest kaynak grubunda bir test oluşturun.- Bu kodu
TasksFragmentTest
koduna 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. Bir FakeAndroidTestRepository
cihazını kurup yırtır. Görev listesinde bir görevi tıkladığınızda doğru TaskDetailFragment
hedefine ulaşacağınızı test etmek için bir gezinme testi ekleyin.
clickTask_navigateToDetailFragmentOne
testini ekleyin.
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's
mock
işlevini kullanın.
TasksFragmentTest.kt
val navController = mock(NavController::class.java)
Mockito ile alay etmek için taklit etmek istediğiniz dersi geçin.
Şimdi, NavController
parçasını parçayla ilişkilendirmeniz gerekir. onFragment
, parçanın kendisinde yöntemler çağırmanıza olanak tanır.
- Yeni modeliniz parçanın
NavController
olsun.
scenario.onFragment {
Navigation.setViewNavController(it.view!!, navController)
}
- Kodu,
RecyclerView
içinde "TITLE1" metnini içeren öğeyi tıklamak için 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 gerçekleştirmenize 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")
Bu modeli taklit eden model verify
yöntemidir. Bu nedenle, belirli bir yöntemin (navigate
) adı verilen navController
parametresini, parametre olarak "actionTasksFragmentToTaskDetailFragment
" kimliğine sahip navController
ile doğrulayabilirsiniz.
Testin tamamı şöyle 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:
- Mockito'yu kullanarak
NavController
sahtesi oluşturabilirsiniz. - Parçalı
NavController
adlı parçayı parçaya ekleyin. - Gezinmenin doğru işlem ve parametrelerle çağrıldığını doğrulayın.
3. Adım: İsteğe bağlı olarak clickAddTaskButton_naviToAddEditFFment yazın
Kendiniz bir gezinme testi yapıp yapamayacağınızı görmek için bu görevi deneyin.
- + FAB'ı tıklarsanız
AddEditTaskFragment
sayfasına gidip gitmediğinizi kontrol edenclickAddTaskButton_navigateToAddEditFragment
testini yazın.
Yanıt aşağıdadır.
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 nihai kod arasındaki farkı görmek için burayı tıklayın.
Tamamlanan 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, veri havuzunu Zip dosyası olarak indirip sıkıştırılmış dosyayı Android Studio'da açabilirsiniz.
Bu codelab'de, manuel bağımlılık yerleştirme, hizmet bulma aracı ve Android Kotlin uygulamalarınızda sahte ve sahte ürünlerin nasıl kullanılacağı ele alındı. Özellikle:
- Neyi test etmek istediğiniz ve test stratejiniz, uygulamanız için hangi test türünü uygulayacağınıza karar verir. 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 kaliteyi taşır, genellikle enstrümantasyon aşamasındadır ve bu testlerin yürütülmesi daha uzun sürebilir.
- Uygulamanızın mimarisi, test etmenin zorluğunu etkiler.
- TDD veya Test Sürücüsü Geliştirme, önce testleri yazıp ardından bu testi geçerken özelliği oluşturmak için kullanılan bir stratejidir.
- Uygulamanızın bölümlerini test etmek için izole etmek amacıyla test ikilisi kullanabilirsiniz. Test çift, test için özel olarak tasarlanmış bir sınıf sürümüdür. Örneğin, bir veritabanından veya internetten veri alıyormuş gibi davranırsınız.
- Gerçek bir sınıfı kod deposu veya ağ katmanı gibi bir test sınıfıyla değiştirmek için bağımlılık ekleme özelliğini kullanın.
- Kullanıcı arayüzü bileşenlerini başlatmak için yerleşik testi (
androidTest
) kullanın. - Örneğin bir parçayı kullanıma sunmak için yapıcı bağımlı enjeksiyonunu kullanamadığınızda genellikle bir hizmet bulma aracı kullanabilirsiniz. Hizmet Bulucu kalıbı, Bağımlı Enjeksiyonu için alternatiftir. Amacı, hem normal hem de test kodu için bağımlılık sağlamak olan "Service Locator" adlı tekli sınıf oluşturmayı içerir.
Udacity kursu:
Android geliştirici dokümanları:
- Uygulama mimarisi rehberi
runBlocking
verunBlockingTest
FragmentScenario
- Kahve makinesi
- Örnek
- JUnit4
- AndroidX Test Kitaplığı
- AndroidX Mimari Bileşenleri Temel Test Kitaplığı
- Kaynak kümeleri
- Komut satırından test etme
Videolar:
Diğer:
Bu kurstaki diğer codelab'lerin bağlantıları için Kotlin codelab'lerdeki Gelişmiş Android açılış sayfasına bakın.