Node.js – Unit(Birim) Test Nedir? Nasıl Yazılır?

Merhaba,

Proje süreçlerinde, yapılacaklarla yapılanlar arasındaki farkı ortaya çıkarmak için yazılım mimarilerinde belki tüm süreçlerin en önemli noktasını arz eden testing yapıları mevcuttur. Bu yapılar ki, projedeki birimlere bir kural/sınır/ölçü tanımakta ve bu birimlerin generate süreçlerinin sonunda bu ölçülere erişip erişemediğini denetleyerek yazılan kodun ihtiyaca dönük olup olmadığını değerlendirmektedirler. İşte biz bu denetleme ve değerlendirme mekanizmalarına Unit(Birim) Testleri diyeceğiz ve bu makalemizde ne olduklarına ve nasıl oluşturulduklarına dair detaylı bir incelemede bulunacağız.

Birim Testi Nedir?

Bu soruya cevap verebilmek için ilk olarak birim teriminin ne manaya geldiğini açıklayalım. Birim; bir işi yerine getiren bütünün en küçük parçasıdır. Birim testi ise; bir işi yerine getiren en küçük parçasının(yani birimin) doğru çalışıp çalışmadığını kontrol etmek için kullanılan yapılardır. Yazılımın en küçük birimleri test edildiği zaman eğer bir sıkıntı söz konusu değilse bu birimlerden meydana gelecek olan bütünden de beklenilen sonuç elde edilecek demektir.

Birim Testleri Neden Kullanılır?

Bir çok nedeninin olmasının yanında bazı mühim durumlar birim testlerini projenin olmazsa olmazı haline getiriyor. Sözgelimi; bir ekip tarafından inşa edilen projede bir kişinin yaptığını bir başkası ister istemez değiştirebilmekte ve farkında olmaksızın kodların işleyişini bozabilmektedir. Bu duruma engel olabilmek için yapılan çalışmalara birim testlerinin yazılması olası değişiklikler neticesinde ilgili birimin test sonucundan geçemeyeceğinden dolayı olası hatalar ve işleyiş bozuklukları engellenmiş olacaktır.

Birim testleri, yazıldıkları birimleri tarafımızca belirlenmiş sonuçlara ve ölçütlere göre değerlendirmektedirler. Bu değerlendirmeden geçemeyen birimler beklenen icraatlarını yerlerine getiremediğinden dolayı sistem tarafından kabul edilmeyeceklerdir. Bu durumda yanlış ya da beklentiye uymayan kodların projeye gözden kaçıp katılmasını engellemekte ve süreçte olası hatalara karşı tarafımızca baştan önlem alınmasını sağlamış olmaktadırlar.

Bir başka örnek durum ise kusursuz çalışan kod üzerinde yapılacak değişiklik neticesinde kodu bozup bozmama korkusunun yaşanmaması için birim testleri aracılığıyla kodun güvenliği baştan sağlanmış olunacaktır.

Birim Testlerinde Süreç Nasıl İşler?

Projenizi birim testleriyle güvenceye almak ve fonksiyonlardan elde edilecek sonuçlardan beklentiniz doğrultusunda emin olmak istiyorsanız ilk olarak bu beklentilere göre ölçütlerinizi oluşturmalı ve doğal olarak ilk başta test kodlarınızı yapılandırmanız gerekmektedir. Ardından test aşaması aşağıdaki gibi bir sırada seyr edecektir;

  1. İlgili testlere uygun fonksiyonlar(birimler) oluşturulur.
  2. İlgili birim testte gönderilir.
  3. Eğer ki kod compile edilip test aşamasında ölçütlere aykırı sonuçlar elde ediliyorsa ilgili kod bu testten geçemeyecektir.
  4. Testten geçemeyen kod test sonuçlarının beklentisi doğrultusunda yeniden gözden geçirilecek ve geliştirilecektir.
  5. Test süreci tekrar edecektir ve eğer ki yine testten geçemezse 4. adım tekrar edecektir.
  6. Eğer ilgili kod testi geçerse başarılı olduğu anlaşılacak ve o fonksiyonun tamamlandığına dair yetkili kişiler tarafından kesin onay verilecektir.

Tabi bu sürecin getirisi olan ayrıntılar da mevcuttur;

  • Birinin yazdığı kodu bir başkası refactor ederse eğer yapılan tüm değişiklikler birim testi tarafından onaylanması gerekeceği için kodun güvenliği sağlanmış olacaktır.
  • Yazılan kodun birim testinden geçebildiği süre zarfınca daha kısa, okunabilir ve kaliteli hale getirilebilme şansı mevcuttur. Sınırları ve güvenliği birim testi sağlayacağı için analitik düşünceye daha çok enerji sarfedilebilir.
  • Kod dökümantasyonu gibi öznel yetenekleri genelleştirmeye çalışan yöntemlere gerek kalmaksızın test kodu ile istenilenler daha anlaşılır biçimde izah edilmekte ve bu izah neticesinde er kişi yazılımcı kimse onun yoğurt yeyişine güvenlierek sonuçlar gözlenmektedir.

Birim testleri, test süreçlerinin uzamaması için performans testlerini kapsamamaktadırlar.

Birim Testi Nasıl Yazılır?

C#, Java vs. gibi dillerde birim testleri mevcuttur ve her birinde kendine has kurallarıyla ilgili yapılar oluşturulur. Tabi hangi dilde birim testi oluşturacaksanız ilgili dile özel dökümanlara göz atmanız gerekecektir. Bizler bu makalemizde Node.js mimarisiyle birim testlerinin nasıl yazıldığını değerlendireceğiz ve konuyu hem teorik hem de pratik olarak a’dan z’ye inceleyeceğiz.

Örnek Node.js Projesinin Ayağa Kaldırılması

İlk olarak ayağa kaldırılmış ortalama bir Node.js projesinin aşağıdaki gibi olduğunu düşünelim;

const app = require("express")();
//Db
require("./db/db")();
//Routes
app.use("/employee", require("./routers/employeeRoutes"));
app.listen(4000, () => console.log("Yayın başarılı..."));
module.exports = app;

Burada dikkat ederseniz uygulamamızın “app” nesnesinide export etmekteyiz. Çünkü birim testlerini server üzerinden çalıştırabilmek için serverı barındıran “app” nesnesine ihtiyacımız olacaktır.

Ayrıca “/employee” dizinine tanımlanan “employeeRoutes” isimli route dosyasının içeriğinide aşağıya alalım.

const router = require("express").Router();
const employeeModel = require("../models/employee");

router.get("/getEmployee", (request, response, next) => {
     employeeModel.find({})
          .then(data => response.json(data))
          .catch(error => response.send("Beklenmeyen bir hatayla karşılaşıldı. - /getEmployee -"));
});

router.post("/addEmployee", (request, response, next) => {
     const employeeInstance = new employeeModel({
          _id: 2,
          name: "Gençay",
          surName: "Yıldız"
     });

     employeeInstance.save()
          .then(data => response.json(data))
          .catch(error => console.log("Beklenmeyen bir hatayla karşılaşıldı. - /addEmployee -"));
});
module.exports = router;

Evet… Şimdi bu yapılanma üzerinde unit testlerimizi yazmaya başlayacağız.

Test Altyapısının Sağlanması

Node.js mimarisinde test kodu yazmak için Mocha, Chai ve Chai-Http modüllerinin projeye entegre edilmesi gerekmektedir. Bu modüllerin görevlerini aşağıda ele alırsak;

  • Mocha
    Test çatısı olarak düşünülebilir.(Testing Framework)
    Test sürecini başlatacak ve çalıştıracak olan modüldür.
  • Chai
    Assertion(iddia, hakkını ispat etme, onaylama) işlemlerini yapan modüldür. Yani test sürecinde ilgili birimden yerine getirmesi beklenen ölçütleri belirtmemizi sağlayan yapıyı sunmaktadır.
  • Chai-Http
    Chai modülünün http yapılanmasını desteklemesi için kullanılan bir modüldür.

İlgili modüllerin proje entegrasyonunu sağlamak için aşağıdaki komutu çalıştırmanız yeterlidir.

npm install mocha chai chai-http --save

Modül entegrasyonundan sonra sıra test sınıflarımıza gelmiş bulunmaktadır.

Node.js’de Mocha modülü test sürecini başlatırken projenin test sınıflarını varsayılan olarak projenin ana dizinindeki “test” klasöründe arayacaktır. Bu sebeple projemizin ana dizininde “test” isimli bir klasör oluşturalım ve ardından ilk test sınıfımızı içerisinde generate edelim.
Node.js - Unit(Birim) Test Nedir Nasıl Yazılır.
Dikkat ederseniz hangi sınıf/işlem/birim üzerine test hazırlayacaksak o sınıfın ismini “.test” ekiyle birlikte oluşturuyoruz. Buradaki isimlendirme opsiyonel olduğu için sizlerin keyfine kalmıştır. Lakin bu şekilde bir formatlandırma daha anlaşılır olacaktır.

Test Yapısının Yazılması

İlgili test sınıfımıza öncelikle projeye install ettiğimiz “Chai” ve “Chai-Http” modüllerini require edelim ve gerekli ilk adımları aşağıdaki gibi oluşturalım.

const chai = require("chai");
const chaiHttp = require("chai-http");

const should = chai.should();
//Sonucu şu olmalı gibisinden sorular sorabilmek için...

chai.use(chaiHttp);
.
.
.

Yukarıdaki kod bloğunu incelerseniz eğer “Chai” ve “Chai-Http” modülleri require edilmiştir. Bunların dışında birim testlerinin uygulanacağı fonksiyonlara ölçütleri belirleyebilmek için “sonucu şu olmalı“, “sonucu bu olmalı” veya “sonucu şöyle olmalı” vs. gibi soruları sormamızı sağlayan “should” fonksiyonundan gelen nesneyide bir değişkene atamaktayız. Tüm bunların dışında “Chai” modülünün http yapılanmasını sağlayabilmek için “use” fonksiyonuna “Chai-Http” modülünü atayarak, kullanmasını sağlamaktayız.

describe Yapısı İle Birim Testlerini Gruplama

Bir test sınıfı içerisinde birden fazla unit test yazılabileceği için işlevsel olarak bu testleri gruplama işlemini “describe” yapısı ile sağlamaktayız. Bu noktadan itibaren oluşturacağımız birim testlerini “describe” yapısı çerçevesinde inşa edeceğiz. “describe” yapısının prototipi aşağıdaki gibidir.

describe("Comment", () => {
     //Testing...
});

Yapılacak testi hangi işleme özel olarak tasarlandığını ifade etmektedir. Burada yapılacak test olabildiğince detaylı tarif edilmesi gerekmektedir. Ayriyetten istenildiği kadar “describe” yapısı ile test fonksiyonlarınızı gruplayabilirsiniz.

.
.
.
describe("1. Test Grubu", () => {
     .
     .
     .
});
describe("2. Test Grubu", () => {
     .
     .
     .
});

it Yapıları İle Birim Testlerinin Yazılması

Birim testlerini gerçekleştirdiğimiz yapılar it yapılarıdır. Bu yapılar describe yapısı içerisinde tanımlanmaktadırlar. Her describe içerisinde birden fazla “it” yapısı olabilmektedir. Yani anlayacağınız describe yapıları it yapılarını kapsamaktadırlar.

it yapılarının prototipini incelersek eğer;

     it("Comment", done => {
          done();
     });

şeklinde olacaktır. Yapısal olarak “Comment” kısmı yaptığı test işleminin ne olduğuna dair açıklama gerektirmektedir. “done” parametresi ise ilgili birimin test neticesinde belirtilen tüm kriterleri yerlerine getirip testten geçebildiği durumda testin başarıyla tamamlandığını ifade etmektedir.

Şimdi aşağıdaki örneği inceleyiniz.

describe("Örnek Test Uygulamaları", () => {
     it("x fonksiyonuna get testi", done => {
          done();
     });
});

Görüldüğü üzere describe içerisinde it yapısı tanımlanmıştır. Burada ki test neticesi ne olursa olsun direkt “done” parametresi çağrıldığından dolayı bu test başarıyla tamamlanacak ve ilgili birim testi geçmiş olacaktır.

Biraz daha gerçekçi bir örnek yaparsak eğer;

const chai = require("chai");
const chaiHttp = require("chai-http");

const should = chai.should();

chai.use(chaiHttp);

const server = require("../app");

describe("Örnek Test Uygulamaları", () => {
     it("GET: employee", done => {
          chai.request(server)
               .get("/employee/getEmployee")
               .end((error, response) => done());
     });
});

şeklinde çalışma yapabiliriz. Artık test kodlarımızı adım adım derinleştirmekteyiz. Yukarıdaki kodu incelerseniz eğer “chai” modülünün “request” fonksiyonu server istemektedir. Dolayısıyla bizde serverımız olan ana modülü(app.js) sayfaya require ederek chai modülünün request fonksiyonuna vermekteyiz. Bu işlemden sonra ilgili server üzerindeki “/employee/getEmployee” adresini tetikleyen route fonksiyonuna “get” isteği gönderilmekte ve bu istek sonucunu “end” fonksiyonu ile elde etmekteyiz. Bu noktada bir hatayla karşılaşılırsa “error” parametresi bizlere olası hatayla ilgili gerekli bilgileri verecektir. “response” parametresi ise hata yoksa bu istek neticesinde dönülen cevabı getirecektir. Bu test dikkat ederseniz direkt olarak done edilmiştir. Makalemizde ilerledikçe adım adım dönen sonuçta sorgularımızı gerçekleştireceğiz ve testten geçilebilmesi için ölçütlerimizi belirteceğiz.

Bir describe içerisinde birden fazla it olabileceği gibi, bir it içerisinde de birden fazla request durumu söz konusu olabilir.

const chai = require("chai");
const chaiHttp = require("chai-http");

const should = chai.should();

chai.use(chaiHttp);

const server = require("../app");

describe("Örnek Test Uygulamaları", () => {
     it("GET : employee", done => {
          chai.request(server)
               .get("/employee/getEmployee")
               .end((error, response) => done());

          chai.request(server).post("/employee/addEmployee")
               .end((error, response) => done());
     });
});

Yukarıda olduğu gibi…

Yazılan Birim Testlerinin Çalıştırılıp Denetlenmesi

Tüm birim testleri yazılıp tamamlandıktan sonra artık sıra bu testlerin işletilmesine gelmiş bulunmaktadır. Bu işlem için projemizin package.json dosyasına gelerek “scripts” kısmında aşağıdaki gibi alan oluşturularak Mocha modülü komutlandırılmalıdır.

.
.
.  
"scripts": {
    "test": "mocha"
  },
.
.
.  

ve bu işlemden sonra projemizde

npm run test

komutunu çalıştırmamız “test” klasörü altındaki tüm birim testlerinin denetlenmesi için yeterli olacaktır.

Testing örneklendirmesi için aşağıdaki birim testlerini ele alabiliriz.

const chai = require("chai");
const chaiHttp = require("chai-http");

const should = chai.should();

chai.use(chaiHttp);

const server = require("../app");

describe("Örnek Test Uygulamaları", () => {
     it("GET : employee", done => {
          chai.request(server)
               .get("/employee/getEmployee")
               .end((error, response) => done());
     });

     it("POST : employee", done => {
          chai.request(server)
               .get("/employee/addEmployee")
               .end((error, response) => done());
     });
});

Node.js - Unit(Birim) Test Nedir Nasıl Yazılır.

Yukarıdaki ekran görüntüsüne dikkat ederseniz eğer yapmış olduğumuz testing işlemi neticesinde ilgili testing süreci sonlandırılmamaktadır. Bunun için package.json dosyasında aşağıdaki ayarın yapılması gerekmektedir.

.
.
.
  "scripts": {
    "test": "mocha --exit"
  }
.
.
.

“mocha” komutunun yanına eklenen “–exit” komutu sayesinde testing işlemi yapıldıktan sonra test modundan otomatik çıkılacak dolayısıyla bu moddan çıkmak için extradan CTRL + C kombinasyonuyla bizlerin enerji sarf etmesine gerek kalmayacaktır.

Evet… Görüldüğü üzere testten başarıyla geçen metotlar yukarıdaki ekran görüntüsünde olduğu gibi onaylanmaktadır. Testen geçemeyen metotlar ise uyarılacaktır. Bununla ilgili yazımızın ilerleyen kısımlarında örneklendirmeler yapacağız.

Mocha modülü ile ilgili bir başka bilgi vermemiz gerekirse eğer; testing sürecinde sade ve sadece ana dizindeki “test” klasörü içerisindeki test sınıflarını taradığını ve içlerindeki tanımlanmış birim testlerini çalıştırdığını söylemiştik. Eğer ki, “test” klasörü içerisinde alt klasörler ile test sınıflarınızı kategorize etmek istiyorsanız, Mocha modülünün bu klasörler içerisinde bulunacak test sınıflarına erişebilmesi içinde yine package.json dosyasında aşağıdaki ayarın yapılması gerekmektedir.

.
.
.
  "scripts": {
    "test": "mocha --recursive"
  }
.
.
.

“–recursive” komutu ile Mocha modülü tüm alt klasörlerin içini tarayacak ve tüm test sınıflarına erişerek çalıştıracaktır.

Birim Testlerinde Should İle Ölçütlerin/Sınırların Belirlenmesi

Testing işlemine tabi tutulacak birimlerin işlevsellikleri neticesinde hangi sınırlarda değerler döndüreceğine dair ölçütlerin belirlenmesi için “Chai” modülünün “should” fonksiyonundan gelen nesneyi kullanacağımızı belirtmiştik.

Bu nesne sayesinde it yapısı içerisinde teste tabi tutulan fonksiyonun geri dönüş kriterlerini aşağıdaki gibi belirtebilmekteyiz.

const chai = require("chai");
const chaiHttp = require("chai-http");

const should = chai.should();

chai.use(chaiHttp);

const server = require("../app");

describe("Örnek Test Uygulamaları", () => {
     it("GET : employee", done => {
          chai.request(server)
               .get("/employee/getEmployee")
               .end((error, response) => {
                    response.should.have.status(200);
                    //Bu request neticesinde status kod 200 olmalıdır.
                    done();
               });
     });
});

Gördüğünüz gibi “Chai” modülü aracılığıyla ilgili route’a yapılan request neticesinde “get”(ya da post, put veya delete’de olabilirdi) talebi neticesinde dönen sonucun “should – have” yapısı ile kriterini belirlemiş bulunmaktayız.

Burada “should”; malı, meli, gerekmeli manasına gelirken, “have”; sahip anlamına gelmektedir. Yani “status kodu 200’e sahip olmalı” manasında bir kriter belirlemiş bulunmaktayız. Eğer ki ilgili birim bu kriterden geçebilirse done fonksiyonu çağrılacak ve test başarıyla tamamlanacaktır.

Tüm bunalrın dışında ilgili birimin işlevi neticesinde geriye döndüğü değerden tutun, değer tipine ve hatta gelen veri obje ise property bilgisine kadar tüm bilgileri kriter olarak belirleyebilmekteyiz.

const chai = require("chai");
const chaiHttp = require("chai-http");

const should = chai.should();

chai.use(chaiHttp);

const server = require("../app");

describe("Örnek Test Uygulamaları", () => {
     it("POST : employee", done => {
          chai.request(server)
               .post("/employee/addEmployee")
               .end((error, response) => {
                    response.should.have.status(200);
                    //Bu request neticesinde status kod 200 olmalıdır.
                    response.body.should.be.a("object");
                    //Gelen veri bir obje olmalıdır.
                    response.body.should.be.property("name");
                    //Obje içerisinde name adında bir property olmalıdır.
                    response.body.should.be.property("name").eql("Gençay");
                    //Obje içerisinde name adındaki propertynin değeri "Gençay" değerine eşit olmalıdır.
                    done();
               });
     });
});

Yukarıdaki kod bloğunu incelerseniz eğer “post” fonksiyonu neticesinde gelen sonuç üzerinde kriterlerimizi belirlemiş bulunmaktayız. Burada “should”; meli, malı, “be”; olmak ve “a”; herhangi bir manasına gelmektedir.
Node.js - Unit(Birim) Test Nedir Nasıl Yazılır.
Gördüğünüz gibi ilgili route’ta bulunan fonksiyonumuz tüm kriterleri sağlayarak testten başarıyla geçmiş bulunmaktadır.

Eğer ki bir metot herhangi bir kriteri sağlamazsa testing aşamasında onay alamayacağı için sistem tarafından otamatik uyarılacaktır ve o birimin görevini yerine getirmediği anlaşılacaktır. Örneğin, yukarıdaki test kodunun status kriterini 400 olarak belirtirsek eğer;
Node.js - Unit(Birim) Test Nedir Nasıl Yazılır.bizlere ekran görüntüsünde olduğu gibi “sen status kodunu 400 istiyorsun ama 200 sonucu geliyor” şeklinde hata verecektir. Bu şekilde test sürecinden geçemeyen tüm metotlar bildirilecektir.

before Yapısı İle Test Önceliği

Test sınıflarında tüm test işlemlerinden önce işlem yapabilmek için tetiklenen before yapısı mevcuttur.

const chai = require("chai");
const chaiHttp = require("chai-http");

const should = chai.should();

chai.use(chaiHttp);

const server = require("../app");

describe("Örnek Test Uygulamaları", () => {
     it("Örnek Test 1", done => {
          console.log("Örnek Test 1");
          done();
     });

     it("Örnek Test 2", done => {
          console.log("Örnek Test 2");
          done();
     });

     before(done => {
          console.log("Herşeyden önce ben çalışırım");
          done();
     });
});

Node.js - Unit(Birim) Test Nedir Nasıl Yazılır.
Bu yapı sayesinde test için gerekli şartları öncelikle yerine getirebilmekte ve ardından test sürecine başlayabilmekteyiz.

Authentication Gerektiren Birimlere Testing Esnasında Token Temini

Eğer ki, test edilecek birimler authentication ile kimlik doğrulama gerektirmekteyseler bunun için ilk olarak gerekli doğrulamanın sağlanmış olması gerekmektedir. Dolayısıyla test sürecinde kullanılmak üzere gerekli token vs. gibi yapıların test sürecinden önce temin edilmesi gerekmektedir.

Bunun içinde bir üstte bahsettiğimiz before yapısı aracılığıyla test başlamadan önce token değerlerimizi elde edecek, ardından testing sürecini başlatacağız.

const chai = require("chai");
const chaiHttp = require("chai-http");

const should = chai.should();

chai.use(chaiHttp);

const server = require("../app");

let token, status;
describe("Token Test Uygulamaları", () => {
     before(done => {
          chai.request(server)
               .post("/getToken")
               .send({ userName: "gncy", password: 12345 })
               .end((error, response) => {
                    if (!error) {
                         token = response.body.token;
                         status = response.body.status;
                         console.log(`Token : ${token}\nStatus : ${status}`);
                    } else
                         console.log("Hata alındı");
                    done();
               });
     });

     it("Employee Get", done => {
          console.log(token);
          console.log(status);
          chai.request(server)
               .get("/employee/getEmployee")
               .set("x-access-token", token)
               .end((error, response) => {
                    response.should.have.status(200);
                    response.body.should.be.a("array");

                    done();
               });
     });
});

Yukarıdaki kod bloğunu incelerseniz eğer test aşamasında authentication gerektiren birimler niçin nasıl token talep edildiğini ve talep edilen tokenın testing sürecinde kullanıldığını görmüş olacaksınız.

Evet…
Unit Testler üzerine olan makalemizin sonuna gelmiş bulunmaktayız. İçerik olarak biraz uzun ve detaylı anlatım sergilemiş olabilirim. Elimden geldiğince mümkün mertebe her hususa değinip, değerlendirmek istedim.

Zahmet edip okuyanlara şimdiden teşekkür ederim…

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. ibrahim dedi ki:

    Gayet güzel anlatmış. Eline sağlk.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*

Copy Protected by Chetan's WP-Copyprotect.