3 Haziran 2011 Cuma

Sınama Aracı Olarak BeanShell

Herhangi bir mühendislik ürününün en önemli özelliği, ister oturduğunuz ev olsun isterse kullanmakta olduğunuz yazılım, işlevini spesifikasyonlarına uygun bir şekilde görmesidir. Doğruluk olarak adlandırılan bu özellik, ürünün inşa edilmesi sırasında yapılan tetkikler ile denetlenebileceği gibi kullanım esnasında ortaya çıkan aykırı davranışların yönlendirmesi sonrasında yapılan bakım çalışmaları ile de sağlanabilir.1 İşte bu yazımızda, Java programlama dili kullanılarak yazılan sınıfların nasıl sınanabileceğine dair bir seçenek sunan BeanShell'e göz atacağız. Bir diğer deyişle, Java kaynak kodunun doğruluğunun nasıl temin edilebileceğine değineceğiz.


Temel BeanShell

BeanShell yorumlayıcısı, gerçekleştirimi barındıran arşiv dosyasının http://beanshell.org/ adresindeki Download sekmesinden indirilip sınıf yolu üzerindeki bir yere konulmasından2 sonra ilişkin sınıfın JSM'ye yüklenmesi ile başlatılır. Kimi ortamlarda, komut satırından bsh girilmesi de yeterli olacaktır.3 Yorumlayıcıdan çıkış ise BeanShell komut kabuğunda girilecek exit ile sağlanabilir.

Aşağıdaki kod parçasından da görülebileceği gibi, dinamik türlemeli olan BeanShell'de bir tanımlayıcının ait olduğu türün belirtilmesi gerekmez; aynı tanımlayıcının farklı zamanlarda farklı türlerden değerlere sahip olması da mümkündür. Ancak bu, söz konusu tanımlayıcının kısıtsız bir biçimde kullanılabileceği anlamına gelmez; tanımlayıcıya atfın yapıldığı andaki türle uyumsuz bir kullanım hataya neden olacaktır.
$ # Bazı ortamlarda bsh komutu da aynı görevi görür.
$ java bsh.Interpreter
BeanShell 2.0b4 - by Pat Niemeyer (pat@pat.net)
bsh % // Tanımlayıcı türlerinin ilan edilmesi gerekmez.
ad = 2;
bsh % ad = ad + ".Mehmet";
bsh % print("Ad: " + ad);
Ad: 2.Mehmet
bsh % exit();
$
Standart çıktıya değer göndermek için print'e ek olarak show komutu da kullanılabilir. Her kullanımında bir bayrak değişkeninin o anki durumunu değilleyerek aç-kapa düğmesi gibi işini gören bu komut, işlem gören deyimlerin sonucunun standart çıktıya basılıp basılmayacağını belirler.
bsh % ad = "Ahmet Adnan";
bsh % soyad = "Saygun";
bsh % ad + " " + soyad;
bsh % /* Sonuçlar basılacak */ show();
bsh % ad + " " + soyad.toUpperCase();
<Ahmet Adnan SAYGUN>
bsh % show();
bsh % ad;
bsh % 
Bilinmesinde yarar bulunan diğer BeanShell komutlarından getClassPath, kullanıldığı andaki sınıf yolu ortam değişkeninin (CLASSPATH) hangi dizin ve arşiv dosyalarını içerdiğini döndürürken, addClassPath sınıf yoluna yeni bir dizin veya arşiv dosyası ekler; setClassPath ise, CLASSPATH değişkenini argümanındaki java.net.URL dizisinin elemanlarına sahip olacak şekilde günceller.
bsh % print(getClassPath());
java.net.URL []: {
file:/home/tevfik/,
...
file:/usr/lib/jvm/java-6-openjdk/jre/lib/rt.jar,
}
bsh % pwd();
/home/tevfik
bsh % addClassPath("a/b/c");
bsh % print(getClassPath());
java.net.URL []: {
file:/home/tevfik/,
...
file:/usr/lib/jvm/java-6-openjdk/jre/lib/rt.jar,
file:/home/tevfik/a/b/c,
}
Dikkat edilirse, sınıf yoluna eklenen dizin o anki çalışma dizinine eklenerek oluşturulmuş bir mutlak yol ile belirleniyor; pwd ile sorgulanabilen bu yol cd komutu ile değiştirilebilir. Dosya/dizin işlemeye dönük diğer komutlar aşağıda verilmiştir:
dir
- Argümansız kullanıldığında o anki çalışma dizininin içeriğini listeleyen bu komut, karakter katarı argümanla çağrıldığında listelemeyi söz konusu konumun göreceli olduğunu kabul ederek yapar.
cat
- Argümanındaki metin dosyasının içeriğini basar.
cp
- İlk argümanındaki dosya veya dizini ikinci argümanındaki konuma kopyalar.
mv
- İlk argümanındaki dosya veya dizini ikinci argümanındaki konuma taşır.
rm
- Argümanındaki dosya veya dizini siler.
Yukarıda verilen Unix temelli işletim dizgelerindeki adaşları ile aynı işlevli komutlara ek olarak, exec komutunu kullanarak herhangi bir komutu BeanShell komut satırından çalıştırabiliriz. Örneğin, aşağıdaki komut çalışma dizininin ayrıntılı bir listesini basacaktır.
bsh % exec("ls -la");
Dışsal bir uygulamanın çalıştırılması için kullanılan exec'e benzer bir komut, Lisp temelli dillerden birini bilenlere tanıdık gelecek olan eval'dir. Bu komut, argümanında geçirilen karakter katarını BeanShell komutları olarak algılar ve sanki komut kabuğundan girilmişçesine yorumlar. Dolayısıyla, yorumlama esnasında yaratılan olası yan etkiler komut sonrasında da geçerli olacaktır.
bsh % a = 5;
bsh % b = 10;
bsh % eval("c = a * b;");
bsh % print(c);
50

BeanShell Kaynak Dosyaları

Diğer komut kabuğu dillerinde olduğu gibi, BeanShell de komutların toptan okunup işlenmesi için araçlar sunar. Bunun için kullanılabilecek komutlardan source, Bash'in . komutuna benzer bir şekilde, argümanında verilen ada sahip dosyayı işleyerek yorumlayıcının aduzayını günceller. Örneğin, aşağıda verilen içeriğe sahip Kapasite.bsh dosyasının içselleştirilmesi sonrasında yorumlayıcının aduzayı söz konusu dosyada tanımlanmış olan kapasiteTest metodu ile zenginleştirilmiş olacaktır.

Kapasite.bsh
kapasiteTest(ilkKap, sonKap) {
  vec = new Vector(ilkKap);
  kap = vec.capacity();
  print("İlk kapasite: " + kap);
  for (i = 1; i < sonKap; i++) {
    vec.add(i);
    if (vec.capacity() != kap) {
      kap = vec.capacity();
      print("Yeni kapasite: " + kap);
    }
  }
} // kapasiteTest(ilkKap, sonKap) sonu
bsh % source("Kapasite.bsh");
bsh % kapasiteTest(0, 10);
İlk kapasite: 0
Yeni kapasite: 1
Yeni kapasite: 2
Yeni kapasite: 4
Yeni kapasite: 8
Yeni kapasite: 16
bsh %
Benzer bir etkiye sahip olan run, içselleştirdiği dosyanın içindeki tanımları komutla birlikte yaratılan yeni bir yorumlayıcı aduzayı içine yerleştirir. Yani, run komutunun işlemesi öncesi ve sonrasındaki aduzayları arasında fark olmayacaktır.

Kod Örtüsü ve Nesneler

Fonksiyonel programlama dillerine aşina olanlar bilir, nesne kod örtüsünden (İng., closure) evrilmiş bir kavramdır. Özetleyecek olursak, bir ya da birden çok fonksiyonda serbest tanımlayıcı olarak var olan ve bu fonksiyonlar tarafından "yakalanmak" suretiyle örtülen verilerin bileşkesine nesne denir. Söz konusu nesne, fonksiyonların çağrılması sırasında varsayılan argüman olarak manipüle edilir.

BeanShell, önceki paragrafta ne kastettiğimizi açmak için güzel bir araç sunar: içiçe metotlar. Gelin bunu aşağıdaki sayaç nesnesi gerçekleştiriminden anlamaya çalışalım.

Sayaç.bsh
yeniSayaç(ilkDeğer) {
  print("Hoşgeldiniz...");
  s = ilkDeğer;
  sayaç() { return s; }
  artır() { s = s + 1; }
  print("Gene bekleriz.");
  
  return this;
} // yeniSayaç(ilkDeğer) sonu
Sayaç nesneleri için yapıcı metot görevini gören yeniSayaç, standart çıktıya bilgilendirme mesajları basmaya ek olarak, gövdesinde tanımlanan her iki metotta da serbest değişken olarak geçen s'yi yapıcının çağrıldığı noktada geçirilen argümanın değerine sahip yerel değişken ile bağlamaktadır. Nesne paradigmasından alıştığımız biçimde ifade edecek olursak, yaratılacak sayaç nesnelerinin ilk değeri yapıcıya geçirilen değer olan s adlı bir alt alanı olacak ve bu nesneler artır ve sayaç adlı metotlar ile işlenecektir. Ancak, altalanın dışarıdan müdaheleye karşı korunmadığı dikkatli olanlarınızın gözünden kaçmamıştır.4
bsh % m = yeniSayaç(0);
Hoşgeldiniz...
Gene bekleriz.
bsh % m.artır();
bsh % m.artır();
bsh % n = yeniSayaç(10);
Hoşgeldiniz...
Gene bekleriz.
bsh % print(n.sayaç());
2
bsh % print(m.s);
10 

BeanShell İle Birim Sınama

Bir betik dili olan BeanShell'in yorumlayıcısı, BeanShell'in yanısıra Java programlama dili sözdiziminin büyük kısmını desteklemesi nedeniyle, Java'nın komut kabuğu olarak da görülebilir. Yani, Java komutlarını BeanShell yorumlayıcısının komut satırından etkileşimli bir biçimde girebiliriz. Girilen komutun sonucuna göre bir sonraki komutu seçebileceğimiz ve ortaya çıkacak olası hataları anında görebileceğimiz anlamına gelen bu özellik, BeanShell yorumlayıcısının aynı zamanda Java için verimli bir sınama ortamı oluşturması demektir.

BeanShell'den Java kodu sınamak amacıyla yararlanmak için yorumlayıcı ortamını Java'nın kurallarına daha sıkı uyulan sıkı denetleme kipine geçirmemiz gerekir. Bu, setStrictJava komutunun true argümanı geçirilerek kullanılmasıyla sağlanır.
bsh % ad = "Ahmet Adnan";
bsh % setStrictJava(true);
bsh % soyad = "Saygun";
// Error: EvalError: (Strict Java mode) Assignment to undeclared variable: soyad 
: at line: 3 : in file: <unknown file> : soyad = "Saygun"
bsh % String soyad = "Saygun";
Sıkı denetim kipine geçildikten sonra yapılması gereken şey, birim sınamasını yapmak istediğimiz sınıfı import bildirimi ile görünür hale getirmek ve gerek source (ve run) ile okuyarak gerekse komut satırından girerek sağlanan Java kodu ile sınıfımızın işlevselliğini sınamaktır. Bu noktada, BeanShell yorumlayıcısının JSM'ye yüklenmesi sırasında java.lang paketinin yanısıra java.awt, java.awt.net, java.io, java.net, java.util, javax.swing, javax.swing.event paketlerinin de otomatikman görünür hale getirildiği bilinmelidir. Dolayısıyla, listelenen paketlerin import ile ithal edilmesine gerek yoktur.
bsh % import a.b.c.XYZ;
bsh % XYZ x = new XYZ(), y = new XYZ(10);
bsh % // XYZ nesnelerinin davranışını sına.
bsh % ...
bsh % reloadClasses();
bsh % // Sınamaya yeniden başla.
bsh % ...
Sınanmakta olan sınıfta hata saptanması durumunda ilişkin kod parçası gözden geçirilmeli ve değişiklik yapıldıktan sonra sınıfımızın yeni halinin BeanShell yorumlayıcısına görünür kılınması gereklidir ki, bu reloadClasses komutu ile sağlanır. Argümansız kullanıldığında sınıf yolu üzerindeki sınıflar arasından değişmiş olanların tümünü yeniden JSM'ya yükleyen bu komut, istenecek olursa bir paketteki tüm sınıflara veya tek bir sınıfa yönelik olarak da icra edilebilir. Ancak, arşiv dosyaları içindeki sınıfların yeniden yüklenemeyeceği bilinmelidir.

BeanShell yorumlayıcısına sağlanan Java kodunda kimi kısıtlamaların olduğu unutulmamalıdır. Bunlardan en önemlisi, soysal türlerin tür argüman(lar)ı olmaksızın kullanılması zorunluluğudur. Başta büyük bir gaf gibi gözükebilecek bu eksiklik Java kodunun BeanShell tarafında nasıl ele alındığı öğrenildiğinde mazur görülecektir: yorumlayıcıya görünür kılınan Java sınıfları, kaynak kod düzeyinde ele alınmaz, derleme sonucunda oluşturulan sınıf dosyalarının yüklenmesi yoluyla kullanılır. Bu da, derleme sırasında yer alan tür silme işlemi nedeniyle soysallık bilgilerinin sınıf dosyasına yansıtılmayacağı ve dolayısıyla BeanShell yorumlayıcısının dikkatine sunulan dosyalarda soysallık kavramının aslında hiç olmayacağı anlamına gelir.

Birim sınama bağlamında değineceğimiz son nokta, muhtemelen, tümleşik geliştirme ortamlarına alışık arkadaşların haklı bir beklentisini karşılayacaktır: BeanShell'in tipik kullanımı, yazımızda önerildiği gibi komut satırından çalıştırılan yorumlayıcıya başka bir ortamda geliştirilerek elde edilen sınıf dosyalarının sağlanması ve bu sınıfların sınanması değildir. Normalde, Eclipse, Emacs veya jEdit gibi geliştirme ortamlarında BeanShell yorumlayıcısı, Java düzenleyici penceresine özel bir konsol penceresi olarak eklemlenir ve programcı eşgüdümlü bir biçimde yönetilen iki pencere arasında gidip gelerek sınama işini hızlı bir şekilde yapar.
 

  1. İnşa edilen ürünün yazılım olması durumunda, yapılan tetkike sınama bakım çalışmalarına ise hatadan arındırma denilir.
  2. Bu işlem, indirilen arşiv dosyasının diskte uygun bir konuma yerleştirilmesi sonrasında mutlak konum adı kullanılarak CLASSPATH ortam değişkenine eklenmesi kadar basittir.
  3. Aslında seçeneklerimiz bunlarla sınırlı değil. Görsel arayüz sevdalıları, java bsh.Console ve xbsh komutlarından biri ile emellerine ulaşabilirler.
  4. Verdiğimiz örnek, tüm altalanların tüm metotlarca kullanılacağını düşündürmesin; kimi metotlar kimi altalanları kullanmadan da işini görebilir.

Hiç yorum yok:

Yorum Gönder

Not: Yalnızca bu blogun üyesi yorum gönderebilir.