Jenkins + e2e + Jasmine İle Test
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/folder
container- 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
ilechrome
/firefox
gezginlerinden birinden veya hepsinden istediğimiz kadar örnek yaratarakbaseUrl
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ığındajasmine
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ı>
Ş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
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
Odaklandığınız test kümeleri ve birimleri için:
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
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
[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/Jasmineconsole.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ş.
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
vespec
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]
}