Merhaba arkadaşlar, bugün sizlerle beraber test konusundan bahsederek ufak bir test örneği hazırlayacağız.
Senaryomuz şu şekilde olacak;
Api endpointinden dönen sonucu test edeceğimiz bir integration test ve endpointin kullandığı servisin içerisinde ki kod bloğunu test edeceğimiz bir unit test olacak.
Farklı bir çok kütüphane bulunsa da biz bu test uygulamasında xUnit ve Moq kütüphanelerinden yararlanacağız.
Öncelikle neden test yazmalıyız konusuna biraz değinmek istiyorum. İlk başta en basit haliyle hafta sonumuzu rahat geçirmek ya da , gece rahat uyumak istiyorsak acaba hangi kod nereden patlayacak diye düşünmek istemiyorsak test yazmalıyız.
Production ortamında stabil çalışan koda müdahaleden korkmamak için test yazmalı, hatta testlerimizi pipelinımıza bağlayarak test coverage kontrolleri yapmalıyız. (Hatta TDD yaklaşımını kullanırsanız çok daha rahat uyuyabilirsiniz. 😀)
Lemi Orhan hocanın testle alakalı güzel bir hikayesini dinlemiştim.
İzlemek isteyenler buyrun => https://www.youtube.com/watch?v=VaeGtoH4dhU#t=1558
Testlerin sağladığı bir kaç avantajdan bahsetmek istiyorum.
- Test yazıyorsanız eğer belli kod standartlarını takip ederek ufak kod parçacıkları oluşturursunuz. Bu hem kodun daha rahat okunmasını hem daha kolay geliştirilebilmesini sağlar.
- Eğer bir işin/kodun testini yazabiliyorsanız o kodu yada işi iyi anlamış olmanız gerekir.
- Testlerde süreklilik yakalarsanız, beklenmedik hataları minimuma indirebilirsiniz.
- Testi olan kodun üzerine geliştirme yada refactoring yapmak çok daha kolaydır.Şimdi geçelim örneğimize;
Api Projemiz
Öncelikle Name Api’ mizi oluşturalım. Ve içerisinde Services adlı bir klasör oluşturarak INameService ve NameService classlarımızı ekleyelim.
namespace NetCoreTest.Services { public interface INameService { bool isValidName(string name); } public class NameService : INameService { public bool isValidName(string name) { return !string.IsNullOrWhiteSpace(name); } } }
Startup içerisinde Name Servisimizi register ederek Name Controller içerisinde kullanmak için Constructor injection yapalım.
services.AddTransient<INameService, NameService>();
using Microsoft.AspNetCore.Mvc; using NetCoreTest.Services; namespace NetCoreTest.Controllers { [Route("api/[controller]")] [ApiController] public class NameController : ControllerBase { private readonly INameService _nameService; public NameController(INameService nameService) { _nameService = nameService; } [Route("alive")] [HttpGet] public IActionResult Alive() { return Content("Api Alive"); } [HttpGet] public StatusCodeResult Get(string name) { var isValidName = _nameService.isValidName(name); if (isValidName) return Ok(); else return BadRequest(); } } }
Alive metoudu api ilk ayağa kaltığında çalışacak. LaunchSettings.json yada startuptan ayarlanabilir.
Get metodu name parametresini Name Servis içerisinde ki isValideName metoduna göndererek eğer servisten True dönerse 200 status kodunu , False dönerse 400 kodunu dönecek.
Apimizin yapısını basitçe tanımladık. Burada gerçek uygulamalarda NameService içerisinde ki isValidName metodu databaseye gidebilir.
Bu yüzden endpoint testimizi yazarken bu servisi mocklayarak databaseye ekstra bir yük bindirmemesini ve uygulamamızın performansını etkilememesini sağlayacağız.
Test Projemiz
NameApi.Test ismiyle xUnit kullanan bir test projesi oluşturduk. Services ve Endpoint klasörlerimizi ekledik. Şimdi geldik test yazmaya.
Öncelikle NameServisimize ait isValidName metodunun unit testini yazalım. Burada test metodumun başına “[Fact]” attributunu koymayı unutmuyorum.İsimlendirmeyi de olabildiğince açık ve anlaşılır bir şekilde yazmaya çalışıyorum. Assert kısmında ise beklediğim sonucu yazıyorum.
using NetCoreTest.Services; using Xunit; namespace NameApi.Test.Services { public class INameServiceTest { private INameService _nameService; public INameServiceTest() { _nameService = new NameService(); } [Fact] public void Should_isValidName_Return_False_When_ParamisEmpty() { string name = string.Empty; var result = _nameService.isValidName(name); Assert.False(result); } [Fact] public void Should_isValidName_Return_True_When_ParamisnotEmpty() { string name = "Enes Aysan"; var result = _nameService.isValidName(name); Assert.True(result); } } }
Endpoint Kısmına gelerek NameController testimi yazıyorum.
using Moq; using NetCoreTest.Controllers; using NetCoreTest.Services; using System.Net; using Xunit; namespace NameApi.Test.Endpoints { public class NameControllerTest { private readonly Mock<INameService> _nameService; private readonly NameController _nameController; private readonly string name; public NameControllerTest() { name = string.Empty; _nameService = new Mock<INameService>(); _nameController = new NameController(_nameService.Object); } [Fact] public void Should_Get_Return_StatusCode200_When_NameService_isValidName_Returns_True() { _nameService.Setup(x => x.isValidName(It.IsAny<string>())).Returns(true); var result = _nameController.Get(name); Assert.Equal((int)HttpStatusCode.OK, result.StatusCode); } [Fact] public void Should_Get_Return_StatusCode400_When_NameService_isValidName_Returns_False() { _nameService.Setup(x => x.isValidName(It.IsAny<string>())).Returns(false); var result = _nameController.Get(name); Assert.Equal((int)HttpStatusCode.BadRequest, result.StatusCode); } } }
Mock<INameService>();
tanımlaması ile NameServisin bir mock objesi olacağını söyleyerek controller üretilirken bu servisin mock objesini yolluyorum.
NameController(_nameService.Object);
Ayrıca bu mock servisimin ayarlarını yapacağım teste göre Setup kısmında ayarlıyorum.
_nameService.Setup(x => x.isValidName(It.IsAny<string>())).Returns(true);
Bunu biraz türkçeleştirmeye çalışırsam “Name Serviste yer alan isValidName metoduna herhangi bir string parametre geldiğinde true sonucunu döndür.” anlamı çıkar.
Testime göre mock servisimin ayarlarını yaparak , Assertlerim de beklentilerimi yazarak testlerimi çalıştırıyorum. Burada cli kullanmıyorsanız, Visual Studionun Test/Test Explorer aracından yararlanabilirsiniz.
Kaynak Kod => https://github.com/EnesAys/NetCoreTest
Umarım faydalı olmuştur, Kalın sağlıcakla 😀