Daha önceki 2 yazımda TDD hakkında konuştuk ve PHPUnit aracını nasıl kullanacağımıza dair basit bir giriş yapmıştık. Aşağıdaki linklerden ulaşabilirsiniz.
Test Driven Development (TDD) Nedir?
PHP ve TDD: PHPUnit Nasıl Kullanılır?
Giriş
Test yapabilmenin en güzel özelliği, test edilebilir kod oluşturduğunuzda kodunuzun kendiliğinden onarılması ve genişletilmesi kolay bir koda dönüşmesi. Kısacası test yapabilmeniz için kalite bir kod üretmeniz gerekiyor. Kaliteli kod üretmek bir kaç prensip bunları uyguladığınızda kodunuz test edilebilir olacaktır.
Test edilebilir kod üretmek için SOLID dediğimiz prensipleri uyguluyoruz. SOLID’in açılımı;
- Single Responsibility
- Open/Closed Principle
- Liskov substitution principle
- Interface segregation principle
- Dependency inversion principle
Şimdi bu konulara tek tek değineceğiz.
Single Responsibility
Türkçesi tek sorumluluk anlamına geliyor. Yani yazdığınız sınıfların/classların sadece tek bir işten sorumlu olması gerekiyor, ve bu sorumluluğu tamamen kapsaması gerekmektedir. Wikipedia‘dan alıntı yaparak bir örnek paylaşmak istiyorum:
Bir yazılım parçası olarak rapor hazırlayıcı düşünelim. Bu hazırlayıcı raporları derliyor ve daha sonra çıktı alıyor. Bu modül 2 sebepten dolayı değişebilir; Ya raporun içeriği değişebilir, ya da raporun formatı değişebilir. Bu 2 değişiklik çok farklı şeylere hitap etmektedir. Bir tanesi dış görünüş ile alakalı, diğeri ise işin özüyle alakalıdır. Bu yüzden bu iki iş çok farklı 2 sorumluluktur. Bu yüzden bu 2 işlev farklı modüllere veya classlara ayrılması gerekmektedir.
Open/Closed Principle
Tasarım desenlerinin temelini oluşturan prensiptir. Türkçesi tam olarak; “Değişime kapalı, genişletmeye açık” anlamına gelmektedir. Yazılımdan ek görevler istendiğinde yazılım değiştirilmeden sadece üstüne birşeyler eklenerek o görevlerin eklenmesi demektir.
Liskov substitution principle
Kısaca yerine konabilirlik prensibi diyebiliriz. Hatta İngilizce’de de oyuncu değişikliği için substitution kullanılır. Burada da yaptığımız tam olarak budur. Kısaca; “oyuncu değişikliği” :). Bir class’ımız var ve bu class, B
classını kullanmaktadır. Ve bu B
class’ı A
classından türetilmiştir. Aynı zamanda da C
classı da A
classından türetilmiştir. Sonuç olarak bizim classımıza B
yerine C
‘yi gönderidiğimizde de hiç bir sorun çıkarmadan çalışmaya devam etmesi gerekmektedir. Kısaca, yarattığımız modüllerde aynı türden türemiş sınıflar kullanıldığında sınıfımızın bunlardan etkilenmemesi gerekmektedir.
Interface segregation principle
Hiç bir somut yani hayata geçirilmiş bir sınıfta ihtiyacı olmayan methodları hayata geçirmesini zorlanmamasıdır. Mesela Insan
ve Kopek
sınıfları Canli
sınıfından türediklerini varsayalım. Ancak Canli
sınıfı alt sınıflarını konus()
methodunu hayata geçirmesi için zorlamamalıdır çünkü köpekler konuşamaz.
Dependency inversion principle
Yazılım mimarisinde, üst seviye sınıflar, alt seviye sınıfları kullanabilecek şekilde tasarlanırlar. Yani üst seviye sınıflar işlerini yapabilmeleri için alt seviye sınıflara ihtiyaç duyarlar. Yani bu şekilde bir yandan tekrar kullanılabilir alt sınıflar inşa ederek, bir yandan da onarılması kolay yazılımlar hayata geçiriyoruz.
Dependency Injection
Bir üst başlıklarda anlattıklarımızın pratikte uygulanmasıdır diyebiliriz. Şimdi bir PHP kodu ile buradaki prensipleri nasıl uygulayacağımıza bir göz atalım;
Basit bir post yazma uygulaması yaptığımızı varsayalım. Öncelikle bir Post
için interface
yaratıyorum:
/**
* Interface segregation principle:
* Temel olarak post'un ihtiyaç duyduğu şeyler dışında
* hiç birşey yapmaya zorlamıyor
*/
interface PostInterface {
public function yeni($baslik, $icerik, $user_id);
public function duzenle($id, $baslik, $icerik);
public function sil($id);
}
Daha sonra MySQL kullanarak çalışan bir Post_MySQL
classı tanımlıyorum.
class Post_MySQL implements PostInterface {
public function duzenle($id, $baslik, $icerik) {
// UPDATE post SET baslik = ...
}
public function sil($id) {
// DELETE FROM post ...
}
public function yeni($baslik, $icerik, $user_id) {
// INSERT INTO post ...
}
}
Burada bir alt seviye sınıf oluşturduk. Birazdan bir üst seviye sınıf oluşturarak bu alt seviye sınıfı sömürmesini sağlıyacağız. (Dependency inversion principle)
/**
* Üst seviye sınıfımız:
* Alt seviye bir sınıf olan PostInterface'i kullanıyor
*/
class PostController {
public $post;
public function __construct(PostInterface $post) {
$this->post = $post;
}
public function yeni($baslik, $icerik, $user_id) {
$this->post->yeni($baslik, $icerik, $_SESSION['user_id']);
}
public function sil($id) {
$this->post->sil($id);
}
public function duzenle($post_id, $baslik, $icerik) {
$this->post->duzenle($_GET['id'], $baslik, $icerik);
}
}
Şimdi ise bunları kullanalım:
// Dependency Injection
$controller = new PostController(new Post_MySQL());
$controller->...
Bugün patron aradı. MySQL yerine MongoDB kullanacağız bundan sonra dedi.
/**
* Liskov substitution principle:
* tek yaptığımız oyuncu değişikliği
* Post_MongoDB sınıfı'da PostInterface'i
* hayata geçirdiği için hiç birşeyden etkilenmeden hayatımıza devam ediyoruz :)
*/
$controller = new PostController(new Post_MongoDB());
$controller->...
Sonuç
Biraz teori, biraz pratik ile kaliteli ve test edilebilir bir kod nasıl üretilir öğrendik (umarım). Bir sonraki yazımda ise bu yaptıklarımızın test yaparken ne işe yaradıklarını anlatacağım. Hepinize iyi çalışmalar.
PostController sınıfı için ,kod içindeki açıklamanıza göre
Alt seviye bir sınıf olan PostInterface‘i kullanıyor demişsiniz.
Kullanımda ise
$controller = new PostController(new Post_MySQL());
kodlamışsınız. Peki o zaman PostInterface PostController sınıfı içinde ne işe yaraycak? çelişkili değil mi?
PostController
‘ın constructor’ıPostInterface
türünden herhangi bir objeyi kabul ediyor.Post_MySQL
sınıfı daPostInterface
türünden olduğu içinPost_MySQL
‘i kabul ediyor. Şuradan görülebilir:Neden Interface tanımlıyoruz ?
Interface tanımlamamızın sebebi kurallar belirlemek. Örneğin bir aracı, farklı teknolojiler kullanarak, aynı işlevleri yerine getirebilecek sınıflar yaratılabilmesi için kullanıyoruz. Örneğimizde programcı Post işlevlerini yerine getirmek için MySQL kullanıyor. Bunu yaparkende
PostInterface
‘in kurallarına bağlı kalıyor. AncakPostController
‘ın MySQL’den haberi yok çünkü onun göreviPostInterface
‘te tanımlanmış methodları çağırabilmek. Daha sonra kullanılacak teknoloji değişiyor ve MongoDB oluyor. Daha sonra başka bir programcı aynıinterface
‘i kullanarak Post işlevlerini yerine getirmek içinPostInterface
‘in kurallarına bağımlı kalıyor vePost_MongoDB
sınıfını oluşturuyor. Ancak buPostController
‘ın umurunda değil çünkü onun tek bildiği şeyPostInterface
ve bu şekildePostController
sınıfıPost_MongoDB
sınıfını da kabul edebiliyor. Bizde bu şekildePostController
sınıfında hiç bir değişiklik yapmadan teknolojiyi değiştirebilmiş olduk (Liskov substitution principle).Post_MongoDB
sınıfı da şu şekilde yaratılıyor:Biz bu olaya kısaca Polymorphism diyoruz.
Cevabınız için teşekkür ederim. Ben class PostController içindeki bu kısmı kast etmiştim.
public function __construct(PostInterface $post) {
$this->post = $post;
}
Orada
PostInterface
yazması sadecePostInterface
kabul ettiği anlamına gelmez.PostInterface
yazması demek o türden herhangi bir objeyi kabul etmesi demektir. Bu OOP’un temel konularından bir tanesidir. AyrıcaPostInterface
birinterface
olduğu için zaten instantiable değildir. Bir örnek:Selamlar,
Arasıra açıp bakıyorum. Fena halde NYP tekniklerine merak saldım..
Şuan için kafama hiç oturmayan şey “abstract” kullanımı. İnsan bu ne için kullanır ki. Herhalde program yazdıkça bunu nerde kullanacağımı anlayacam..
abstract
methodlar, teorideki adıyla pure virtual functionlar, bir sınıfı abstract yapar yani o sınıftan obje yaratılamaz. Bu yüzden php'de bir sınıfta abstract method varsa sınıftaabstract
olarak işaretlenmelidir. Kısacaabstract
olan sınıfınew
ile yaratamazsın.Bu şekilde obje yaratmak (instantiate) için değil, genişletmek (
extend
etmek) için sınıflar yaratılabilir ve senin yazdığın sınıfı genişletecek kişiye nereleri overwrite etmesi gerektiği konusunda yol gösteriyorsun.Not: Her
abstract
method overwrite edilmek zorundadır.