Flutter'un sağladığı 3 test tipi vardır. Bunlar;
Güven seviyesi, bakım maliyeti, bağımlılıklar ve yürütme hızı bakımından farklılıkları da şu şekildedir.
Bu bölümde Unit Test'lerin ne olduğu, özellikleri, yapısı ve daha bir çok özelliğini inceleyeceğiz. Hadi başlayalım!
Unit Test; Tek bir işlevi (single function), yöntemi (method) veya sınıfı (class) ayrı ayrı test etmemizi sağlayan ve beklenen değeri ürettiğinden emin olmanın doğal bir yoludur.
Hataları simüle etmede ve hata durumlarının doğru şekilde ele alındığını kontrol etmede en uygun yöntemleri sunar.
En hızlı ve maliyeti en düşük test türüdür. Çok kısa bir süre içerisinde yüzlerce hatta binlerce test durumunu kontrol edebilirsiniz.
Yazılımdaki parçaların birbirleriyle nasıl bütünleştiğine ve iş birliğinde çalıştığına değil, küçük kod parçalarına odaklandıkları için tam anlamıyla güven sağlamazlar. Ancak bunu sağlamak görevleri de değildir.
Birim testleri genel olarak 3 aşamaya ayrılır.
Pre Test: Test edilecek kısım için gerekli olabilecek başlangıçların yapıldığı aşamadır. setUpAll ve setUp metotları kullanılarak gerçekleştirilir.
- setUp: Her test senaryosundan önce çalıştırılması gereken işlemleri içerir.
- setUpAll: Tüm test durumları yürütülmeden önce bir kez çalışır.
Test: Asıl testin yürütüldüğü test aşamsı burasıdır.
- test: Asıl test aşamasıdır.
Post Test: Mock değerleri sıfırlamak için kullanılan test sonrası aşamasıdır.
- tearDown: Her test senaryosundan sonra çalıştırılması gereken işlemleri içerir.
- tearDownAll: Tüm test durumları yürütüldükten sonra bir kez çalışır.
test paketi, Unit testleri yazmak için temel frameworkü sağlar. flutter_test paketi ise, widget'ları da test etmenizi sağlayacak için ek araçlar sağlar.
Bundan dolayı test sayfanızda flutter_test paketi aşağıdaki gibi ekli olmalıdır.
import 'package:flutter_test/flutter_test.dart';
Bu paketi test sayfanızda kullanabilmek için de, pubspec.yaml dosyasında dev_dependencies altında flutter_test paketi eklenmiş olmalıdır. Flutter projesi oluşturulduğunda varsayılan olarak bu paket eklenmiş olarak gelmektedir.
Testlerinizi favori IDE'niz üzerinden aşağıdaki gibi çalıştırabilirsiniz.
IntelliJ/Android Studio
VSCode
Bir diğer seçenekte, komut satırı üzerinden çalıştırmaktır.
Flutter test ile ilgili ihtiyaç duyabileceğiniz tüm komutları
flutter test --help
komutu ile öğrenebilirsiniz.
Örneğin;
Terminalden çalıştıracağınız flutter test
komutu ile test klasörü içerisindeki tüm testleri koşturabilirsiniz.
Eğer belirli bir dosyayı test etmek istiyorsanız;
flutter test test/core/hesapla_test.dart
Projenizdeki Unit Test Coverage raporunu görmek istiyorsanız;
flutter test --coverage test
komutunu kullanabilirsiniz.
Flutter, Coverage kapsama verilerini göstermek için lcov.info dosyasını kullanır. Komut satırından coverage alma komutunu çalıştırdığınıda proje ana dizini altında "coverage" klasörü ve onun altında "lcov.info" dosyası oluşur. Bu dosyayı kullanarak raporun HTML olarak çıktısını da oluşturmanız mümkün.
Bunu kullanmak için macOS'ta sisteminizde lcov (brew install lcov
) kurulu olmalıdır. Daha sonra aşağıdaki komutu çalıştırabilirisiniz.
genhtml coverage/lcov.info -o coverage/html
Raporu komut satırından açmak için;
open coverage/html/index.html
Flutter'da tüm test dosyaları, projenizin altındaki test dizini altına yerleştirilmeli ve mutlaka _test.dart ile bitmelidir.
Test dosyalarınızı oluştururken, projenizin lib dizin yapısını kullanmak iyi bir alışkanlıktır. Böylece ilgili test dosyalarına bulmanız da kolaylaşır.
Bir test fonksiyonu Given, When, Then pattern adı verilen 3 temel bölüme ayrılır.
Given: Sistemin başlangıç durumunu tanımlar.
When: Yapılacak aksiyonu tanımlamak için kullanılır. Yapılmak istenen asıl eylem, bu bölümde yazılır.
Then: Beklenen durumla gerçekleşen durumu karşılaştırdığımız bölümdür.
Unit Test'lerdeki beklenen sonuçların karşılaştırması Given | When | Then pattern'inin Then adımında gerçekleşir.
Örneğimizde expect metodu ile Hesapla class'ına ait topla metoduyla bir işlem gerçekleşmiş, bu işlemin sonunda elde edilen sonuç ile beklenen sonuç karşılaştırılmıştır. Eğer sonuç ve beklenen aynı ise test başarılı olacaktır.
Testin başarısız olma durumunda ise kullanıcıya ek bilgi sağlayabiliriz.
Bunun için örneğimizi aşağıdaki gibi güncelleyelim.
Yukarıda da görüldüğü gibi result değeri 5 iken, 6 ile karşılaştırıldığından dolayı test fail edecek ve reason bölümünde belirttiğimiz "Sonuçlar eşit değil." mesajını bize ek bilgi olarak gösterecektir. reason bölümünde belirttiğiniz hata mesajının yalnızca testin fail olması durumunda ortaya çıkacağını unutmayın.
Kontrol etmek için örneğimizi aşağıdaki gibi güncelleyelim.
Görüldüğü gibi test geçerli olduğu için reason mesajı görüntülenmedi.
Ancak testi geçerli kılmak için isNot ve equals isimli Eşleştirici/Matcher'ları kullanarak doğruladık.
Son örneğimizde isNot ve equal matcher'larını kullandığımızı belirttik. Bu iki matcher'ı kullanarak 5 ile 6'nın eşit olmadıklarını kontrol etmek istedik. Test yazımı sırasında da basit iki nesnenin karşılaştırılmasının ötesinde daha karmaşık doğrulama testlerine ihtiyaç olur. Tam da bu nokta da matcher'lar kullanılır.
Dart, test frameworkünde bulunan dahili bir Matcher kitaplığı sağlar. Tüm özelliklerine buradan göz atabilirsiniz.
Bunun yanında Streams ile çalışırken karmaşık doğrulama yapmak için bir StreamMatcher, kendi eşleştiricinizi geliştirmeniz gerekiyorsa, CustomMatcher adlı bir yardımcı program sınıfı mevcuttur.
Şimdiye kadarki örneğimizde tek bir metodun test edilmesi ile çalıştık. Birden çok test içeren bir test dosyasına sahip olmak oldukça yaygındır.
Buna göre test örneğimizi aşağıdaki gibi düzenleyerek kullanabiliriz.
Bazen kodunuzun, beklenen veya geçersiz bir durumu hesaba katmak için yürütülmesi sırasında bir istisna atması gerekir.
Hesapla isimli class'ımızda bulunan "bol" fonksiyonunu kullanarak bunu sağlayabiliriz. Bunun için bu fonksiyonu düzenleyerek içerisinde bir Argument hatası fırlatalım.
Görüldüğü üzere bu işlemi gerçekleştirdiğimizde testimiz doğru şekilde çalışıyor ancak fail alıyor. Bunun önüne geçip, hem hata bilgisini vermesi hem de testin pass olması için, gerçekleşen durumu anonim bir fonksiyon ile sarmalayacağız. Böylece fonksiyon kodu dahili bir try-catch bloğunda çalışabilecek ve hatayı matcher'ımız ile karşılaştırabilecektir.
Bunun için kodumuzu aşağıdaki gibi düzenleyerek, tekrar çalıştıralım.