Yazı İçeriği
Giriş
Bir kod üzerinde yaptığınız ekleme ve düzenlemelerle, kodun daha önceden çalışan bir özelliğini bozduğunuz, bunu da ancak bir süre sonra başka bir yerden hata gelince fark ettiğiniz oldu mu?
Test kodları yazarak ve sık sık çalıştırarak, hem bu sorunun önüne geçebilir, hem kodunuzun işlevlerini anlaşılır ve temiz bir şekilde gösterebilirsiniz.
Test kodu nedir?
Yazdığınız fonksiyonları ve diğer kodları zaten test ediyorsunuzdur. Mesela bir denklemi kodluyorsanız, kodlama bittikten sonra bilinen sayısal örnek değerleri deneyip doğru sonuç verdiğini teyit ediyorsunuzdur. Mesela sabit hızla hareket denklemini (konum = ilk_konum + hız * süre) bir Matlab fonksiyonu haline getirdikten sonra birkaç farklı süre, hız ve ilk konum değeriyle deneme yapıyor olabilirsiniz:
hareket_denklemi.m
function [son_konum] = hareket_denklemi(ilk_konum, hiz, sure) %HAREKET_DENKLEMİ x_f = x_i + v * t son_konum = ilk_konum + hiz * sure; end
İki deneme:
>> hareket_denklemi(15, 0, 20)
ans =
15
>> hareket_denklemi(0, 5, 4)
ans =
20
Yazdığım iki deneme, kodun o an çalıştığını gösteriyor. Bu denemeleri kaydederek ve her değişiklikte tekrar çalıştırarak daha hızlı ve rutin bir kontrol elde edebilirim. Matlab bu süreci pratikleştiren araçlar sağlıyor.
Elle denemeler yapmak yerine bu araçları kullanarak,
- Tüm testlerinizi veya seçtiğiniz bir kısmını, kod her değiştiğinde veya istediğiniz zaman pratik bir şekilde tekrar tekrar yapabilirsiniz,
- Tüm testleri aynı anda çalıştırıp normalde daha geç fark edeceğiniz hataları erkenden fark edebilirsiniz,
- Test kodlarını içeren dosyalara bakan biri kodunuzun ne işe yaradığını testlerdeki örneklerden anlayabilir,
- Testlerinizi her düzenlemeden sonra çalıştırarak kodunuzun en azından temel işlevlerinde önemli bir hata olmadığından emin olabilirsiniz,
- Asıl koddan önce testlerinizi yazarak hedeflerinizi belirleyebilirsiniz.
Matlab ile test dosyası nasıl oluşturulur?
Matlab testlerinizi kaydetmek için üç temel yöntem sağlar. Script tabanlı, fonksiyon tabanlı ve class tabanlı testler. Script tabanlı olanların öğrenilmesi ve yazılması daha kolayken fonksiyon ve class tabanlı olanlar daha karışık olsalar da testlerinize ek özellikler sağlarlar. Bu yazıda kendi kullandığım script tabanlı testleri açıklayacağım.
İlk olarak test dosyasını bir script olarak oluşturacağım. Bu dosyanın adı “test” ifadesi ile başlamalı veya bitmeli, aksi durumda Matlab onun bir test dosyası olduğunu anlamaz. Projenizin tüm testlerini tek dosyada tutabileceğiniz gibi, gruplara (farklı dosyalara) ayırabilir veya her birini ayrı bir dosyaya kaydedebilirsiniz.
Yukarıda oluştuduğum hareket_denklemi fonksiyonu ile aynı klasörde, test_matlabturkiye.m adında bir dosya oluşturdum.
Dosyanın içindeki testler, bölümlerle (section) birbirlerinden ayrılırlar. Yani “%% Test Adı” şeklinde bir satırla. Örneğin yukarıda command window’da yaptığım iki test için şu şekilde bir dosya oluşturdum:
test_matlabturkiye.m
%% Sifir hiz ilk_konum = 15; hiz = 0; sure = 20; beklenen_sonuc = ilk_konum; sonuc = hareket_denklemi(ilk_konum, hiz, sure); assert(sonuc == beklenen_sonuc) %% Sifir konumdan sabit hizla hareket ilk_konum = 0; hiz = 5; sure = 4; beklenen_sonuc = 20; sonuc = hareket_denklemi(ilk_konum, hiz, sure); assert(sonuc == beklenen_sonuc) Bu dosyada her bölümün %% ile başlıyor olması Matlab’in testleri algılayabilmesi için önemlidir. assert fonksiyonuna, olması gereken (True değer döndürmesi gereken) durumu yazıyorsunuz. Yani assert(sonuc == beklenen_sonuc) yazmakla if ~(sonuc == beklenen_sonuc) error("Hata") end
yazmak aşağı yukarı aynı şeydir. Ama assert ile amacımızı daha net belirtip tek satırda çözebiliyoruz.
Yukarıdaki örneklerde fonksiyonun döndürdüğü değerin beklenen_sonuc olarak manuel bir şekilde test dosyasına yazdığım değerle aynı olduğunu teyit ettirdim.
Artık runtests komutu ile testleri her istendiğinde çalıştırabilirim. Matlab, aktif klasördeki “test” ifadesi ile başlayan ya da biten tüm dosyalardaki testleri çalıştıracak. Şu an sadece bir dosya var.
>> test_sonuclari = runtests
Running test_matlabturkiye
Done test_matlabturkiye
__________
Totals:
2 Passed, 0 Failed, 0 Incomplete.
0.028964 seconds testing time.
Matlab, test_sonuclari değişkenine sonuçlarla ilgili bazı detayları kaydetti. Ayrıca yazılı olarak da iki test bulduğunu ve bu iki testin de başarılı olduğunu yazdı. Başarısız olan olsaydı bunu da belirtecekti.
Diyelim ki yapılan yanlış bir düzenlemeyle hareket_denklem.m dosyasından “ilk_konum” silindi. Yani hesaplamayı yapan dosya şu hali aldı:
hareket_denklemi.m
function [son_konum] = hareket_denklemi(ilk_konum, hiz, sure) %HAREKET_DENKLEMİ x_f = x_i + v * t son_konum = hiz * sure; end
Şimdi runtests komutu çalıştığında bize hatayı gösterecek:
>> test_sonuclari = runtests
Running test_matlabturkiye
================================================================================
Error occurred in test_matlabturkiye/SifirHiz and it did not run to completion.
Error ID:
'MATLAB:assertion:failed' Error Details:
Error using test_matlabturkiye (line 6)
Assertion failed.
================================================================================
Done test_matlabturkiye
Failure Summary:
Name Failed Incomplete Reason(s)
============================================================
test_matlabturkiye/SifirHiz X X Errored.
Totals:
1 Passed, 1 Failed (rerun), 1 Incomplete.
0.043302 seconds testing time.
Şimdi hem test_sonuclari değişkeninde, hem de yazılı sonuçlarda iki testten birinin başarısız olduğunu ve başarısız olan testin “test_matlabturkiye” dosyasındaki “SabitHiz” olduğunu belirtiyor. Testin adı (“Sabithiz”) test dosyasında %% ile başlatılan bölüm satırından algılanıyor.
Artık bu hatayı düzelttikten sonra, hareket_denklemi fonksiyonu üzerine güvenle çalışmaya devam edebilirim. Sözgelimi, ivmeyle de hesap yapabilmek gibi ek özellikler ekleyebilirim. Ek özellikler ve düzenlemeler yapıldıkça, her seferinde varsa yeni testleri de dosyaya ekleyip runtests komutunu çalıştıracağım. Böylece tüm denemelerim hızlıca yapılacak, çalışan bir yeri bozmadığımdan emin olacağım, kodumu kullanmak isteyen başka biri testleri kendisi de inceleyerek ve çalıştırarak kodun nasıl çalıştığını öğrenebilecek ve düzgün çalıştığını teyit edebilecek.
Ek Bilgiler
Testleri yukarıdaki gibi oluşturup kullanabilirsiniz. Testler konusunda birçok detay daha var. İlk başta işe yarayabilecek birkaç detaydan daha bahsedeceğim.
Neler test edilebilir?
Yukarıdaki örneklerde iki sayısal sonuç ile test yaptım. Ancak, true/false sonuç alabildiğiniz her şeyi test edebilirsiniz. Örneğin eşitlik (==) yerine eşit değildir, büyüktür gibi operatörleri kullanabilirsiniz. Değer yerine değişken türünü isa fonksiyonuyla, matematiksel bir sonucun reel bir sayı karmaşık sayı barındırmadığını isreal fonksiyonu ile deneyebilirsiniz. Dosyaya çıktı vermesi gereken bir test yapıyorsanız, dosyayı okuyup içeriğini değerlendirebilir ya da basitçe bir dosya oluşmuş mu diye bakmak için isfile fonksiyonunu kullanabilirsiniz. Birden fazla fonksiyonu çağırıp, class oluşturup, bunlardan elde ettiklerinizi de teste tabi tutabilirsiniz.
Fazladan, belki “burası asla bozulmaz zaten” dediğiniz kısımların bile testinin yazılmasında bir zarar yoktur.
Test dosyasındaki bölümler birbirlerinden bağımsızdır
Test dosyasında %% işaretiyle oluşturduğunuz bölümler, yani testler birbirlerini etkilemezler. Yani, bir bölüm hata verse de Matlab bir sonraki bölümü çalıştırır (normal bir script olsaydı, hata verdiği yerde dururdu). Ayrıca, bir bölümde yapılan değişken tanımlamaları ve değişiklikleri diğer bölüme taşınmaz. Bu sayede testler birbirlerini etkilemez.
Örneğin, yukarıdaki test dosyası örneğinde ilk_konum, hız, sure ve beklenen_sonuc değişkenlerini her seferinde baştan tanımladım. Aşağıdaki gibi bir test kodu yazsaydım:
%% Sifir hiz ilk_konum = 15; hiz = 0; sure = 20; beklenen_sonuc = ilk_konum; sonuc = hareket_denklemi(ilk_konum, hiz, sure); assert(sonuc == beklenen_sonuc) %% Sifir konumdan sabit hizla hareket sonuc = hareket_denklemi(ilk_konum, hiz, sure); assert(sonuc == beklenen_sonuc)
Matlab ikinci testteki işlemin sonucu doğru olsa bile hata verirdi, “Sifir konumdan sabit hizla hareket “ testini başarısız (failed) ve tamamlanamayan (incopmplete) olarak işaretlerdi. Çünkü, normal bir matlab kodunda ikinci assert satırı üstünde tanımlanan ilk_konum gibi değişkenlere erişebilse de, testlerde her bölümün kendi değişken hafızası (workspace) olduğu için bu kod çalışmazdı.
Bunun bir istisnası şudur: Test dosyasının sonunda tanımladığınız fonksiyonlar, tüm bölümler tarafından ulaşılabilir. Burada birçok testte kullanacağınız fonksiyonları tanımlayabilirsiniz.
Test dosyalarının giriş bölümünü kullanmak
Test dosyasında, %% ile ilk bölümü belirtmeden önce yazdıklarınız giriş kısmıdır. Burayı temelde iki amaçla kullanabilirsiniz.
Birincisi, burada tanımladığınız değişkenlere tüm testler tarafından erişilebilir. Yani burada birden fazla testte kullanacağınız değişkenleri tanımlayabilirsiniz. Örneğin, birçok testiniz bir dosyadaki verilerin okunup işlenmesini içeriyorsa, dosya adını giriş kısmında bir kere tanımlamanız yeterli.
test_dosya.m
% Giris dosya = "matlabturkiye.xlsx"; %% Test 1: Tablo 20 satir olmali tablo = readtable(dosya); assert(height(tablo)==20); %% Test 2: Tablo 10 sutun olmali tablo = readtable(dosya); assert(width(tablo)==10);
İkinci kullanım, o dosyadaki testler başlamadan önce ön koşul oluşmasıdır. Mesela yukarıdaki örnekte, matlabturkiye.xlsx adlı dosya yoksa tüm testler ayrı ayrı çalışmaya çalışacak ve hata verecekler. Bunu önlemek için giriş kısmında dosyanın varlığını kontrol ettirebilirim:
% Giris dosya = "matlabturkiye.xlsx"; assert(isfile(dosya)); %% Test 1: Tablo 20 satir olmali tablo = readtable(dosya); assert(height(tablo)==20); %% Test 2: Tablo 10 sutun olmali tablo = readtable(dosya); assert(width(tablo)==10);
Bu durumda matlabturkiye.xlsx dosyası yoksa, Matlab
Error occurred while setting up or tearing down test_dosya.
As a result, all test_dosya tests failed and did not run to completion.
şeklinde durumu bildirecek ve tek tek her testi denemeyecektir.
Toleranslarla test yapmak
Birçok matematiksel hesaplamada, eşitlik operatörü değerler tam olarak eşleşmediği için veya bilgisayarların veri saklama yöntemleri sebebiyle normalde eşit olarak göreceğiniz yerlerde eşitlik görmeyebilir.
Örneğin,
>> assert(0.3 – 0.2 – 0.1 == 0)
Assertion failed.
Böyle durumlarda toleranslar kullanabilirsiniz. Toleranslarla karşılaştırma yapabilen bir yardımcı fonksiyon yazmak pratik olacaktır. Matlab dokümantasyonunda örneği bulunabilir.
Alt klasörlerdeki testleri çalıştırmak
Runtests komutu çalıştığında, aktif klasörünüzdeki testleri bulup çalıştırır. Ben testlerimi klasörlerimi kalabalıklaştırmamaları için testler adında bir alt klasörde tutmayı tercih ediyorum. Runtests komutunun alt klasörlerde tutuğunuz testleri görmesi için şu şekilde çalıştırılması gerekiyor:
>> test_sonuclari = runtests(pwd, 'IncludeSubfolders', true);
Ancak bu durumda da, test edilen fonksiyonlar ve classlar pathe eklenmemişse hata verebiliyor. Ben hem testleri çağırma hem de mevcut klasörümü ekli değilse geçici olarak pathe eklemek için aşağıdaki kodu favourites kısmına ekledim.
% Aktif ve alt klasorlerindeki testleri calistirip test_sonuclari degiskenine
% atar.
path_ekli_mi_9999_ = any(string(strsplit(path,';'))==pwd);
if ~path_ekli_mi_9999_
addpath(pwd);
end
test_sonuclari = runtests(pwd, 'IncludeSubfolders', true);
if ~path_ekli_mi_9999_
rmpath(pwd);
end
clearvars("path_ekli_mi_9999_");
Assert’e açıklama eklemek
Assert fonksiyonuna, sprintf fonksiyonuna benzer şekilde ek hata bilgileri de iletebilirsiniz.
assert(hareket_denklemi(ilk_konum, hiz, sure) == beklenen_sonuc)
yerine
assert(sonuc == beklenen_sonuc, ‘Beklenen sonuç: %g, Elde edilen sonuç: %g’, beklenen_sonuc, sonuc);
Bu test başarısız olursa “Beklenen sonuç: 20, Elde edilen sonuç: 15”, şeklinde bu yazı da çıkacaktır.
Kodun hata verdiğini test etmek
Bazı durumlarda kodunuzun hata vermesini istiyor olabilirsiniz. Fonksiyon ve class temelli testlerde bu doğrudan mümkün, ama script temelli olanlarda da try-catch ile bu sağlanabilir:
%% Sure olmadan kod calismamali, hata vermeli
ilk_konum = 0;
hiz = 5;
beklenen_sonuc = 20;
try
hareket_denklemi(ilk_konum, hiz);
calisti = true;
catch
calisti = false;
end
assert(~calisti, "Sure degeri girilmemesine ragmen kod hatali olarak calismis.")
Diğer özellikler
Matlab’in test dokümantasyonunda birçok detay daha görebilirsiniz. Bu detaylar fonksiyon ve classlarla test tanımlama, assert’ten daha kapsamlı karşılaştırma yöntemleri, sonuçların değil hızın test edilmesi, testleri gruplandırma, testleri paralel olarak çalıştırma, görsel arayüzleri test etme gibi ek bilgileri içeriyor.
İlgili Dokümantasyon Linkleri:
Test dokümantasyonu:
https://www.mathworks.com/help/matlab/matlab-unit-test-framework.html
Script içinde yardımcı fonksiyon tanımlanmış bir test dosyası: https://www.mathworks.com/help/matlab/matlab_prog/write-script-based-test-using-local-functions.html#d122e72648
Sectionlar (bölümler):
https://www.mathworks.com/help/matlab/matlab_prog/run-sections-of-programs.html
Path:
https://www.mathworks.com/help/matlab/matlab_env/what-is-the-matlab-search-path.html
Favourites:
https://www.mathworks.com/help/matlab/matlab_env/create-matlab-favorites-to-rerun-commands.html