Test Çiftleri ve Bağımlılık Eklemeye Giriş

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:

Zip'i İndir

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:

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: com.example.android.architecture.blueprints.todoapp

.addedittask

Görev ekranı ekleme veya düzenleme: Görev eklemek veya düzenlemek için kullanıcı arayüzü katman kodu.

.data

Veri katmanı: Görevlerin veri katmanıyla ilgilidir. Veritabanı, ağı ve depo kodunu içerir.

.statistics

İstatistik ekranı: İstatistik ekranı için kullanıcı arayüzü katman kodu.

.taskdetail

Görev ayrıntıları ekranı: Tek bir görev için kullanıcı arayüzü katman kodu.

.tasks

Görevler ekranı: Tüm görevlerin listesi için kullanıcı arayüzü katman kodu.

.util

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:

  1. Öncelikle depoda birim test edeceksiniz.
  2. 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.
  3. Daha sonra parçalar ve görünüm modelleri için entegrasyon testleri yazmayı öğreneceksiniz.
  4. 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. StubTaskRepository, örneğin getTasks öğesinden belirli görev kombinasyonlarını döndürecek şekilde programlanabilir.

Sahte

Geçilen ancak kullanılmayan bir parametre çifti (örneğin, bir parametre olarak sağlamanız gerekiyorsa). Bir NoOpTaskRepository kullanıyorsanız TaskRepository yönteminin her iki yöntemde de no kodu olmadan uygulanması gerekir.

Casusluk

Bazı ek bilgileri izleyen bir test testi de yapılır. Örneğin, bir SpyTaskRepository yaparsanız addTask yönteminin çağrılma sayısı izlenebilir.

İ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.

  1. test kaynak kümesinde sağ tıklayın Yeni -> Paketi'ni seçin.

  1. İçinde source paketi bulunan bir veri paketi oluşturun.
  2. 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.

  1. 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 { ... }
  1. FakeDataSource uygulamasını TasksDataSource uygulayın:
class FakeDataSource : TasksDataSource {

}

Android Studio, TasksDataSource için gerekli yöntemleri uygulamadığınızdan şikayet edecektir.

  1. Hızlı düzeltme menüsünü kullanarak Üyeleri uygula'yı seçin.


  1. 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.
  1. FakeDataSource oluşturucuyu, tasks boş değerli bir varsayılan liste değeri olan MutableList<Task>? olan bir var 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:

  1. getTasks yazın: tasks null ise Success sonucunu döndürün. tasks null ise Error sonucunu döndürün.
  2. deleteAllTasks yazın: değiştirilebilir görevler listesini temizleyin.
  3. 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

  1. DefaultTaskRepository oluşturucusunu Application 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 }
  1. Bağımlıları ilettiğiniz için init yöntemini kaldırın. Artık bağımlılık oluşturmanız gerekmez.
  2. 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
  1. 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.

  1. DefaultTasksRepository sınıf adını sağ tıklayın ve Oluştur'u, ardından Test'i seçin.
  2. Test kaynak grubunda DefaultTasksRepositoryTest oluşturmak için talimatları uygulayın.
  3. 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 }
  1. Üç değişken, iki FakeDataSource üye değişkeni (Deponuz için her veri kaynağı için bir tane) ve DefaultTasksRepository 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.

  1. createRepository adlı bir yöntem oluşturun ve bu yöntemi @Before ile ekleyin.
  2. remoteTasks ve localTasks listelerini kullanarak sahte veri kaynaklarınızı örneklendirin.
  3. Kısa süre önce oluşturduğunuz iki sahte veri kaynağını ve Dispatchers.Unconfined özelliğini kullanarak tasksRepository 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ı!

  1. Deponun getTasks yöntemi için bir test yazın. true ile getTasks öğ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.

  1. 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.

  1. @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.
  2. DefaultTasksRepositoryTest'yi runBlockingTest'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))
    }

}
  1. 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.

  1. 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.

  1. Dosyayı çıkarmak için ayıkla'yı seçin.

  1. Ayıklama Arayüzü penceresinde arayüz adını TasksRepository olarak değiştirin.
  2. Forma katılan üyeler bölümünde, iki tamamlayıcı üye ve gizli yöntemler hariç tüm üyeleri işaretleyin.


  1. Yeniden düzenle'yi tıklayın. Yeni TasksRepository arayüzü, veri/kaynak paketinde görünecektir.

DefaultTasksRepository artık TasksRepository özelliğini uyguluyor.

  1. 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.

  1. Test kaynağı grubunda, data/source dosyasında Kotlin dosyasını ve FakeTestRepository.kt sınıfını oluşturun ve TasksRepository arayüzünden genişletin.

FakeTestRepository.kt

class FakeTestRepository : TasksRepository  {
}

Arayüz yöntemlerini uygulamanız gerektiği bildirilir.

  1. Öneri menüsünü görene kadar fareyle hatanın üzerine gelin ve ardından Üyeleri uygula'yı seçip seçin.
  1. 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.

  1. FakeTestRepository öğesinde, hem mevcut görev listesini temsil eden bir LinkedHashMap değişkeni hem de gözlemlenebilir görevleriniz için bir MutableLiveData 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:

  1. getTasks: Bu yöntem, tasksServiceData değerini alıp tasksServiceData.values.toList() kullanarak bir listeye dönüştürmeli ve daha sonra bunu Success sonucu olarak döndürmelidir.
  2. refreshTasks - observableTasks değerini, getTasks() tarafından döndürülen değer olarak günceller.
  3. observeTasks: runBlocking kullanılarak bir eş yordam oluşturulur, refreshTasks çalıştırılır, ardından observableTasks 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.

  1. vararg görevden oluşan, her birini HashMap'ye ekleyen ve ardından görevleri yenileyen addTasks 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.

  1. TasksViewModel uygulamasını açın.
  2. TasksRepository sınıfında yapmak için TasksViewModel 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.

  1. TasksViewModel dosyasının alt kısmına, sınıfın dışında, düz TasksRepository ifadesini alan bir TasksViewModelFactory 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.

  1. 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))
}
  1. 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.

  1. TasksViewModelTest uygulamasını açın.
  2. TasksViewModelTest içine bir FakeTestRepository 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
}
  1. Üç görevle FakeTestRepository yapmak için setupViewModel yöntemini güncelleyin, ardından tasksViewModel 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)
        
    }
  1. AndroidX Testi ApplicationProvider.getApplicationContext kodunu artık kullanmadığınız için @RunWith(AndroidJUnit4::class) ek açıklamasını da kaldırabilirsiniz.
  2. 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.

  1. TaskDetailViewModel uygulamasını açın.
  2. 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 }
  1. TaskDetailViewModel dosyasının altına, sınıfın dışında bir TaskDetailViewModelFactory 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)
}
  1. 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))
}
  1. 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

  1. 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.

  1. taskdetail.TaskDetailFragment uygulamasını açın.
  2. 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.

  1. 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.

  1. 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:

Henüz bir şey iddia etmediğinden bu test henüz tamamlanmadı. Şimdilik testi çalıştırın ve neler olduğunu gözlemleyin.

  1. Bu bir enstrümantasyon testidir, bu yüzden emülatör veya cihazınızın görünür olduğundan emin olun.
  2. 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:

  1. Kod deposu oluşturabilen ve depolayabilen bir Hizmet Bulucu sınıfı oluşturun. Varsayılan olarak "normal" bir depo oluşturur.
  2. Kod deposuna ihtiyacınız olduğunda Hizmet Bulucu'yu kullanabilmeniz için kodunuzu yeniden düzenleyin.
  3. 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.

  1. Ana kaynak grubunun en üst düzeyinde ServiceLocator.kt dosyasını oluşturun.
  2. ServiceLocator adlı bir object tanımlayın.
  3. database ve repository örnek değişkenlerini oluşturun ve her ikisini de null olarak ayarlayın.
  4. 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:

  1. 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çin this üzerinde synchronized olmalıdır.
  2. createTasksRepository: Yeni bir kod deposu oluşturma kodu. createTaskLocalDataSource numaralı telefonu arayıp yeni bir TasksRemoteDataSource oluşturacak.
  3. createTaskLocalDataSource: Yeni bir yerel veri kaynağı oluşturmak için kod. createDataBase aranacak.
  4. 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.

  1. Paket hiyerarşinizin en üst düzeyinde, TodoApplication uygulamasını açın ve veri deponuz için bir val oluşturun ve ServiceLocator.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.

  1. 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.

  1. TaskDetailFragement uygulamasını açın ve sınıfın üst kısmında getRepository çağrısını bulun.
  2. 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)
}
  1. 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)
    }
  1. StatisticsViewModel ve AddEditTaskViewModel için, kod deposunu alan kodu TodoApplication 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

  1. 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.

  1. androidTest kaynak grubunu sağ tıklayın ve bir veri paketi oluşturun. Tekrar sağ tıklayın ve bir kaynak paketi oluşturun.
  2. Bu kaynak paketinde FakeAndroidTestRepository.kt adlı yeni bir sınıf oluşturun.
  3. 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.

  1. ServiceLocator.kt uygulamasını açın.
  2. 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.

  1. Any değerini kullanarak lock adlı bir örnek değişkeni ekleyin.

ServiceLocator.kt

private val lock = Any()
  1. 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.

  1. TaskDetailFragmentTest uygulamasını açın.
  2. lateinit TasksRepository değişkeni tanımlayın.
  3. 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()
    }
  1. activeTaskDetails_DisplayedInUi() fonksiyonunun işlev gövdesini runBlockingTest içinde sarmalayın.
  2. 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)

    }
  1. @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)

    }

}
  1. 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!):

  1. Test cihazınızda Ayarlar ve Geliştirici seçenekleri'ne gidin.
  2. Ş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:

1. Statik Espresso yöntemi

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).

4. Görünüm Onayı

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.

  1. TaskDetailFragmentTest.kt uygulamasını açın.
  2. 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
  1. // THEN yorumundan sonraki her şey Espresso'yu kullanıyor. Test yapısını ve withId kullanımını inceleyin ve ayrıntı sayfasının nasıl görünmesi gerektiğine dair iddialarda bulunmak için kontrol edin.
  2. 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.

  1. 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
}
  1. Önceki teste bakarak bu testi tamamlayın.
  2. Ç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

  1. 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 ve RecyclerView 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ınan CountingIdlingResource adlı sınıfı da içerir.

2. Adım: TasksFragmentTest Oluşturma

  1. TasksFragment'yi açın.
  2. 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.
  3. 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.

  1. 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)
        
    }
  1. 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.

  1. Yeni modeliniz parçanın NavController olsun.
scenario.onFragment {
    Navigation.setViewNavController(it.view!!, navController)
}
  1. 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.

  1. 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")
    )
}
  1. Testinizi çalıştırın.

Özetle, gezinmeyi test etmek için:

  1. Mockito'yu kullanarak NavController sahtesi oluşturabilirsiniz.
  2. Parçalı NavController adlı parçayı parçaya ekleyin.
  3. 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.

  1. + FAB'ı tıklarsanız AddEditTaskFragment sayfasına gidip gitmediğinizi kontrol eden clickAddTaskButton_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.

Zip'i İndir

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ı:

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.