(TR) .Net Core ile xUnit Test (In-Memory DB, Swagger & JWT Authentication)

Kemal AKCIL
6 min readJan 30, 2021

--

You can access the English version of this article I wrote at this link.

Projenin tamamına https://github.com/Kakcil/xunit-test-sample-dotnet-core adresinden ulaşabilirsiniz.

presentation

Giriş:

Bu makalede .Net Core (3.1) projelerinde xUnit ile test işlemlerini ileri düzeyde nasıl gerçekleştireceğimizi inceleyeceğiz. İlişkisel tablolarımız (country,city,district) ve kimlik doğrulamak için kullanıcı (user) tablomuz bulunmaktadır. Bu tablolarımızı bellek içi veritabanı’nda (in-memory database) tutacağız ve datalarımızı proje içerisinde statik olarak initialize ediyoruz. Daha sonra test projemizle bu verileri test ediyoruz. Hazır proje üzerinden adım adım anlatmaya çalışacağım.

Projede Swagger kuruludur. Service projemizi çalıştırıp Swagger üzerinden önce login işlemini gerçekleştirip Bearer Token alalım. (Aldığımız tokenın 12345abcdef olduğunu farz edelim) Sayfanın sağ üst kısmındaki Authorize butonuna tıklayıp

açılan modal içinde “Bearer 12345abcdef’” şeklinde girip authorize olabilir ve test işlemleri öncesinde servis projenizin istediğimiz çıktıyı verip vermediğini görebiliriz.
Ayrıca “sample-service”in in-memory database’te olması xUnit ile alakalı bir durum değildir. Projenin çalışabilirliğini arttırmak için gerçek sunucu/veritabanı yerine bu alternatifi kullandık. Normal şartlar altında yalnızca test projemizin in-memory database kullanması yeterlidir. Test ettiği projenin hangi db’yi kullandığı xUnit’i ilgilendirmemektedir.
Başlayalım:

Bölüm 1: xUnit Hakkında

“xUnit.net, .NET Framework için ücretsiz, açık kaynaklı, topluluk odaklı bir birim test aracıdır. NUnit v2'nin orijinal mucidi tarafından yazılan xUnit.net, C #, F #, VB.NET ve diğer .NET dillerinin birim testi için en son teknolojidir. xUnit.net, ReSharper, CodeRush, TestDriven.NET ve Xamarin ile çalışır. .NET Vakfı’nın bir parçasıdır ve davranış kurallarına göre çalışır. Apache 2 (OSI onaylı bir lisans) altında lisanslanmıştır.”

xUnit ile fonksiyonel test gerçekleştireceğiz. Fonksiyonel test gerçekleştirmek, beklenen sonuçların alındığından emin olmak için yazılımın her bir işlevini kontrol etmeyi ve test etmeyi gerektirir.

Yazılım Testi Tipleri

Böyle bir hiyerarşi grafiği oluşturdum. Diğer tablo üyelerini, araştırmanın dışında olduğu için belirtmedim.

.Net Unit Test Tipleri

Yukarıdaki hiyerarşi grafiğinde görüldüğü üzere .Net’te temelde kullandığımız 3 adet unit test tipi vardır. xUnit’i kullanmamızın sebebi, diğerlerine nazaran daha avantajlı olduğu kanısında olmamızdır. Bu avantajlara bu makalede değinilmeyecektir, araştırabilirsiniz.

AAA Pattern’i Nedir?

İsmini, birazdan tanımlayacağım maddelerden almış olan bu Pattern sektörde neredeyse bir standart haline geldi. Bu, testimizi sırası ile üç bölüme ayırmamızı önerir: Düzenleme (Arrange), harekete geçme (Act) ve teyit etme (Assert). Her işlem sadece adını aldıkları kısımdan sorumludur.

Arrange: Gerekli tüm ön koşulları ve girdileri düzenlemek ve hazır hale getirmek.
Act: Sınıfta bulunan test senaryosuna uygun davranışı harekete geçirmek.
Assert: Beklenen sonucun gerçekleşip gerçekleşmediğini teyit etmek.

Mocking Nedir?

Bu da mutlaka bilinmesi gereken başka bir kavram. Test edilen bir nesnenin, birçok nesne ile bağımlılıkları olabilir. Bu bağımlılıkları izole etmek için, bazı gerçek nesneleri, onların davranışlarını taklit eden (izole edilmiş) nesnelerle değiştirmek gerekir. Mesela log gönderimi yapan bir servisimizi, bir metodu test ederken kullanmak zorunda kaldığımız senaryosunu inceleyelim: Bu log servisimizi mocklayarak onun gibi gözüken ancak log işlemini gerçekleştirmeyen sahte (mock) servis oluşturuyoruz ve test işleminde bunu kullanıyoruz.

xUnit Öznitelikleri Nelerdir?

[Fact] : Yazmış olduğumuz metodun test çalıştırıcısı tarafından yürütülmesi gerektiğini ve test metoduna hiçbir yöntemle parametre göndermeyeceğimizi belirtir.
[Theory] : Test kodumuza bazı parametreler göndereceğimizi ifade eder. Bu parametreleri temelde aşağıda açıklayacağım 3 yöntem ile gönderebiliriz.
[Theory, InlineData] : Test edilecek metodumuz parametre olarak bir class yerine string, int vb. tipinde değerler alıyorsa bu attribute’ü kullanıyoruz.
[Theory, ClassData] : Test edilecek metodumuz parametre olarak bir class alıyorsa bu attribute’ü kullanıyoruz.
[Theory, MemberData] : Test edilecek metodumuz parametre olarak bir class yerine string, int vb. tipte değerler alıyorsa bu attribute’u de kullanabiliriz. Çoklu veri test etme işlemlerinde InlineData yerine kullanılabilinir. Zaruri değildir.

Fixture (Bağlama) Nedir?

Test projemizin içinde “Fixture” adında bir klasörümüz bulunacaktır. Bunu biz oluşturuyoruz. Bu klasörün içerisinde test projemiz ile test edilecek projemizi birbirine bağlayacak işlemleri gerçekleştireceğiz. Ana projemiz servisler içermektedir ve bu servisleri “Fixture” klasörü altında “ServiceFixture.cs” dosyasındaki işlemlerle birbirine bağlayacağız. Ayrıca mockunu oluşturduğumuz veri tabanına da dataları burada initialize edeceğiz. Test projemizin db’sine manuel olarak ekleyeceğimiz veriler test edeceğimiz projeye oldukça yakın hatta mümkünse birebir olmalıdır.

Bölüm 2: Proje Kurulumu ve Klasör Yapısı

Kurulum:

Adım 1
Adım 2

2 adımda test projemizi oluşturuyoruz.

Test projemizle, test edeceğimiz proje arasındaki bağlantıları kurmak için öncelikle test projemize “Project Reference” seçip ekliyoruz.

Project Reference

Klasör Yapısı:

sample-service.csproj

“sample-service” projesinde:

  • ViewModel gibi de düşünebileceğimiz custom olarak oluşturduğumuz Data Transfer Objelerimiz (Dtos) bulunmakta.
  • “Entities” klasörümüzde DB Modellerimiz ve verileri statik olarak initialize ettiğimiz “DbInitializer.cs” dosyamız bulunmaktadır. Ayrıca “SampleDbContext.cs” dosyamızda veri tabanı ayarlarımız bulunmaktadır.
  • “Helpers” klasöründe proje içerisinde ihtiyacımız olan metotlar ve propertyler bulunmaktadır.
  • “Services” klasöründe Controller’larımızda kullandığımız servislerimiz bulunmaktadır.
sample-service-test.csproj

“sample-service-test” projesinde:

  • “Fixture” klasöründe servislerle ve veritabanı ile bağlamaları gerçekleştiriyoruz. Controller’lar ile bağlamaları ise testleri yazdığımız sınıflarda (…ControllerTest.cs) gerçekleştireceğiz. İsimlendirmelerimiz (naming convention) gördüğümüz üzere ControllerAdıControllerTest.cs şeklindedir. Kural değildir ancak örnek alınabilinir.
MockAuthorize.cs
  • “Mock” klasöründe makalenin başında anlattığımız mocking işlemlerini yapıyoruz.
    Mesela: MockAuthorize.cs dosyasında JWT Authentication işlemlerini gerçekleştiriyoruz. Böylece testlerimiz sample-service projesindeki Controller’larımızdan geçerken “[Authorize]” attribute’ü olan her yere erişebileceklerdir.

Bölüm 3: Test İşlemleri

Örnek olarak CityControllerTest.cs dosyamızı inceleyelim:

CityControllerTest.cs

“CityControllerTest” adlı classımızı “IClassFixture<ServiceFixture>”dan kalıtıyoruz.
“sample-service” projemizdeki “CityController” tipinden bu classta “CityController” adında bir property üretiyoruz.
Daha sonra yapıcı fonksiyonumuzda (Constructor) bu property’e new CityController(fixture.CityService) diyerek parametre olarak “sample-service” projesindeki CityController’ın yapıcı fonksiyonunda istediği CityService parametresini gönderiyoruz. Bu “CityService” parametresi test projemizdeki ServiceFixture.cs dosyasında tanımladığımız “CityService”dir.

Hemen altında CityController.Authorize() diyerek JWT Authentication işlemimizi gerçekleştiriyoruz.

Fixture işlemlerimizi gerçekleştirdik ve sınıfımızın içerisinde artık test işlemleri yazılabilir.

MetotAdıMetodunBulunduğuController_HangiDurumdaTestEdileceği_BeklenenDeğer isimlendirme kuralını tecrübelerim dahilinde kullanıyorum. İsimlendirmeleri bir kurala tabi tutmamızın en temel sebebi: Testlerimizi çalıştırdığımız vakit hangi testimizin başarıyla sonuçlandığını/hata verdiğini anlamak için. Araştırmalarınızı genişletip kendinize en uygun isimlendirme kuralını seçebilirsiniz.

test explorer

Testlerin çalışma şeklini örnek projeyi https://github.com/Kakcil/xunit-test-sample-dotnet-core adresinden indirip aşağıdaki resimde gösterildiği kısımdan breakpointler ile debug ederek detaylı şekilde inceleyebilirsiniz ve kendi projenize uygun şekilde configure edebilirsiniz.

debugging test

Code Coverage (Kapsam):

Yazılan kodun ne kadarının test edildiğini gösterir.
Nuget “coverlet.msbuild” paketini kullanılır. Öncelikle bu paket, test projesine eklenmelidir. Ardından kapsam oranını görmek için“Package Manager Console”da

dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover

komutu çalıştırılmalıdır.

coverage result

Ben projelerimde Lines Coverage’ı önemsiyorum. Bu oranı en az 60% da tutmayı hedefliyorum. Görüldüğü üzere %41 ile başarısız.

Peki bu oranı nasıl arttırabiliriz?
Testlerimizin kodun neresinden geçip geçmediğini görebildiğimiz bir test raporu alırsak, testten geçmeyen kodlar için test yazarak arttırabiliriz. O halde bir rapor alalım. Tek bir komut ile bu raporu alabiliriz. “Package Manager Console”da

dotnet test --collect:"Code Coverage"

komutu çalıştırdıktan sonra test projemizde “TestResults” adında bir klasör oluşacaktır. Buradaki raporu açmaya çalıştığımızda vs2019'da bu işlemi gerçekleştiriyorsak muhtemelen aşağıdaki hatayı alacağız.

error coverage

Bu hatayı çözmek için https://gist.github.com/Kakcil/34d2b5d4e7bc0fb25788ec459d4f0bc5
adresinden çözüm yolunu inceleyebilirsiniz.

coverage report

Hatanın çözümünün ardından raporu açtığımızda coverage oranları ve detayları karşımıza gelecektir. Sonuçların içlerine girmeye devam ederek detaylandırabiliriz. Ancak bu oranlar Blocks Coverage oranlarıdır. Code Coverage Results penceresinde sağ tık ile add/remove columns ‘a tıklayarak açılan pencereden Lines sütunlarını ekleyip akabinde Lines Coverage oranlarını görüntüleyebiliriz.

Ekstra:

[Theory]
[ClassData(typeof(CityControllerTheory.DetailCityOkParams))]

şeklinde bir kullanımı

[Theory, ClassData(typeof(CityControllerTheory.DetailCityOkParams))]

şeklinde de kullanabiliriz.

Ayırdığınız vakit için teşekkür ederim. Umarım yararlı bir anlatım olmuştur.

Kaynak: Bilgilerim.

--

--