Jenkins + e2e + Jasmine İle Test

Cem Topkaya
9 min readDec 23, 2020

--

Benim bu makalede paylaşmak istediğim hangi ayarların nasıl ve neden yapıldığı. Çünkü uçtan uca testlerimi Jenkins ortamımda koştururken, arka planda konteynerleştirilmiş web api sunucularla çalıştığı için tüm sürecin (yapılandır -build-, test -e2e-, paketle, yayımla -publish-, konteynerleştirilmiş kullanıcı test ortamı ve gerçek ortamlar için yansı -image- ve kapsayıcı -angular-http-server --path path/to/dist/foldercontainer- oluşturma) tamamlanması ciddi zaman alıyordu.

Neticede Jenkins hattım geliştiriciye “bak şuralarda çalışmadı” demek için değil, “sürekli -Continous- geliştirme -Development-, entegrasyon -Integration-, test -Testing- ve dağıtım -Deployment-”, başarılı veya başarısız demek için kısıtlı kaynaklarla ayakta. Yani yazılım mühendisi o dakikaya kadar tüm yerel testlerini yapmış “belki” entegrasyon tarafında imkanları yetersiz kalmış olabilir. Bu yüzden Jasmine ile e2e testlerinde Fast-Fail (hızlıca başarısız ol) düsturuyla hareket edeceğim.

Not: Fast-Fail, kod yazarken de düsturunuz olsun!

  • Protractor
  • Jasmine’e Dair
  • Assertion
  • Spie
  • Mock
  • [before|after][all|each]
  • Jasmine Kurulumları
  • İnternet Gezgininizde Jasmine Kullanmak
  • NodeJs Üstünde Jasmine
  • Jasmine Ayarları
  • Test Dosyalarının Dizin ve Adı
  • Testleri Sırasıyla veya Karışık Çalıştırmak
  • Hata Olunca Tüm Testleri Durdur
  • Beklenti (expect) Olmayan Testi Başarısız Kıl
  • Tüm Beklentileri (hata olsa bile) Dene
  • Devre dışı bırakılmış (disabled) Testleri Raporda Gizle
  • Özel Raporcu (Custom Reporter)
  • Yardımcı Araçlar

Protractor

Selenium üstünden uçtan uca testleri yapmamızı sağlayan çatıdır. Protractor ayarlarını bir vereceğimiz bir javascript dosyasını (configFile) ve diğer seçenekleri (options) vererek başlatabiliriz.

Angular projenizde eğer birden fazla kütüphane barındıracak şekilde geliştirme yaptığınızı ve eğer uçtan uca (end to end) testlerinizi her bir paket için çalıştırmak için ng e2e @akbank-internet-banka/talimatlar --configuration dev.docker diye bir komut çalıştırdığınızı düşünün. Bu komutla:

  • Angular önce projeyi build edecek ancak dist dizini yerine bellekte dosyaları tutacak
  • serve ederek uçtan uca testlerin koşacağı web uygulamasını ayaklandıracak. talimatlar Paketi uygulama projesi içinde görüntülenecek ve bu paket ile ilgili uçtan uca testler çalışacak.
  • Uçtan uca testleri çalıştırmak için önce protractor devreye girecek.
  • Selenium ile chrome/firefox gezginlerinden birinden veya hepsinden istediğimiz kadar örnek yaratarak baseUrl adresine ziyaret edecek.
  • İnternet gezginleri ayaklandığında jasmine çatısıyla yazdığınız testleri tek tek işletecek ve testler başarılı vey başarısız tamamlandığında jasmine raporcularından bir veya birkaçının hazırladığı rapor konsolda, dosyada veya ileti yoluyla hazırlanmış olacak.

Angular projesinin testini hızlandırmak için angular-http-server paketini kullanıyorum (aslında basit bir http-server işinizi görür ama bu paketin parametreleri ng serve --host --port ... parametleriyle aynı olduğu için).

Yani ng build ile ./dist dizinine javascript, html dosyalarımızı yani web sitesini açıyorum (ng serve ile belleğe açılıyordu) ve angular-http-server ile istediğim port üstünden ./dist dizinini göstererek yayımlıyorum. Artık geriye protractor ile testlerimi çalıştırmak kalıyor.

Hata Giderme

Could not find update-config.json

Projenizin node_modules dizini içindeki protractor modülü içindeki çalıştırılabilir protractor dosyası üstünden başlattığınızda sorun kalmayacaktır.

[04:57:24] I/launcher - Running 1 instances of WebDriver
[04:57:24] I/direct - Using ChromeDriver directly...
[04:57:24] E/direct - Error code: 135
[04:57:24] E/direct - Error message: Could not find update-config.json. Run 'webdriver-manager update' to download binaries.
[04:57:24] E/direct - Error: Could not find update-config.json. Run 'webdriver-manager update' to download binaries.

Bir Dizi Aynı CSS Özelliğine Sahip Elemanın İçerdiği Metine Göre Bulmak Belirli Bir Elemanı Bulmak (cssContainingText)

let generalTab:ElementFinder;
generalTab = element(
by.cssContainingText(".mat-tab-label", "General")
);
generalTab.click();

Bir Dizi A Etiketinden İçerdiği Metine Göre Bulmak (linkText)

import { 
$$, browser, by, element, ElementFinder } from "protractor";
let udmLink: ElementFinder;// Tüm a etiketleri içinde süzgeç ile metinlerine bakarak:
udmLink = $$("a")
.filter(async (el) => (await el.getText()) == "UDM")
.first();
// veya linkText ile:udmLink = element(by.linkText("UDM"));// Ve tıklayalım
udmLink.click();Bir Dizi A Etiketinden İçerdiği Metine Göre Bulmak (linkText)
import {
$$, browser, by, element, ElementFinder } from "protractor";
let udmLink: ElementFinder;// Tüm a etiketleri içinde süzgeç ile metinlerine bakarak:
udmLink = $$("a")
.filter(async (el) => (await el.getText()) == "UDM")
.first();
// veya linkText ile:udmLink = element(by.linkText("UDM"));// Ve tıklayalım
udmLink.click();

Bir Düğmeyi İçerdiği Metine Göre Bulmak (buttonText)

<button>Save</button>

Bulmak için element(by.buttonText(‘Save’)); yeterli

Başka bir örnek;

element(by.buttonText("edit"));

Butonun içinde hem span hem de onun içinde mat-icon etiketi içinde metin olarak edit yazıyor.

Angular e2e Testlerinde Protractor Debug

Önce node debug modda yani breakpoint gördüğünde duracak şekilde başlatılmalı. Bunu aşağıdaki komutla sağlarız:

node --inspect-brk

Şimdi node istediğimiz gibi başladığına göre protractor’ü node processinin içine ekleyelim:

node --inspect-brk <protracotor dosyasının yolu> <ayar dosyası>
node --inspect-brk ./node_modules/protractor/bin/protractor ./projects/cinar/cn-nrf/e2e/protractor.conf.js

Şimdi node bizi dinler modda bekliyor, sırada e2e testimizi protractor ile başlatmak. Bunu başka bir konsolda protractore aynı konfigurasyon ile başlatacağız:

$ node ./node_modules/../protractor ./projects/../protractor.conf.js

Bunu launch.json içinde yapmak da mümkün:

Çalıştırmak için:

Jasmine’e Dair

Jasmine, açık kaynaklı JavaScript test çatısıdır (testing framework). Pivotal Labs tarafında geliştirilmiştir ki; bu şirketi de Vmware Aralık 2019 da satın almış. İster internet gezgininizde ister NodeJS ortamında çalıştırabileceğiniz, okunması kolay sözdizimine sahiptir. ScrewUnit, JSSpec, JSpec ve RSpec gibi diğer birim test çatılarından (unit test framework) etkilenir. Tarayıcı testlerinizi ve Node.js testlerinizi aynı Jasmine çatısıyla çalıştırabilirsiniz.

Jasmine, JavaScript kodunu test etmek için davranış odaklı (BDD) bir geliştirme çatısıdır. Başka herhangi bir JavaScript çatısına bağlı olmayıp DOM (Document Object Model -internet gezginine bağımlı değildir-) gerektirmez. Temiz, açık bir sözdizimi vardır, böylece kolayca testler yazabilirsiniz.

Birim testler nasıl yazılır diye bir çok kaynak mevcut ve sizin de malumunuzdur eminim. Test için kullandığımız assertion, spie, ve mock yapılarına Jasmine ev sahipliği eder ki; bu sayede harici kütüphaneler kullanmanıza müsaade etse dahi gerektirmez.

Assertion

Spie

Mock

Belirli Test Spec Çalıştırmak (fit) — Focus

Sadece istediğiniz test birimleri (spec) çalıştırmak için başına odak (focus) anlamına gelen “f” ile fit olarak işaretleyin

https://davidtang.io/2016-01-03-controlling-which-tests-run-in-jasmine/

Belirli Test Suite’lerini Çalıştırmak (fdescribe) — Focu

Sadece istediğiniz test kümelerini çalıştırmak için başına odak (focus) anlamına gelen “f” ile fdescribe olarak işaretleyin

https://davidtang.io/2016-01-03-controlling-which-tests-run-in-jasmine/

Odaklandığınız test kümeleri ve birimleri için:

https://davidtang.io/2016-01-03-controlling-which-tests-run-in-jasmine/

Hariç Tutulacak Test Spec (xit) — eXclude

Belirli testleri çalıştırmak istemiyorsanız başına hariç (exclude) anlamına gelen “x” ile xit olarak işaretleyin

https://davidtang.io/2016-01-03-controlling-which-tests-run-in-jasmine/

Hariç Tutulacak Test Suite (xdescribe) — eXclude

Belirli test kümelerini çalıştırmak istemiyorsanız başına hariç (exclude) anlamına gelen “x” ile xdescribe olarak işaretleyin

https://davidtang.io/2016-01-03-controlling-which-tests-run-in-jasmine/

[before|after][all|each]

Testlerimizin her biri (each) veya tümü (all) çalışmadan önce (before) veya sonra (after) çalıştığında ortak işlevleri çalıştırmak için bu adreste hazırladığım örneğin konsoluna bakabilirsiniz.

Jasmine Kurulumları

İnternet Gezgininizde Jasmine Kullanmak

Jasmine ile internet gezgininizde birim testlerinizi koşturabilir ve sonuçlarını güzel bir raporda görebilirsiniz. CDN (Content Delivery Network) üstünden Jasmine dosyalarını HTML dosyanıza bağlayabilir ve birim testlerinizi yazarak koşturabilirsiniz.

NodeJs Üstünde Jasmine

Zaten NodeJS için nasıl kurulduğunu kendi sitesinde rahatlıkla bulacaksınız o yüzden sadece komutları ve basit bir index.js nasıl oluşturulur buraya da bazı notlarla ekleyeyim.

dependencies & devDependencies İle ilgili şu makeleye bakabilirsiniz (ya da hiç dağılmadan devam edin okumaya).

Hem dependencies (bağımlılıklar) hem de devDependencies (geliştirme bağımlılıkları), package.json içindeki iki basit JSON nesnesidir. Her ikisi de, uygulama geliştirme için gerekli olan bağımlı modüllerinin (NPM paketleri) adlarını ve sürümlerini içerir.

devDependencies: geliştirme bağımlılıkları adı üstünde, yalnızca geliştirme yaptığınız zaman gerekli olan modüllerin ad ve sürümlerini içerir (ESLint, JEST, babel vb.).

dependencies: ise çalışma zamanında da kullanacağınız modüllerin ad ve versiyon bilgilerini içerir.

Jasmine geliştirme sırasında kullanacağınız bir çatı olduğu için devDependencies kısmında yer alsın diye --save-dev anahtarıyla yüklüyoruz.

$ npm install --save-dev jasmine

Şimdi bir javascript dosyasında Jasmine çatısını ayaklandırıp, ayarlayıp, testlerimizi koşturalım:

const Jasmine = require('jasmine');
const jasmine = new Jasmine();

Ayarlar için loadConfig() ve env.configure() işlevleri kullanılıyor:

jasmine.loadConfig({...})

Neden iki ayrı fonksiyon? Esasen baştaki anlatımımda belirttiğim gibi hata olduğu anda sonraki testleri yapsın istemiyorum ve bunu ancak env.configuration() fonksiyonu sağlıyor.

Jasmine Ayarları

Test Dosyalarının Dizin ve Adı

Hangi dizin ve hangi dosya adlarının test dosyaları olduğunu spec_dir ve spec_files ile belirtiyoruz:

jasmine.loadConfig({
// Bulunduğum dizindeki testleri bul
spec_dir: './',

// test dosyalarının adları *.spec.js olsun
spec_files: [
"*.[sS]pec.js"
],
...

Dizin, dosyalar ve proje başlangıç dizinini Jasmine nesnesinden (new Jasmine()) öğrenmek için:

console.log(jasmine.projectBaseDir)
// /home/runner/Jasmine
console.log(jasmine.specDir)
// ./
console.log(jasmine.specFiles)
// [ '/home/runner/Jasmine/test1.spec.js' ]

Testleri Sırasıyla veya Karışık Çalıştırmak

Keyfe keder bir sırayla testleri çalıştırmak isterseniz random anahtarını true veya sırayla çalışması için false işaretlemelisiniz.

jasmine.loadConfig({
...
// testleri sırasıyla çalıştırsın
random: false,
...

Hata Olunca Tüm Testleri Durdur

Bu başlık zaten Jasmine konusuna başlamamın sebebiydi. Ancak tek bir çözümü olmadığı için daha büyük puntolarla yazalım başlığımızı. Çözümler:

  • Jasmine içinde failFast ayarıyla
  • jasmine-fail-fast paketiyle
  • Protractor içinde protractor-fail-fast paketiyle
directConnect: true,
baseUrl: "http://localhost:4500/",
framework: "jasmine",
allSpecs: [],
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 10 * 60 * 1000,
print: function () {},
hideDisabled: true,
},
plugins: [require("protractor-fail-fast").init()],
....
  • Özel rapor üretecinizle

failFast Ayarıyla

Testlerin birinde beklentiyi karşılamazsa tüm testleri durdur. expect Eğer istenilen çıktıyı üretmezse hata fırlatılır ve bu hatayı yakalamak için try catch ile sarmanız gerekir. İşte tüm hataları toplayıp bize raporda sunuyor Jasmine. Derseniz ki hata olduğunda devam etmesin, failFast imdadımıza yetişiyor. Daha önceden stoppingOnSpecFailure özelliği veya komut satırı arayüzünde (Command Line Interface) --stop-on-failure anahtarıyla durdurabiliyorken bu özellik kaldırılacak diye işaretlenmiş.

https://github.com/jasmine/jasmine/blob/cd1131354bea551ef11478e313fe66fb11721cf4/src/core/Env.js#L509

failFast özelliğini configure ile veriyoruz:

jasmine.jasmine.getEnv().configure({
...
failFast: true
...

Özetle; Jasmine npm paketini kullanıyorsanız, komut satırında --fail-fast ile veya jasmine.json dosyanızdaki stopOnSpecFailure seçeneğini ayarlayarak geçebilirsiniz.

Ayrıca Jasmine Ruby gem veya Python kullanıyorsanız, jasmine.yml dosyasında bir stop_on_spec_failure seçeneğini kullanabilirsiniz. ***

Özel Rapor Üreteçle

specFilter hali hazırda getEnv() fonksiyonunun ardından kullanılabiliyor ancak sonraki sürümlerde sadece ayarlar ile verilmesi gerekecek.

Deprecated Kullanımı

jasmine.getEnv().specFilter = function (spec) {
console.log("----->", spec);
return true;
};

Geçerli kullanımı:

jasmine.jasmine.getEnv().configure({
...
specFilter: function(spec) {
...
},
...
})

Beklenti (expect) Olmayan Testi Başarısız Kıl

jasmine.loadConfig({
...
// Test içinde expect ifadesi yoksa, doğrudan başarısız ilan et
failSpecWithNoExpectations: true
...

Tüm Beklentileri (hata olsa bile) Dene

Bir testin içinde birden fazla expect varsa ve yalnızca bir beklenti karşılanmazsa başarısız ilan edilsin istiyorsak oneFailurePerSpec değerini true yapmalıyız. Eğer aynı testin (it, spec nasıl çağırıyorsan işte) içinde birden fazla expect var ve hepsi çalışsın ve başarısız olanları suitInfo.failedExpectations dizisinde görmek istersen oneFailurePerSpec değerini false işaretlemelisin.

jasmine.jasmine.getEnv().configure({
...
oneFailurePerSpec: false,
failFast: true
...

Devre dışı bırakılmış (disabled) Testleri Raporda Gizle

Raporda devre dışı bırakılan testleri çıktıda gizleyip gizlemeyeceklerini bu özellikle tayin ediyoruz. Şu anda yalnızca Jasmine’nin HTMLReporter’ı destekliyor.

jasmine.jasmine.getEnv().configure({
...
hideDisabled: true
...

Özel Raporcu (Custom Reporter)

Kendimize özgü rapor oluşturmak çok ama çok pratik yapılandırılıyor. Bunu yapmak için çeşitli fonksiyonları uygulamış (implement) bir JSON nesnesini rapor aracınız olarak raporcular arasına ekliyorsunuz (jasmine.jasmine.getEnv().addReporter(benimRaporum)) ve ta taam. Kaynak olarak şu adrese bakabilirsiniz.

Hemen şu fonksiyonların listesini ve nasıl bir JSON nesnesi olacağına bir bakalım:

var benimCanimRaporum = {
jasmineStarted: function(...){..},
suiteStarted : function(...){..},
specStarted : function(...){..},
specDone : function(...){..},
suiteDone : function(...){..},
jasmineDone : function(...){..},
};

Ana fikir şu:

  • jasmine, suite ve spec adımlarında
  • başladığında (started) ve tamamlandığında (done)

çalışacak fonksiyonları bizden bekliyor olacak.

Bir örnek hazırladım ama siz de çatallayıp (fork) değiştirebilirsiniz.

Yardımcı Araçlar

Jasmine çatısı bazı yardımcı işlevleri (fonksiyon) util içinde hizmetimize sunuyor:

new Jasmine().util: {
inherit: [Function],
htmlEscape: [Function],
argsToArray: [Function],
isUndefined: [Function],
arrayContains: [Function],
clone: [Function],
cloneArgs: [Function],
getPropertyDescriptor: [Function],
objectDifference: [Function],
has: [Function],
errorWithStack: [Function: errorWithStack],
jasmineFile: [Function],
forEachBreakable: [Function]
}

--

--

Cem Topkaya
Cem Topkaya

Written by Cem Topkaya

Evlat, kardeş, ağabey, eş, baba, müzik sever, öğrenmek ister, paylaşmaya can atar, iyi biri olmaya çalışır, hakkı geçenlerden helallik ister vs.

No responses yet