9 Ocak 2012 Pazartesi

Ubuntu'da /tmp dizininin otomatik silinmesini engellemece

# sudo vim /etc/default/rcS

Change from:

TMPTIME=0

To:

TMPTIME=1

19 Aralık 2011 Pazartesi

Ubuntu üzerinde Ruby On Rails kurulumu yapmak

Ubuntu 10.04 üzerinde Ruby kurulumu gerçekleştireceğiz.

sudo apt-get install aptitude
sudo aptitude update
sudo aptitude install build-essential git-core curl
bash < <(curl -s https://rvm.beginrescueend.com/install/rvm)
echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"' >> ~/.bashrc

. ~/.bashrc

rvm requirements

/usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion

sudo /usr/bin/apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion

rvm install 1.9.2
rvm use 1.9.2
ruby -v
rvm --default use 1.9.2

$rvm gemset create YOUR_GEMSET_NAME
$rvm 1.9.2@YOUR_GEMSET_NAME
$gem install bundler

$ gem install rails --no-rdoc --no-ri
$ rails new RAIL_APP

Edit Gemfile
add/edit this group definition

group :development, :test do
gem 'rspec-rails'
end

$ bundle install

$ sudo apt-get update
# Install needed packages
$ sudo apt-get install git-core curl build-essential openssl libssl-dev
# Install node.js
$ git clone https://github.com/joyent/node.git && cd ~/Development/NodeJS
$ ./configure
$ make
$ sudo make install
$ cd
# Install npm
$ curl http://npmjs.org/install.sh | sudo sh
#Install CoffeeScript
$ sudo npm install -g coffee-script

$ sudo apt-get install libmysql-ruby libmysqlclient-dev
#change gem 'sqlite3' to gem 'mysql2' in Gemfile
$ bunlder install

$ rake db:create

#make slim as a default template add this "Slim::Engine.set_default_options :sections => true" line to config/environment.rb

Create a migration file of exist schema, run this command; this command generates a file in db/schema. Last command reload schema, you can see the effect on schema_migrations table.
$ rake db:schema:dump RAILS_ENV=development
$ rake db:schema:load

9 Aralık 2011 Cuma

Kanban Ruhu

Şu sıralar "Kanban - Successful Evolutionary for Your Tech Business" kitabını okuyorum. Kanban kavramına şu an çalıştığım yerde ilgili olmaya başladım ama aslında bu sistemi daha önce çalıştığım yerde ruhumuzda oluşturmuşuz zaten. Scrum süreç yönetimini uygulayan bir şirketin ufak parçalara ayrılmasından sonra adı Scrum olmayan ama yime Project Owner, Tester, Developer birimlerinden - her biri bir/iki kişiden - oluşan bir süreç oluşturmuştuk kendimize. Developer yazar, Tester test eder, Project Owner müşteri ile - müşteri parçalanma sonrasında iş veren oldu - konuşur ve iş birimlerini yönetirdi.

Developer ekibi iki kişiden oluşmaktaydı. Bunlardan biri de bendim. İki kişi de farklı projelerden sorumluydu. Lakin şöyle bir sıkıntımız mevcuttu. İki farklı proje ama aynı context altında hizmet vermekte ve aynı trunk üzerinde mergeler geliştirilmekteydi. Bir hikaye tamamlanır tamamlanmaz deployment gerçekleşirdi ama deployment süreci branch'lerin merge edilmesi, merge'den kaynaklanan sorunların tespiti v.b. yaklaşık bir günümü alıyordu. Haftada birkaç kez releaase'lerin çıkması ciddi şekilde iş kaybına neden oluyordu. Bunu çözmemiz gerekiyordu ve bunun için cuma günlerini deployment günü ilan ettik. Her hafta cuma günü düzenli bir şekilde deployment'i gerçekleştiriyorduk.

Bir sonraki haftanın ilk günü ise geçen hafta yapılan çalışmanın özeti ve yapılacakların tartışıldığı bir toplantı yapıyorduk. Bu toplantıda tasarımcı, tester, yazılımcı, project owner ve müşteri bulunuyordu. Böylelikle müşterinin sürekli güncel olması sağlanıyordu.Bu toplantıda o hafta backlog'daki listemizden maaliyetine göre nelerin yapılacağı veya yeni hangi özelliklerin eklenmesi gerektiği tartışılıyordu.

Mevcut sistem içerisinde değişimi yine kendimiz gerçekleştirmiş ve yönetmiş olduk. Kanban mevcut sistemdeki sıkıntıları görmek ve direnci min. seviyede tutarak değişim uygulamak için ideal bir sistem. Bir ritim anlayışını oluşturmakta ve WIP'e limitler koyarak sıkıntıları yönetilebilir hale getirmektedir.

14 Ağustos 2011 Pazar

Sending Basecamp Worklogs to Campfire with Ruby

We collects all system notifications at Campfire Chat Room. We decide to send worklogs entered to Campfire. I wrote the code under. I choosed Ruby Programming Language for fast development.


#!/usr/bin/ruby

require 'rubygems'
require 'rexml/document'
require 'date'

include REXML

$_37SIGNAL_ACCOUNT="YOUR 37 SIGNALS ACCOUNT_NAME"
$CAMPFIRE_TOKENACCESS_KEY="YOUR CAMPFIRE ACCESS TOKEN"
$CAMPFIRE_ROOM_ID="YOUR ROOM ID"
$BASECAMP_TOKENACCESS_KEY="YOUR BASECAMP ACCESS TOKEN"

class TimeEntryIdFile
def self.read
lastWorkLogId = 0
if File.exist? "/tmp/last.worklog.id" then
File.open('/tmp/last.worklog.id', 'r') do |f1|
while line = f1.gets
lastWorkLogId = line
end
end
return lastWorkLogId
end
end

def self.update(lastId)
File.open('/tmp/last.worklog.id', 'w').write(lastId)
end

end

class Campfire
def self.sendMessage(message)
message = message.gsub("'", "-")
`curl -u #{$CAMPFIRE_TOKENACCESS_KEY}:X -H 'Content-Type: application/json' -d '{\"message\":{\"body\":\"#{message}\"}}' https://#{$_37SIGNAL_ACCOUNT}.campfirenow.com/room/#{$CAMPFIRE_ROOM_ID}/speak.json`
end
end

class BaseCamp
BASECAMP_API_COMMAND="curl -k -u #{$BASECAMP_TOKENACCESS_KEY}:X -H 'Accept: application/xml' -H 'Content-Type: application/xml' https://#{$_37SIGNAL_ACCOUNT}.basecamphq.com"

def self.project(projectId)
projectDoc = `#{BASECAMP_API_COMMAND}/projects/#{projectId}.xml`
doc = Document.new(projectDoc)
return Project.parse(doc.root)
end

def self.person(personId)
personDoc = `#{BASECAMP_API_COMMAND}/people/#{personId}.xml`
doc = Document.new(personDoc)
return Person.parse(doc.root)
end

def self.todo(todoItemId)
todoDoc = `#{BASECAMP_API_COMMAND}/todo_items/#{todoItemId}.xml`
doc = Document.new(todoDoc)
return ToDo.parse(doc.root)
end

def self.todoList(todoListId)
todoListDoc = `#{BASECAMP_API_COMMAND}/todo_lists/#{todoListId}.xml`
doc = Document.new(todoListDoc)

return ToDoList.parse(doc.root)
end

def self.timeEntries ()
timeXmlDoc = `#{BASECAMP_API_COMMAND}/time_entries/report.xml`
doc = Document.new(timeXmlDoc)
timeEntries = Array.new
doc.root.each_element('//time-entry') {|timeEntryElement| timeEntries.push(TimeEntry.parse(timeEntryElement)) }
return timeEntries
end

def self.timeEntriesFrom(lastTimeEntryId)
puts "last time entry id #{lastTimeEntryId}"
lastTimeEntries = Array.new
timeEntries = BaseCamp.timeEntries()

timeEntries.each {|timeEntry|
if lastTimeEntryId.nil? or lastTimeEntryId == 0 or timeEntry.id > lastTimeEntryId then lastTimeEntries.push(timeEntry) end
}
return lastTimeEntries
end
end

class ToDoList
def initialize(name)
@name=name
end

def self.parse(xmlElement)
return ToDoList.new(xmlElement.elements["name"].text)
end

def name()
return @name
end
end

class ToDo
def initialize(content, listId)
@content=content
@listId=listId
end

def self.parse(xmlElement)
return ToDo.new(xmlElement.elements["content"].text, xmlElement.elements["todo-list-id"].text)
end

def content()
return @content
end

def listId()
return @listId
end
end

class Project
def initialize(name)
@name=name
end

def self.parse(xmlElement)
return Project.new(xmlElement.elements["name"].text)
end

def name()
return @name
end
end

class Person
def initialize(firstName, lastName)
@firstName=firstName
@lastName=lastName
end

def self.parse(xmlElement)
return Person.new(xmlElement.elements["first-name"].text, xmlElement.elements["last-name"].text)
end

def name()
return @firstName +" "+@lastName
end
end

class TimeEntry
def initialize(id, personId, projectId, todoItemId, hours, desc, date)
@id=id
@personId = personId
@projectId = projectId
@todoItemId = todoItemId
@hours = hours.to_f
@desc=desc
@date = date
end

def self.parse ( xmlElement )
timeEntry = TimeEntry.new(
xmlElement.elements["id"].text,
xmlElement.elements["person-id"].text,
xmlElement.elements["project-id"].text,
xmlElement.elements["todo-item-id"].text,
xmlElement.elements["hours"].text,
xmlElement.elements["description"].text,
xmlElement.elements["date"].text)
return timeEntry
end

def id
return @id
end

def formattedDate()
return Date.strptime(@date, '%Y-%m-%d').strftime("%Y, %b %d")
end

def hours
if @hours == 0.5 then
return "yarim"
elsif (@hours -@hours.floor) == 0.5 then
return @hours.floor.to_s + " bucuk"
else
return @hours
end
end

def message
person = BaseCamp.person(@personId)
project = BaseCamp.project(@projectId)
todo = BaseCamp.todo(@todoItemId)
todoList = BaseCamp.todoList(todo.listId)

message = "\\n\\nToDo List Name : " +todoList.name
message += "\\n\\n@WorkLog "+self.formattedDate()+"\\n\\n"+person.name+",\\n\\n"

if @hours.to_f == 0.0 then
message += "hic ugrasmamis bir de worklog giriyor"
else
message += "'"+todo.content+"' isiyle "+self.hours().to_s+" saat ugrasmis"
end

unless @desc.nil? then
message += ",\\n\\ndedi ki:\\n\\n"+@desc
end

return message
end
end

lastWorklogID=0
lastWorklogID = TimeEntryIdFile.read()
lastTimeEntries = BaseCamp.timeEntriesFrom(lastWorklogID)

if lastTimeEntries.length !=0 then
campfireMessage = ""
lastTimeEntries.each {|timeEntry| campfireMessage = campfireMessage+"\\n"+timeEntry.message()}

Campfire.sendMessage(campfireMessage)
TimeEntryIdFile.update(lastTimeEntries[0].id)
end

30 Temmuz 2011 Cumartesi

Temiz Kod Yazımı : Anlamlı İsimlendirmeler


Kod yazarken oluşturduğumuz herşeye isim vermeye çalışırız. Bazen saatlerce düşünürüz acaba ne isim versem diye. Çünkü aynı ismi başka bir yerde kullanmışızdır. Yeni vereceğimiz isim bir öncekinin eklerler veya tamlamalarla çeşitlendirilmiş hali olabilir. Ama bütün gaye yeni olanı diğerinden ayıran anlamı birkaç kelime ile verebilmektir.

Kod yazan kişi için verilen isim iç açıcı olabilir ama yukardan yani müşteri seviyesinde baktığımız da ne kadar iç açıcı olabilir! "Ben bunu anlamadım." şeklinde bir yaklaşımda bulunulduğunda yazmış olduğunuz eseri taşlıyorlarmış gibi hissedersiniz. Empati kurmaya başladığınızda anlarsınız ki evet gerçtekten hiçbir şey anlaşılmıyor. Artık Business seviyesinde yani patron, müşteri seviyesinde düşünüp ben burada bu değeri verebilmiş miyim diye tekrardan sormamız gerekiyor kendimize. Self-CodeReviewing diyebiliriz belki buna.

Domain Driven Design bize business seviyesinde modelleme yapmamıza olanak sağlar. Wikipedia'da bakın nasıl tanımlanmış "Domain-driven design (DDD) is an approach to developing software for complex needs by deeply connecting the implementation to an evolving model of the core business concepts."
InfoQ'da ki "Domain Driven Design and Development in Practice" isimli bir makale de ise "Domain Driven Design (DDD) is about mapping business domain concepts into software artifacts." olarak tanımlanmış.

Amacı Açıklayan İsimlendirmeler (Intention Revealing Names)

Yapılan isimlendirmeler şu soruya cevap vermeli "Bu ne için var, Ne yapıyor ve Nasıl kullanılmış". Eğer bir isimlendirmeye yönelik bir yorum/açıklama yazılmışsa koda, bilin ki o isimlendirme amacını açıklamıyor.

Mesela:


List<ogrenci> ogrenciler; // devamsızlığı olan öğrenciler


ogrenciler isimlendirmesi yukarıdaki sorularımızın hiçbirine cevap vermiyor.

Aşağıdaki gibi bir isimlendirme kullansaydık sorularımıza cevap verebilirdik ve yorum yazmaya da gerek kalmazdı.


List<ogrenci> devamsizligiBulunanOgrenciler;


Peki aşağıdaki kod ne iş yapmaktadır?


public List<int> icerisindeBul(List<int[]> kaynakListe){
List<int> liste1 = new ArrayList<int[]>;
for ( int[] oge : kaynakListe) {
if ( oge[1] == 2 ) {
liste1.add(oge[0]);
}
}
return liste1;
}


Ne yaptığı anlaşılmakta gibi ama eğer bunu bir utility method olarak tanımlarsak. Bunun dışında bir listeden 2. elemanı 2 olan dizinin 1. elemanını başka bir dizi olarak geri döndürüyoruz. Ama neden???

Soralım:

1. kaynakListe içerisindeki tutulan değerler neyi ifade ediyor?
2. kaynakListe içerisindeki dizilerin 2. elemanının ne gibi bir özelliği var?
3. karşılaştırma 2 değeri neyi ifade ediyor?
4. Döndürülen listeyi ben nasıl kullanacağım?

Bu soruların hiçbirine cevap veremiyoruz değil mi?

Kodumuzu şöyle yazsaydık:


public List<int> devamsizlikYapanOgrencilerinNolariniGetir(List<int[]> kayitliTumOgrenciler){
List<int> ogrenciNolari = new ArrayList<int[]>;
for ( int[] ogrenci : kayitliTumOgrenciler) {
if ( devamsizlikYapanOgrenciMi (ogrenci) ) {
ogrenciNolari .add(oge[0]);
}
}
return ogrenciNolari ;
}


şimdi tüm sorularımıza cevap verebiliyoruz. Ayrıca "oge[1] == 2" karşılaştırmasını yardımcı bir methoda taşıyarak bu kodun 100% anlamlı olmasını sağladık.

Yanlış Bilgiden Kaçınılmalı

Bazen yazmış olduğumuz platform ve teknolojiye ait kodlarımızın içerisinde o an anlamlı olabilecek ama başka bir zamanda bize bile anlamsız gelecek ifadeler kullanabiliriz. Bir Unix platformu için yapacağımız kod çalışmasında scp, ssh gibi; JDBC kodlaması yapıyorsak ResultSet'in kısaltılmış hali olan rs gibi kısaltmalar kullanmak mantıklı olabilir ama yine de anlamsızdırlar.

Bazen verilen ifadelerin sonlarına - özellikle bu Collection yapılarında çok yapılıyor - veri tipi ekleniyor; ogrenciList, nameStr gibi... ogrenciList yerine ogrenciler ifadesinin kullanımı anlamlı olacaktır. ogrenciList olarak ifade edilen bir yapı gerçekte List tipinde olmayabilir. OgrenciList bir sınıf tipi de olabilir. Burada gereksiz eklentiler yanlış bilgilendirmeye neden olmaktadır.

İsimlendirmelerde Anlamlı Farklılıklar Oluşturun

Kullandığımız dilin getirdiği kurallar nedeni ile isimlendirmeleri çeşitlendirmemiz gerekiyor. Aynı alanda (scope) aynı değişken ismini farklı tipler için birkaç kez kullanamıyoruz. Sırf derleyiciyi memnun etmek için bu sefer eklerle veya karakterle ayrımı yapmak zorunda kalıyoruz.

Derleyiciyi memnun etmek için neler yapıyoruz:

Değişken isminin başına veya sonuna "_" karakteri koyuyoruz. Eğer aynı isimden çok kullanmamız gerekirse bakın ne hale gelecek : "araba_______" :D
  • Data, Info gibi kelimeler ekleyerek kelime çeşitliliği sağlanır ama aslında hepsi aynı anlamdır. ogrenci, ogrenciBilgisi, ogrenciVerisi hepsi aynı.
  • "a", "the" kullanamıyla çeşitliliği sağlarız. Bir bakıma bu, parametre ile lokal değişkeni birbirinden ayırır. Anlam olarak yine bir ayrım söz konusu değildir.
  • Yazılımcı getOgrenci(), getOgrenciBilgisi() methodlarından hangisini çağıracak. Çünkü ikisi de aynı.
  • Söylenebilir İsimler Kullanın

    Söylenebilir isimler kullanmak retrospektif toplantılarında R2D2 gibi sesler çıkarmamızı engeller. Mesela arayüz kodlarında çok kullanılan wysiwyg kısaltmasını kullanırsak bunu söylerken nasıl bir duruma düşeceğimizi hayal edebilirsiniz.

    Aranılabilir Kelimeler Kullanın

    Bundan önce bahsettiğimiz kurallar bu kurala zaten uymamıza neden olmaktadır. Kısaltmalar, anlamsız isimlendirmeler bizi aranılabilir bir içerikten uzaklaştıracaktır.

    Arayüz (Interface) ve Gerçekleştirici (Implementer) İsimlendirmeleri

    Arayüz ile gerçekleştirici arasındaki ayrımı nasıl yaparsınız? Sizin için önemli olan arayüz mü yoksa gerçekleştirici mi?

    Kimileri arayüzleri gerçekleştiricilerinden şu şekilde ayırır "IHesapMakinasi" kimileri de gerçekleştiriciyi arayüzden şu şekilde ayırır "HesapMakinasiImp". Peki hangisini seçmeli?

    Birbiri ile network üzerinen konuşan iki sistem yaptığınızı düşünün. Nesneleri serialize edip network üzerinden gönderelim. Gelen byte kodları deserialize yapıldığında elimizde bir nesne olacaktır ama bu nesne hangi tipte olduğunu alıcı JVM bilmiyor. Bunun için bir tanımlayıcı gerekmektedir. Siz arayüz tanımını alıcı tarafa verdiyseniz kolaylıkla bunu gerçek tipine dönüştürebilir. Bu durumda bizim için önemli olan arayüz (interface). Arayüze gerçek bir isim verirsem onun gerçekleştiricilerinin ne olduğu önemsiz kalır. Bu durumda arayüzü "HesapMakinasi" olarak adlandırmamız gerekecek. Gerçekleştiriciyi isterseniz "HesapMakinasiImpl" veya "BasitHesapMakinasi" / "MuhendisHesapMakinasi" / "GelismisHesapMakinasi" olarak adlandırabilirsiniz.

    Editörlerde dosyaların isimlerine göre sıralandığını düşünürsek "HesapMakinasi" ve "HesapMakinasiImpl" hep alt alta olacaktır.


    Fantastik Kelimelerden Kaçının

    Hiç unutmuyorum performans analizi yaptığımız bir uygulamayı yazarken ileriye yönelik tahmin modülü için FortuneTeller ismini koymuştum. "Clean Code" kitabını okuduktan sonra yanlış bir kullanım yaptığımın farkına vardım.

    Her Amaç İçin Tek Kelime

    Projeye başlamadan önce kendi standartlarınızı belirleyin. Bu standartlar içerisinde kullanacağınız genel kelimelerde bulunmalıdır. Örneğin fetch, get, retrieve kelimelerinin hepsi aynı anlama gelmektedir. Bir standartınız yoksa hangi methodun ne için çağrılacağı ve oluşturulacağı bilinemez. get methodunu bir sınıfın özelliklerine erişmek için kullanabilirsiniz bu bir JEE standartı ve birçok editör, 3th party API'lar bu standarta uyulduğunu varsaymaktadır.

    Controller, Manager gibi kelimeler daha çok yapılar arasındaki iletişimi yönetmektedirler. Manager, Controller kelimesi gördüğünüzde orada mutlaka genel birşeyler yapılmaktadır ve mutlaka içerisi yönetilemeyece şekilde şişmiştir. Bu kelimelerin kullanımı bir antipattern'dir.

    Eğer iki değeri birleştirip yeni bir nesne oluşturan bir methodunuz varsa bu method'a "add" ismini koyabilirsiniz. Aynı sistemde bir değeri bir diziye ekleme işlemi için "add" kullanamazsınız. "append" veya "insert" daha anlamlı bir kullanım olacaktır.


    Temiz Kod Yazımı Nasıl Olmalı?

    Şu sıralar Robert C. Martin tarafından yazılmış "Clean Code kitabını okuyorum. Kitaba temiz kodun ne anlama geldiğini ünlü kodculara sorarak başlamış.

    - Bjarne Stroustrup (C++'ı geliştiren ve "The C++ Programming" kitabının yazarı) : Temiz kod birşeyi en iyi şekilde yapmalıdır. Hata yönetimi ve mimarisi etkili, kolay geliştirilebilir ve performansı yüksek olmalıdır.

    - Grady Booch, "Object Oriented Analysis and Design with Applications" kitabının yazarı : Temiz kod basit ve ne yaptığını direkt göstermelidir. İyi yazılmış bir yazı gibi okunabilmelidir.

    - "Big" Dave Thomas, OTI'nin yaratıcısı ve Eclipse stratejisinin babası: Temiz kodu okunabilir ve orijinal kodcusundan başka bir kodcu tarafından geliştirilebilir olmalıdır. Bir şeyi yapmak için birden fazla yol yerine tek bir çözüm yolunu sunmalıdır.

    Söylemlerden de görüldüğü gibi temiz kod; basit, okunabilen ve tek iş yapandır. Kod yazarken şu soruyu sorabilirsiniz kendinize "Yazılımla ilgisi olmayan biri bu kodu okduğunda ne yaptığını anlayabilir mi?". Eğer hayır ise demekki karmaşık ve okunamayan kod yazıyorsunuz demektir.

    28 Temmuz 2011 Perşembe

    Web Uygulaması İçin Otomatik Kabul Testi Yazımı

    Otomatik Kabul Testi nedir ve nasıl olmalı sorularına daha önceki yazılarımda bahsetmiştim ve test kodu yazımında uymamız gereken kurallara değinmiştim. Bu kurallara gerçekleştirim seviyesinde yenilerini ekleyeceğiz.

    Bu yazımdaki anlatımda biraz daha domain seviyesinde olaylara bakacağız. Bu yüzden bazı gerçekleştirimleri UML diyagramları üzerinden anlatacağım.

    Elimizde bir blog uygulaması olsun. Bu blog uygulamasını ziyaret eden normal kullanıcılar ve blog girişlerini yapan admin kullanıcısı olsun.

    [User]-(looksAtListOfBlockEntrySummary)
    [User]-(looksAtBlogEntry)
    (looksAtListOfBlockEntrySummary)>(visitsHomePage)
    (looksAtBlogEntry)>(clicksBlogEntrySummary)
    [Admin]-(login)
    (login)>(visitsHomePage)
    (login)>(fillsLoginForm)
    [Admin]-(entersNewBlog)
    (entersNewBlog)>(clicksNewEntryTab)

    Aşağıdaki diyagramda bir ziyaretçinin yapacağı hareketleri görebilirsiniz.


    1. Bir blog sayfasını açar
    2. Anasayfada tanımlı blog girişlerinin özetleri listelenmiştir
    3. Bu özetlerden birine tıkladığında tam halini görmektedir
    Aşağıdaki diyagramda ise bir yöneticinin yapacağı hareketleri görebilirsiniz:


    1. Blog sitesini açar
    2. Login formunu doldurur
    3. Yeni Giriş tabına tıklar
    4. Blog girişini yapar
    5. Ayrıca ziyaretçinin yaptığı tüm hareketleri yapar
    Yukarıdaki gereksinimlere baktığımızda domain modelleri neler olduğu kendisini belli etmektedir.

    Admin kullanıcısının 5. gereksiniminde bir iş kuralı (business-rule) vardır ve ilerde kod tekrarına neden olabilir. 5. maddede oluşabilecek kötü kokuları şimdiden almamız gerekir.

    Önce Çağır Sonra Gerçekleştir


    Burada bir kural daha oluşmakta. Esnek bir test kodu yazmak için belki de öncelikle neyi istediğinizi yazmanız gerekmekte. Bunu yaptıkça gerçekleştirimde nasıl bir yol izleyeceğinizi kurgulayabilirsiniz.

    Blog uygulamasında ziyaretçi gereksinimlerini ele alalım. Test etmek istediğim gereksinimler:
    1. Anasayfa blog girişlerinin özetlerini listeler
    2. Blog özetine tıklandığında o girişin detayı görüntülenir
    3. Blog Detay Sayfası blog girişinin tamamını görüntüler
    Bu gereksinimlere ait test kodunu aşağıdaki gibi görmek isterim:

    1. Gereksinim için
    User.visitsHomePage();
    assertThat(homePage(), listsBlogEntrySummaries());

    2. Gereksinim için
    User.visitsHomePage().clicksAnySummary().waitFor(five.seconds());
    assertThat(user, visitsBlogDetailPage());

    lastClickedBlogSummary = homepage.lastClickedSummary();
    assertThat(blogDetailPage(), displaysFullBlogEntry().of(lastClickedBlogSummary ));

    3. Gereksinim için

    blogEntry = blogEntry().withId(5);
    User.visitsBlogDetailPage().forA(blogEntry);
    assertThat(blogDetailPage(), displaysFullBlogEntry().of(blogEntry));

    İş gereksinimlerini hiçbir yorum yazma gereksinimi duymadan anlaşılır bir şekilde kodlayabildim.

    User.visitsHomePage().clicksAnySummary().waitFor(five.seconds());

    şeklindeki bir çağrımda visitsHomePage() methodu bir static factory method olup sonraki methodlar User sınıfına ait bir nesneye aittir.

    assertThat(blogDetailPage(), displaysFullBlogEntry().of(blogEntry));

    şeklindeki çağrımda ise static import yapılarak sadece method isimleri görünmektedir. Bu statik methodları içerikle ilişkili olan bir sınıf içerisinde barındırabilirsiniz. assertThat() methodu junit'in bir methodudur. İkinci parametre olarak bir Hamcrest projesine ait Matcher almaktadır. Siz istediğiniz şekilde kendi mathcer'larınızı oluşturabilirsiniz.

    Genel Web Bileşenleri Tanımlayın

    Yukarıdaki gereksinim testlerine ait kodlar sürekli değişime uğrayarak daha esnek hale gelecektir. Bu kodlarda beni rahatsız eden bir iki şey var.

    Ziyaret Edilebilir Web Sayfası Bileşeni Tanımlayın

    Kullanıcı sürekli tekrar eden ve sayfa çeşitlemesine bağlı olarak tekrarlayan visits*() methodunu kullanmakta. Ziyaret edilen sayfa üzerinden daha sonrasında bazı sorgulamalar yapılmakta. Ben burada Page olgusu oluşturmam gerektiğini anlıyorum.

    [Page|url]



    şeklinde yapı oluşturduktan sonra sayfa ziyaretleri şu hale gelecektir:

    User.visits(the.homepage());
    User.visits(the.entryDetailPage());

    the yardımcı sınıfı içerisinde yardımcı factory methodlar tanımlayabiliriz.

    public static homePage () {
    return Page.forA("http://www.myblog.com");
    }

    şeklinde Page nesnelerimizi oluşturabiliriz.

    Yukarıda 3. gereksinim için yazdığımı test çağrımında bir sıkıntımız bulunmakta.

    User.visitsBlogDetailPage().forA(blogEntry);

    Bu çağrım şekli bir blog girişi için detay sayfasının oluşturumunu User sınıfına yüklemektedir. Halbu ki User'in böyle bir sorumluluğu olamaz. Page yapısını oluşturduktan sonra bu çağrım şekli

    User.visits(the.entryDetailPage().forA(blogEntry));

    olacaktır.

    Web Bileşenleri Tanımlayın

    Bilgidiğimiz gibi Web uygulaması bir çok temel bileşenin bir araya gelerek oluşturduğu bileşenlerden oluşmaktadır. Mesela kullanıcı giriş formu, blog özeti, blog özet listesi, blog detayı v.b. Yukarıdaki sayfa ziyaretinde olduğu gibi her bir bileşen için bir method tanımlamak anlamsız olacaktır.

    Önceki gereksinimlerimizde clicksAnySummary() methodu kötü kokular yaymaktadır. click event'i mouseover, mouseout gibi genel bir methoddur ve bu işlemler bir web bileşeni için çalışmaktadır. Bu methodların PageElement isminde bir yapıdan türemiş bileşenler üzerinde çalışması daha mantıklı gibi. Gereksinimlerimize bakarak aşağıdaki web bileşeni diyagramını oluşturabiliriz.

    [PageElement]^[LoginForm]
    [PageElement]^[EntrySummary]
    [PageElement]^[EntrySummaries]
    [PageElement]^[BlogEntryLink]



    Web bileşenlerini tanımladıktan sonra çağrımlarımız bu şekilde olacaktır:

    User.visits(the.homepage()).clicks(the.homepage().anyBlogSummary());

    Web Bileşenlerinin Yüklenmesini Ana Bileşenlerin Sorumluluğuna Verin

    Bir sayfadaki bileşenleri Page nesnesi yüklemeli ve bileşen sorgulamasını Page üzerinden yapın the.homepage().hasAnyEntrySummary() çağrımında olduğu gibi.

    EntrySummary bileşeni de detay sayfasına yönlendirecek link, özet bilgi ve başlık içermektedir. Bir EntrySummary nesnesi oluşturulurken bu alt bileşenleri yüklemeli. Böylece şu çağrımı yapabilirim:

    String entrySummaryText = the.homepage().selectAnyEntrySummary().content();