Birim Testler: Yazılımı Bağımsızlaştırmak-2

Dikiş Noktasına Sahtenin Eklenmesi

1- Bağımlılığı Sınıf Kurucu(Constructor) Fonksiyon aracılığıyla yollamak.

Bunu yapabilmek içint test edeceğimiz sınıfa bir tane kurucu fonksiyon tanımlamalı ve dış etkeni temsil edecek sınıf parametre olarak gönderilmeli!
Aşağıdaki örnekte kurucu fonksiyona temsil edilecek sınıfı parametre olarak yolluyoruz.

public class PinGüncelleyici
{
	public IKartNoUzunlukYöneticisi KartNoUzunlukYönetici;

	// Mevcut testler bozulmasın diye bu parametresiz inşa edici sınıfını da ekledik!
	public PinGüncelleyici()
	{
	}
	public PinGüncelleyici(IKartNoUzunlukYöneticisi yönetici)
	{
		KartNoUzunlukYönetici = yönetici;
	}

	public bool PinCheckGirdiDenetimi(string kartNo, int pinDenemeSayisi)
	{
		int standartKartNoUzunluk = 16;

		if (String.IsNullOrEmpty(kartNo))
		{
			throw new ArgumentException("kartNo değeri boş ya da null olamaz");
		}

		bool sonuc = KartNoUzunlukDestekleniyorMu(standartKartNoUzunluk);
		if (!sonuc)
		{
			throw new ArgumentException(standartKartNoUzunluk + " uzunluktaki kart no değeri desteklenmiyor");
		}

		if (kartNo.Length != standartKartNoUzunluk)
		{
			return false;
		}

		if (pinDenemeSayisi < 0 || pinDenemeSayisi >= 5)
		{
			return false;
		}

		return true;
	}

	//Interface kullanan şekliyle değiştirildi
	private bool KartNoUzunlukDestekleniyorMu(int standartKartNoUzunluk)
	{
		return KartNoUzunlukYönetici.TanımlıMı(standartKartNoUzunluk);
	}

	static void Main(String[] args)
	{
	}
}

Diğer yazdığımız testlerin kodlarını değiştirmeyelim diye parametresiz bir kurucu(constructor ) fonksiyon tanımladık. Bu parametresiz kurucu fonksiyon tanımlanmasaydı parametre kullandığımız kurucu fonksiyon tek olarak kalacaktı ve şimdiye kadar yazdığımız bütün testler derlenmeyecekti, değiştirilmek zorunda kalacaktı.
Görüldüğü gibi kurucu fonksiyonlarda yapılan herhangi bir değişiklik bütün testlerin hata vermesine sebep olabiliyor.
Kurucu fonksiyonlara bağımlılıkları parametre olarak atamamız, bu sınıf için sorunu kolaylıkla çözdü. Yalnız her bağımlılık için bir paremetre eklersek, kurucu fonksiyonlarımıza çok fazla parametre atamak zorunda kalabiliriz.

Aşağıdaki gibi bağımlılık (ILogger,ICalculater,…) listesinin uzadığını düşünürsek durum bir müddet sonra kontrolden çıkacaktır.

public PinGüncelleyici(IKartNoUzunlukYöneticisi yönetici, ILogger logger, ICalculater calc ...)

Kurucu fonksiyona, parametre olarak bağımlılıkları eklemek yerine ayrı fonksiyonlar aracılığıyla atasak ya da sınıf özelliği(property) olarak bu değerleri tutsak ve gerektiği yerde atasak bazı durumlar için daha doğru olmaz mı? Şimdi bu yöntemleri inceleyelim.

Sınıf Özelliği(Property) Olarak Bağımlılık Atama

Yukarıdaki örnekte bağımlılığı, sınıf kurucu fonksiyonu kullanarak eklemiştik. Şimdi bu bağımlılığı sınıfın bir özelliği(property) olarak tutalım ve gerektiği yerde kullanılmak üzere atayalım.

//Sınıf Özelliği(Property) aracılığıyla bağımlılığımızı atadık
public class PinGüncelleyici
{
	//Bağımlılığı Sınıf Özelliği(Property) olarak tutuyoruz
	public IKartNoUzunlukYöneticisi KartNoUzunlukYönetici { get; set; }

	public PinGüncelleyici()
	{
		KartNoUzunlukYönetici = new KartNoUzunlukYöneticisi();
	}

	public bool PinCheckGirdiDenetimi(string kartNo, int pinDenemeSayisi)
	{
		int standartKartNoUzunluk = 16;

		if (String.IsNullOrEmpty(kartNo))
		{
			throw new ArgumentException("kartNo değeri boş ya da null olamaz");
		}

		bool sonuc = KartNoUzunlukDestekleniyorMu(standartKartNoUzunluk);
		if (!sonuc)
		{
			throw new ArgumentException(standartKartNoUzunluk + " uzunluktaki kart no değeri desteklenmiyor");
		}

		if (kartNo.Length != standartKartNoUzunluk)
		{
			return false;
		}

		if (pinDenemeSayisi < 0 || pinDenemeSayisi >= 5)
		{
			return false;
		}

		return true;
	}

	//Interface kullanan şekliyle değiştirildi
	private bool KartNoUzunlukDestekleniyorMu(int standartKartNoUzunluk)
	{

		return KartNoUzunlukYönetici.TanımlıMı(standartKartNoUzunluk);
	}
	static void Main(String[] args)
	{
		PinGüncelleyici güncelle = new PinGüncelleyici();
		string kartNo = "1234567890123456";
		int pinDenemeSayisi = 1;

		güncelle.PinCheckGirdiDenetimi(kartNo, pinDenemeSayisi);
	}
}

Şimdi bu haliyle sınıfı kullanacağız, bu haline göre testimizi yazalım.

[TestCase("1234567890123456", 1)]
public void PinCheckGirdiDenetimi_HerZamanDesteklenenUzunluk_TrueDön(string kartNo, int pinDenemeSayisi)
{
	//Arrange
	PinGüncelleyici pinGüncelleyici = new PinGüncelleyici();
	//Sınıf özelliği olarak atadım yalnız kurucu fonksiyon içinde de atandığı için iki kez atanıyor.
	pinGüncelleyici.KartNoUzunlukYönetici = new HerZamanTrueDönenKartNoUzunlukYöneticisi();

	//Act
	bool sonuc = pinGüncelleyici.PinCheckGirdiDenetimi(kartNo, pinDenemeSayisi);

	//Assert
	Assert.IsTrue(sonuc, "kartNo uzunluğu her zaman desteklenen uzunlukta gönderiliyor.");
}

Görüldüğü gibi bağımlılığı özellik(property) olarak atadık. Bu yaklaşımın bir dezavantajı bağımlılığın kurucu fonksiyonda atanma zorunluluğunun ortadan kalkmasıdır. Kurucu fonksiyona koysaydık, sınıfı kullananlar bağımlılığı mutlaka atamak zorunda kalacaktı, şimdi sadece ihtiyaca göre atıyor. Bu durum, bu sınıfın bağımlılık olmadan da çalışabileceğini ilan ediyor. Şayet bağımlılığı olduğu halde bu şekilde kullanmış olsaydık, bu değer “null” olduğu için atama yapmadan çağrılması durumunda nullexception hatası fırlatılacaktı.

Şimdiye kadar, kurucu fonkiyonu kullanmak ve sınıf özelliği olarak atamak gibi yöntemler gördük. Şimdi daha farklı bir yöntemi Fabrika desenini görelim!.

Fabrika Deseni Kullanarak Bağımlılık Ataması

Fabrika deseni bolca kullanılan bir bağımlılık ekleme yöntemidir. SetYöneticiTürü fonksiyonunu kullanarak yapacağım testten önce istediğim bağımlılık türünün belirlediğim sınıfta(PinGüncelleyici) tanımlanmasını sağladım.
SetYöneticiTürü fonksiyonu  , bağımlılık atamak için kullandığım yerdir olayısıyla dikiş noktası(seam) olarak adlandırılır.


public static class KartNoUzunlukYöneticisiFabrika
{
	public static IKartNoUzunlukYöneticisi KartNoUzunlukYönetici = null;

	public static IKartNoUzunlukYöneticisi Üret()
	{
		if (KartNoUzunlukYönetici != null)
			return KartNoUzunlukYönetici;

		return new SahteKartNoUzunlukYöneticisi();

	}

	public static void SetYöneticiTürü(IKartNoUzunlukYöneticisi tür)
	{
		KartNoUzunlukYönetici = tür;
	}
}

// Fabrika desenini kullanarak test bağımlılık atadık
[TestCase("1234567890123456", 1)]
public void PinCheckGirdiDenetimi_HerZamanDesteklenenUzunluk_TrueDön(string kartNo, int pinDenemeSayisi)
{
	//Arrange
	var KartNoUzunlukYönetici = new HerZamanTrueDönenKartNoUzunlukYöneticisi();//istediğimiz türde yönetici üretiyoruz
	//Bundan sonra sınıf bizim atadığımız yöneticiyi kullanacak
	KartNoUzunlukYöneticisiFabrika.SetYöneticiTürü(KartNoUzunlukYönetici);
	PinGüncelleyici pinGüncelleyici = new PinGüncelleyici();

	//Act
	bool sonuc = pinGüncelleyici.PinCheckGirdiDenetimi(kartNo, pinDenemeSayisi);

	//Assert
	Assert.IsTrue(sonuc, "kartNo uzunluğu her zaman desteklenen uzunlukta gönderiliyor.");
}

Bu yazıda herhangi bir Mock framework kullanmadan sınıflara sahte eklenmesi için kullanılan yöntemleri anlattım.
Bir sonraki yazıda bu işleri yapan Mock frameworkleri anlatacağım.

Son iki makalede yazdığım kodlar ektedir.

YazilimBagimsizlastirma

Birim Testler: Yazılımı Bağımsızlaştırmak-1

Bir önceki makalede, yazılım biriminin yaptığı işi test ettim. İşler her zaman fonksiyonların kendi yaptıklarıyla sınırlı değildir.
Testini yapacağımız yazılım biriminin doğru çalışma durumu başka öğelere bağlı olabilir. Bunlar dosya sistemi, zaman, hafıza birimi gibi dış etkenlerdir. Test edeceğimiz birim çalışırken bunların vereceği yanıtları bilemeyiz çünkü kontrolümüzde değildir.
Testini yapacağımız alanın doğru çalıştığını garantilemek için bu dış etkenleri test dışı tutacak şekilde birim testlermizi yazmalıyız.

Dış etkenlerden bağımsızlaşmak için dış etkenlerin yerini tutacak yazılımsal nesneler kullanmamız gerekiyor.

Dış etkenleri yalancıktan temsil eden bu yazılımsal birimlere genel olarak sahte(fake) denir ve stub(koçan), mock(taklit) gibi iki çeşidi vardır.
Diğer bir kavramsa seam’dir; seam sahtelerin, yazılıma konulduğu yeri gösteren dikiş noktasına verilen addır.

Seam(Dikiş Noktası) : Sahtelerin(stub,mock) yazılımda yerleştirildiği yerdir.

Stub(Koçan): Kontrol edilebilir dış etkendir, temsil ettiği dış etkenin bütün özelliklerini barındırmaz sadece bir kısmını barındırır.
Mısırın varlığını temsil etmesi için sadece mısır koçanının yeterli olması gibi.

Mock(Taklit) : Stub gibidir, tek farkı , bunun kendisine sorgulama yapabilmemizdir.

Birim test yapacaksak bu kavramlara iyice hakim olmalıyız. Kavramları anlamak kolay olsa da yazılıma uygulamak oldukça zor.
Yazılımdaki dış etkenin yerine geçecek bir sahte(fake) üretmeli ve gerçeğinin yerine uygun şekilde yerleştirmeliyiz.

 

Sahte Üretilmesi

Dış etkenlerden bağımsızlığı kazanmak için dış etkenlerin sahtesinin üretilmesi gerektiğini belirtmiştik.

Bu sahte üretilirken iki tür yaklaşım vardır.

1- Sahtesi üretilecek nesne bir arayüzden miras aldırılır ve bu arayüzden miras aldırılarak üretilen sahte gerçeğinin yerine kullanılır.
2- Sahte oluşturularak doğrudan dikiş noktasına eklenir(constructor, property ya da fonksiyon aracılığıyla testi yapılacak sınıfa atanır)

Ortak Arayüz Kullanarak Sahte Üretilmesi

Bir önceki yazımıda kullandığımız TestConsole uygulamasına bir dış etken ekliyeceğiz. Sonra bu dış etkeni sahtesiyle değiştireceğiz.
PinGüncelleyici sınıfımızda kart no değerinin uzunluğunu kontrol ediyorduk, Türkiye’deki bütün kartlar 16 haneli olsa da dünya genelinde kullanılan kartlar 14’ten 19 haneye kadar olabiliyor.
Hangi uzunlukların desteklendiğini bir config dosyasından okumamız gerektiği gibi bir senaryomuz olsun. Bu bizim dış bağımlılığımızdır, kontrolümüzde değildir çünkü dosyada hangi değerlerin tanımlı olduğunu bilemeyiz.
Test edilecek sınıfımız bu dış etkenin cevabını bilemediği için uzunluğu test etmemiz tutarlı bir sonuç vermez. Veriyi kontrol edebileceğimiz şekilde bu dış etkeni sahtesiyle değiştirelim.

Önce sınıfımıza bir dış etken ekleyelim:

public class PinGüncelleyici
{
	public bool PinCheckGirdiDenetimi(string kartNo, int pinDenemeSayisi)
	{
		int standartKartNoUzunluk = 16;

		if (String.IsNullOrEmpty(kartNo))
		{
			throw new ArgumentException("kartNo değeri boş ya da null olamaz");
		}
         //eklenen dış etkeni kullanacak
		bool sonuc = KartNoUzunlukDestekleniyorMu(standartKartNoUzunluk);
		if (!sonuc)
		{
			throw new ArgumentException(standartKartNoUzunluk + " uzunluktaki kart no değeri desteklenmiyor");
		}

		if (kartNo.Length != standartKartNoUzunluk)
		{
			return false;
		}

		if (pinDenemeSayisi < 0 || pinDenemeSayisi >= 5)
		{
			return false;
		}

		return true;
	}
	
	private bool KartNoUzunlukDestekleniyorMu(int standartKartNoUzunluk)
	{
		//Config dosyası okunuyor!
		//Kontrol edemediğimiz bir dış etken var!
		KartNoUzunlukYöneticisi uzunlukYönetici = new KartNoUzunlukYöneticisi();
		return uzunlukYönetici.TanımlıMı(standartKartNoUzunluk);
	}  
}

// Bu sınıf config dosyasını okuma görevi vardır
// verilen uzunluk değeri dosyada tanımlı mı ona bakar!
public class KartNoUzunlukYöneticisi
{
	public bool TanımlıMı(int standartKartNoUzunluk)
	{
		//Her zaman desteklendiğini söylesin
		//konuyu dağıtmaması için buraya dosya okuma kodu yazmıyorum
		return true;
	}
}

Dış bağımlılıktaki işleri temsil etmesi için ek bir sınıf koyduk, dosya okuma işi bu sınıfta yapılıyor.

Dosya Sistemi ile Araya Sınıf Konuldu

Şimdi dış bağımlılığı ortak arayüz kullanarak sahtesiyle değiştirelim. Bunu yapabilmek için yapılan işi soyutlamalı ve yerine kendi sınıfımızı koymalıyız.

KartNoUzunlukYöneticisi isimli sınıfın yaptıklarını soyutlamak için bunu bir arayüzden miras aldıralım.
Arayüzün ismi IKartNoUzunlukYöneticisi olsun(sadece başına “I” koyduk bu arayüz tanımlamak için genel olarak yazılımcıların geleneğidir.)

Dışa Bağımlılığı Kaldırma
Resim-2 -Dışa Bağımlılığı Kaldırma

SahteKartNoUzunlukYöneticisi isimli yeni bir fonksiyon tanımlayarak IKartNoUzunlukYöneticisi’nden miras alsın, testlerimizde dosyadan okuma yapan KartNoUzunlukYöneticisi yerine kontrol edebildiğimiz SahteKartNoUzunlukYöneticisi isimli sınıfı kullanacağız, böylelikle dış etkene bağımlılıktan kurtulup yazılımın istediğimiz gibi davranmasını sağlayabileceğiz!

Miras alacağımız arayüz:

public interface IKartNoUzunlukYöneticisi
{
	bool TanımlıMı(int standartKartNoUzunluk);
}

Sahte sınıfımız:

public class SahteKartNoUzunlukYöneticisi : IKartNoUzunlukYöneticisi
{
	public bool TanımlıMı(int standartKartNoUzunluk)
	{
		//Her zaman desteklendiğini söylesin,dosya okuma kodu yazmayalım!
		return true;
	}
}

Şimdi PinGüncelleyici sınıfında bulunan dış bağımlılığı kullanan fonksiyonumuzu güncelleyelim.

//Interface kullanan şekliyle değiştirildi
private bool KartNoUzunlukDestekleniyorMu(int standartKartNoUzunluk)
{
	//Config dosyası oku!
	//burada kontrol edemediğimiz bir dış etken var!
	IKartNoUzunlukYöneticisi uzunlukYönetici = new KartNoUzunlukYöneticisi();
	return uzunlukYönetici.TanımlıMı(standartKartNoUzunluk);
}

görüldüğü gibi şu aşamada görsel olarak büyük değişiklik olmadı ama fonksiyonumuz artık arayüz kullanıyor, istediğimiz sahte(stub) sınıfı fonksiyona ekleyebilir hale getirdik.

Sahte sınıfımızın ismini daha anlamlı olarak güncellersek sınıf başına “Her zaman True Dönen” koyabiliriz çünkü gerçekten de bu sınıf her zaman true dönen senaryoyu temsil etmektedir.

Bu sınıf, test edeceğimiz KartNoUzunlukDestekleniyorMu(…) fonksiyonunda kart no  uzunluğunun desteklendiği senaryoyu temsil eder.

Böyle bir isimlendirme alışkanlığıyla, kartno uzunluğunun desteklenmediği senaryo için HerzamanFalseDönenKartNoUzunlukYöneticisi gibi bir sınıf da tanımlamamız gerekecektir. Bu şekilde her senaryo için yazılımlarımızda bir sınıf tanımlarsak yüzbinlerce sınıf tanımlamamız gerekebilir. Tabii bu yönetilemez bir durumdur.
Bu sınıf sadece test amaçlı kullanılacak , bu şekilde istediğimiz davranışları gösteren sınıflar üretmek için ilerde yazılım çatıları(software framework) kullanacağız. Böylelikle projelerimizi sadece test için oluşturulmuş gereksiz sınıflardan kurtaracağız.

public class HerzamanTrueDönenKartNoUzunlukYöneticisi : IKartNoUzunlukYöneticisi
{
	public bool TanımlıMı(int standartKartNoUzunluk)
	{
		//Her zaman desteklendiğini söylesin,dosya okuma kodu yazmayalım!
		return true;
	}
}

KartNoUzunlukDestekleniyorMu fonksiyonunu bağımlılıktan kurtaracak sınıfımızı da tanımlamış olduk, peki

IKartNoUzunlukYöneticisi uzunlukYönetici = new KartNoUzunlukYöneticisi();

satırında da görüldüğü gibi bu satıra artık istediğimiz yukarıdaki HerzamanTrueDönenKartNoUzunlukYöneticisi sınıfının bir nesnesini
eklemek gerekiyor, bunun için burada seam denilen dikiş noktasına ekleme yapmalıyız.

Bunu yapmak için bir kaç yol var. Sonraki yazımızda buradan devam edeceğiz.

İlk Birim Testimiz-2

İlk birim testimizi yazmaya devam ediyoruz, geliştirme ortaını hazırladık şimdi ilk birim testimizi yazacağız.

Aşağıdaki sınıfın PinCheckGirdiDenetimi isimli tek bir fonksiyonu var. kartNo(Kredi Kartı No) ve pinDenemeSayisi(şifre deneme sayısı) isimli ,iki adet parametre alıyor.

Fonksiyonda bu değerlerin istenilen şekilde gelip gelmediği test ediliyor. Birim testlermiz bu kontrollerin doğru şekilde çalıştığını test edecek.

public class PinGüncelleyici
{
	public bool PinCheckGirdiDenetimi(string kartNo, int pinDenemeSayisi)
	{
		if (String.IsNullOrEmpty(kartNo))
		{
			throw new ArgumentException("Kart No değeri boş ya da null olamaz");
		}

		if (kartNo.Length != 16)
		{
			return false;
		}

		if (pinDenemeSayisi < 0 || pinDenemeSayisi >= 5)
		{
			return false;
		}

		return true;
	}
	static void Main(String[] args)
	{

	}
}

Birim Testlerin Olacağı Proje

Zorunluluk olmasa da isimlendirme standartı koymalıyız bu testlerin okunabilirliğini artırır.
Projenin ismi TestConsole projesiydi; solution’a TestConsole.UnitTests isminde class library projesi eklemiştik.
Şimdi burada bir tane birim test sınıfı tanımlıyoruz ismi PinGüncelleyiciTests olacak. Birim test sınıfı için bir isimlendirme standartı kullanıyoruz.

[Sınıf İsmi]Tests  dolayısıyla birim test sınıfımızın ismi PinGüncelleyiciTests olacak.
Bu sınıfın test amaçlı yazıldığını göstermek için NUnit’in bir attribute’ünü kullanıyoruz : [TestFixture] ki MSTest Runner bunun birim test sınıfı olduğunu anlasın.

[TestFixture]
public class PinGüncelleyiciTests
{
	[Test]
	public void PinCheckGirdiDenetimi_FazlaUzunluktaKartNo_FalseDön()
	{
		//Arrange
		PinGüncelleyici pinGüncelleyici = new PinGüncelleyici();
		string kartNo = "12345678901234567";// 17 uzunluk
		int pinDenemeSayisi = 1;

		//Act
		bool sonuc = pinGüncelleyici.PinCheckGirdiDenetimi(kartNo, pinDenemeSayisi);

		//Assert
		Assert.IsFalse(sonuc, "kartNo uzun olduğundan dolayı hata dönmesi gerekir.");
	}
}

Yazılacak fonksiyonun başına [Test] diyoruz ki MSTest Runner bunun birim test fonksiyonu olduğunu anlasın.

Fonksiyon ismini gördüğünüz gibi uzun tuttuk, böyle bir zorunluluk yok ama testin ismine baktığımız zaman ne iş yaptığını anlamamız gerekiyor.

3 parça halinde fonksiyon ismini oluşturduk, ayrıştırırsak :
PinCheckGirdiDenetimi: Birim yazılımın ismi, nereyi test ediyorsak o yazılmalı. Sınıf ismi, fonksiyon ismi ya da kod parçasına verdiğimiz bir isim olabilir.
FazlaUzunluktaKartNo : Senaryo adı, birim testte hangi senaryoyu test ediyoruz, içerikte fonksiyona yanlış uzunlukta kart no yollayacağım o yüzden bu ismi verdim.
FalseDön : Birim test sonucunda hangi cevabı bekliyorum, burada beklenen davranış yazılır, mesela exceptioın fırlat, fonksiyon çağır vs…

Birim fonksiyonlarını isimlendirirken şu desene uymakta büyük fayda var.
[TestEdilecekAlanİsmi]_[Senaryo]_[BeklenenDavranış]

böylelikle fonksiyon isminden hangi yazılım birimini, hangi senaryoyu test ettiğimizi ve beklediğimiz sonucu görebiliyoruz. Böylelikle testleri okumak kolaylaştı. Testi anlamak için gerekli bütün bilgileri fonksiyon ismine koymuş oluyoruz.

[TestFixture] ve [Test] attribute’lerini, Assert’i kullanarak NUnit’i kullanmaya adım atmış olduk.

Şimdilik testimizi yazdık, testin VS’de görülebilmesi için derledikten sonra Test Explorer’i açmanız gerekiyor.

Visual Studio’da Test -> Windows -> Test Explorer’a tıkladığınız zaman ilk test fonksiyonunu görmemiz gerekiyor. MSTest Runner , NUnit Test Adapter’i kullanarak bu testlere ulaştı, bu kodların test olduğunu anladı,ekranda isimleriyle beraber gösterdi.

NUnit , test fonksiyonlarımızın public tanımlanmasını ve void olmasını zorunlu kılar.

Kodun içinde farketmişsinizdir , 3 tane bölüm var, buna 3A deseni de denir:

Arrange : Test için gerekli nesneleri üret, ortamı hazırla.
Act : Test için gerekli fonksiyonu çağır.
Assert : Oluşan değerin istediğimiz değerlerle uyumluluğunu sorgula.

Birim testi bu şekilde bölümlere ayırmak zorunluluk değildir ama testi okumayı kolaylaştıran güzel bir davranış kalıbıdır.

NUnit’in belki de en önemli yeri static bir sınıf olan Assert sınıfı, kodumuzla NUnit arasındaki köprüdür. NUnit’in gücünü bu sınıf vasıtasıyla kullanırız.

Assert sınıfı bir çok fonksiyon barındırır, mesela:

Assert.True (Boolean ifade) ;

bu fonksiyonla istediğimiz değeri karşılaştırırız.

Assert.AreEqual(2, 1+1, "asla hata vermez");

Assert sınıfını kullanmak ,hatırlamak kolaydır. Test hata verdiğinde , 3. parametre olarak verdiğimiz hata mesajını Test Explorer ekranında görebiliriz.
Dolayısıyla buraya hatayı açıklayıcı mesajlar yazmalıyız. “test hata verdi” ,”a bekliyorduk b geldi” gibi genel, anlaşılmaz hata mesajları konulmamalı.

Testin Çalıştırılması

[TestFixture] ve [Test] attribute’lerini kullandıktan sonra testin test explorer’de görülmesi gerekir demiştik.

Test Explorer Ekranı

İlk birim testimizi başarıyla yazdık, görüldüğü gibi karmaşık senaryolar olmadığı sürece birim test yazılması son derece kolaydır.

Birim test yazmayı engelleyen nedenlerden bir tanesi de testlerle beraber birim testlerin de değişmesi gerektiğidir.

Bu normal bir durum olsa da koddaki basit değişikliklerle testin değişmek zorunda olması, hata vermesi yazılımcıyı yorar, sinirlendirir ve olay birim testleri projeden kaldırmaya kadar gidebilir.

Birim testler doğru yapılmazsa projedeki kod bakım maliyeti iki katına çıkabilir.

Bunu engellemek için küçük değişikliklerin birim testlerde en az etkiyi yapmasını istiyoruz. NUnit bunun için bize bazı imkanlar sunar.

Mesela sınıfı inşa eden Constructor fonksiyonuna bir parametre eklediğimizi ve bir çok testin değişmek zorunda olduğunu düşünelim, 3-5 fonksiyon için birim testler değiştirilebilir ama ya o sınıf için 100 test yazıldıysa, bütün birim testleri elden mi geçireceğiz?

Böyle bir senaryoyla karşı karşıya kalmamak için NUnit birim testlere dışarıdan parametre yollama imkanı vermiştir.

Birim fonksiyonumuz aşağıdaki hale döner.

[TestCase("12345678901234567", 1)]
[TestCase("123456789012345", 1)]
public void PinCheckGirdiDenetimi_YanlışUzunluktaKartNo_FalseDön(string kartNo, int pinDenemeSayisi)
{
//Arrange
PinGüncelleyici pinİşler = new PinGüncelleyici();

//Act
bool sonuc = pinİşler.PinCheckGirdiDenetimi(kartNo, pinDenemeSayisi);

//Assert
Assert.IsFalse(sonuc, "kartNo uzun olduğundan dolayı hata dönmesi gerekir.");
}

Birim test fonksiyonundaki değişiklikleri adım adım anlatırsak:

– [Test] kaldırdık ve [TestCase] koyduk.
– [TestCase] içine istediğimiz kadar(2 tane) parametre koyduk, böylelikle farklı parametreler için ayrı ayrı fonksiyon yazmak zorunda kalmadık.
Ayrı fonksiyonlar yazmasak da Test Explorer bunları ayrı testler olduğu için ayrı test olarak gösterecektir.
– Parametreleri test fonksiyonumuzun arayüzüne koyduk. [TestCase] deki parametreler ile fonksiyon arayüzündeki parametreler eşlenik olmalıdır.
-Senaryo ismini farklılaştırdık ,daha genel bir isim yaptık FazlaUzunluktaKartNo -> YanlışUzunluktaKartNo , artık kart no’nun sadece uzun olmasını değil kısa olmasını
da test ediyoruz.

Görüldüğü gibi birden fazla test durumunu [TestCase]’ler aracılığıyla test edebiliyoruz, bu kodu azaltacak bakım maliyetini düşürecektir, dahası en önemli katkı olarak fonksiyonların arayüzlerinde oluşacak küçük bir değişiklik, parametre ekleme gibi durum için birim testlerin hepsini değiştirmek zorunda kalmayacağız, tek bir birim test fonksiyonun arayüzünü ve [TestCase]’lerdeki parametreleri değiştireceğiz.

Farkında vardıysanız fonksiyonun bütün özelliklerini test etmedik.Şimdi ek bir fonksiyon daha yazalım ve kart no boş geldiğinde fonksiyonumuz gerçekten exception fırlatıyor mu diye bakalım?.

[TestCase("")]
[TestCase(null)]
public void PinCheckGirdiDenetimi_BoşyadaNullKartNo_ExceptionFırlat(string kartNo)
{
	//Arrange
	PinGüncelleyici pinİşler = new PinGüncelleyici();
	int pinDenemeSayisi = 1;

	//Act
	var ex = Assert.Catch<ArgumentException>(() => pinİşler.PinCheckGirdiDenetimi(kartNo, pinDenemeSayisi));

	//Assert
	StringAssert.Contains("kartNo değeri boş ya da null olamaz", ex.Message);
}

Yine
[TestEdilecekAlanİsmi]_[Senaryo]_[BeklenenDavranış] desenine uyacak şekilde fonksiyonumuzu isimlendirdik.
Burada yukarıdaki örnekten fazla olarak Assert.Catch lambda expression ve StringAssert.Contains kullanıldı.

Assert.Catch fonksiyonunun, lambda içinden fırlatılan exception’u yakalama özelliği var. Exception’u yakaladık ve içindeki mesajda istediğimiz string var mı diye kontrol ettik.

Görüldüğü gibi Assert sayesinde bir fonksiyon içindeki bir çok beklenen durumu kontrol edebiliyoruz. İlerde Assert sınıfının diğer özelliklerini de kullanmaya devam edeceğiz.

Şimdilik birim testlere giriş yaptık, ilerde çok daha gerçek test senaryolarıyla karşılaşmaya, bunlarla başa çıkmak için birim test dünyasının
kavramlarını öğrenmeye devam edeceğiz.

Proje kodunu ekte bulabilirsiniz.
TestConsole

İlk Birim Testimiz -1

Bir önceki yazımızda herhangi bir unit test framework’ü (birim test çatısı) kullanmadan ilk birim testimizi yazmıştık. Bu yazımızda birim test çatısı(BTÇ) kullanarak ilk birim testimizi yazacağız. BTÇ olarak NUnit isimli yazılımcılar tarafından çok kullanılan ve sevilen çatıyı seçtim.

Birim Test Çatısı Nedir

Birim testler için gerekli fonksiyonların kümelendiği yazılım kütüphanesine BTÇ denir.

İki ana ayaktan oluşur , birincisi aşağıdaki gibi sonuçları test etme fonksiyonlarını barındıran kısımdır:


Assert.isTrue(istenilenSonuç, "hata mesajı: gelen değer 123 olmalıydı") ;
Assert.IsFalse(sonuc, "hata mesajı: Pin 0'dan küçük olamaz" );

İkinci ayak ise bu testlerin koşturulması ve sonuçlarının ekranda gösterilmesi için gerekli olan kısımdır.

NUnit bu iş için ayrı bir arayüz sunuyor, koşturduğumuz testleri oradan takip edebilir, kaç tanesi hata vermiş kaç tanesi düzgün çalışmış görebiliriz.

Biz Visual Studio(VS) ile daha uyumlu bir yaklaşım seçeceğiz. Testleri koşturmak için  NUnit’in arayüzünü değil VS’deki varsayılan MSTest Runner’ı kullanacağız. MSTest Runner ile NUnit arasında bağlantı kurmak için NUnit Test Adapter’i kullanacağız, böylelikle VS’nin ekranlarından test sonuçlarını görebileceğiz. İlerde NUnit’i VS’ye proje bazlı yüklemeyi göstereceğim.

Birim Test Çatısının Yazılımdaki Yeri

Aşağıdaki resim birim testler yazılırken BTÇ’nın nerelere yayıldığını ve etki ettiğini gösteriyor. Görüldüğü gibi BTÇ kütüphanelerinin iki ana görevi var:
BTÇ birim test yazılırken ve koşturulurken kullanılıyor.
Birim Test Çatısı

Resim-1 Birim Test Çatısı

 

Birim testler yapılırken BTÇ’nin yardımlarını şöyle özetleyebiliriz.

1-Sonuçlarını sorgulamamıza yarayan fonksiyonlar sunar, Assertion(İleri Sürmek) fonksiyonları diye adlandırılır.
2-Kodumuzdaki birim testleri tespit eder.
3-Test hakkında sonuçları gösterir.
– Kaç tanesi başarılı.
– Kaç tanesi hata aldı.
– Testler ne kadar sürdü.
– Hangi test hata aldı, hata mesajı gösterilmesi.

Piyasada yaklaşık olarak 150 civarında birim test çatısı var, bunlardan sadece 3 tanesi aktif olarak .NET için geliştiriliyor. xUnit.net, NUnit ve MSTest Framwworkleri, biz kolaylık açısından NUnit’i seçtik.
BTÇ kullanmak da birim testlerinizin kalitesini yükseltmez. BTÇ sayesinde birim testleriniz okunabilir, güvenilir ve bakım maliyeti düşük hale gelmez.

Birim testler de sonuçta yazılımdır ve yazılımın kalitesi ayrı bir gayretin sonucudur. Yazılım kalitesi hakkında çok önemli bir kaynak Robert C. Martin’in “Clean Code” isimli kitabıdır, yazılımcılar genelde bu kitabı okuduktan sonra yazılımcılık hayatlarını ikiye ayrırılar, öncesi ve sonrası diye. Bu kitabı mutlaka okumanızı tavsiye ediyorum.

Örnek Proje

Makaledeki örnekleri TestConsole isimli projemizde uygulayacağız. Projede PinGüncelleyici isimli sınıfımız olacak, kredi kartlarındaki pin doğrulama adımlarını yapacak.

İlk birim testlerimizin kolay olması için bu projede PinCheckGirdiDenetimi ismine sahip basit tek bir doğrulama fonksiyonu var.

Birim testlerimizi bu fonksiyon işini doğru yapıyor mu diye kontrol etmek için yazacağız.
Birim testlerimizi her zaman kodumuzdan ayrı bir projeye koymalıyız. TestConsole.UnitTests isimli yeni bir class library projesi oluşturup solution’a ekleyelim.

NUnit’i Projeye Ekleme

NUnit’in, testlerimizi yazmak için gerekli kütüphane fonksiyonlarını barındıran kısmı var. Bir de NUnit Test Adapter isimli NUnit’i Visual Studio ile bütünleşik hale getiren kısmı var.

Bu ikisini en kolay şekilde Visual Studio’daki Nuget Paket Yöneticisi aracılığı ile kurabiliriz.
Aşağıdaki gibi Nuget’e ulaşabiliriz

NuGet Paket Manager Açma

online arama kısmında Nunit yazarak arama yaptırıyoruz.
NUnit Kurulmuş Hal

NUnit ve NUnit Test Adapter’i sadece “TestConsole.UnitTests” projesine
kuruyoruz.

TestConsole.UnitTests projesinde referans kısmına sağ tıklayarak referans ekle diyoruz. TestConsole.UnitTests projesine, TestConsole projesini referans olarak ekliyoruz. Şimdi birim test yazmaya hazırız!

Bu yazıda projeyi ve gerkeli yazılımları hazırladık. Bir sonraki yazımızda BTÇ ile ilk testimizi yazacağız!

Test Güdümlü Programlama Nedir

Test Driven Development, Türkçe’siyle Test güdümlü programlamanın(TGP) tam bir tanımı yoktur.

Herşeyiyle mükemmel birim testleri yazabildiğimizi düşünün , karşımıza bir sorun daha çıkacak, birim testlerini ne zaman yazmalıyız?

Kodu yazmadan evvel mi yoksa yazdıktan sonra mı? TGP’deki tanım karmaşası birim testlerin ne zaman yazılması gerektiğiyle alakalıdır.

Bazı uzmanlar önce birim testinin sonra yazılımın; bazısı da ilk önce yazılımın sonra birim testinin üretilmesi gerektiğini savunur. Bazı uzmanlar ise belli miktarda birim testinin olmasının TGP için yeterli olduğunu iddaa eder.

İlk önce yazılım sonra birim testler üretildiğinde yazılım geliştirme aşamaları:

(1) Yazılım Biriminin Üretilmesi -> (2) Birim Testlerin Yazılması -> (3) Testleri çalıştır -> (4) Test geçmezse yazılımdaki hataları düzelt

Buna alternatif olarak bazı uzmanlar, ilk önce testin yazılmasını sonra bu isteği karşılayan kodun yazılması gerektiğini savunur.

(1) Birim Testlerin Yazılması -> (2) Testi Geçecek Yazılım Biriminin Üretilmesi -> (3) Testleri çalıştır -> (4) Testlerin karşılamadığı özellikleri yazılım birimine ekle ; (3) Testleri çalıştır -> (4) Testlerin karşılamadığı özellikleri yazılım birimine ekle.. (3. ve 4. adımlar testler geçinceye kadar tekrarlanır)

İlk önce testler yazıldığında, tam anlamıyla testlerle şekillenen bir yazılımdan bahsedilebilir.

Adımlar:
1- Olmasını istediğiniz bir özelliğin birim testini yazın.
2- Birim Testi geçen kodu yazın
3- Kodu olabildiğince okunabilir hale getirin.(Refactoring yapın)

Refactoring yazılımcılar tarafından çokca duyulan bir kavramdır. Kodun fonksiyonelliğini değiştirmeden yani yaptığı işi bozmadan kodun yapısını değiştirmektir.
Yazılımdaki bir değişkenin ,fonksiyonu ismini değiştirmek, fonksiyonları parçalayıp küçültmek gibi işlemler refactoring demektir.

TGP düzgün yapıldığında koddaki hataları azaltır, bakım maliyetini düşürür, kod kalitesini yükseltir yalnız TGP yanlış yapıldığında projenin uzamasına sebep olur.

TGP yapılması iyi birim test yazıldığı ya da yazılım mimarisinin düzgün olduğu anlamına gelmez. TGP, kodun okunabilirliğini artırmaz , o daha başka bir gayretin (solid prensipleri, katmanlı mimari, tasarım desenleri vs..) sonucudur.

TGP sadece kodun daha az hatalı olmasını sağlar. TGP, kodlara ve onların çözdüğü problemlere hakimiyetimizi artırır böylelikle daha doğru çalışan yazılımlar üretmiş oluruz.

Buradaki yazılarımızda birim testlerin nasıl yapılması gerektiğinden bahsedeceğiz. Testleri, önce mi sonra mı yapmak size kalmış.

Birim Test Nedir

 

Birim test(Unit Test) kavramı 1970’lerde Kent Beck tarafından ortaya atıldı. Kent Beck, yazılım dünyasının efsanelerinden, getirdiği bu kavramla yazılım dünyasını derinden etkilemiş,  kaliteli yazılım üretimini tepeden tırnağa değiştirmiştir.

Birim Test, yazılım biriminin (unit of work) kodunu çağırıp ürettiği tek bir sonucu değerlendiren yazılımdır.

Birim test tanımındaki “tek bir sonuç ifadesi” önemlidir. Her durumda sadece tek bir sonuç çıkarılmalı ve olması gerektiği düşünülen çıktıyla bu değer karşılaştırılmalıdır.

Yazılım Birimi Nedir

Çağrıldığı zaman belli bir iş yapan ve dikkate değer sonuç üreten bir yazılım parçasıdır. Yazılım birimi , tek bir fonksiyonun yaptığı işlemler olabilir.  Birden fazla sınıfın fonksiyonlarının tetiklenerek birbirlerini çağırması ve sonuç üretmesi gibi karmaşık bir iş de olabilir.

Yazılım biriminin bir fonksiyon olduğunu düşünelim.

Üretilen sonuç :
-Void (yok) olabilir.
-Yazılımın iç durumunu değiştirebilir yani sınıfın bir değeri değişebilir.
-Dış bir yazılımın durumunu değiştirebilir, mesela dinleyen sunucuya TCP mesajı atabilir.

Birim test, tek sonuç üreten iş birimini çağırır ve bu tek sonucu kontrol eder çünkü yapmak istediğimiz şeyin bu tek sonuç olduğunu kabul eder.

Birim testler yaparken , yazılım birimlerini yani testi yapılan alanı büyük tutmak gerekir. Yazılım birimini çağırırken arada yapılmış bir işin sonuç için çok önemi yoktur.

Yazılım biriminin içindeki if sorgusunun doğru çalışması o kadar önemli değildir, önemli olan sonucun doğru üretilmesidir. Yazılım birimi olarak if sorgusunu kabul etsek ve bunu test etsek yazılımın doğru çalışması test edilmiş olmaz, yalnızca if sorgusu doğru çalışmış mı
onu kontrol etmiş oluruz. Bu yüzden yazılım birimleri büyük tutulmalıdır.

Tanımı Güncelleyelim

Birim Test :  Yazılım biriminin kodunu çağırıp tek bir sonucu değerlendiren yazılımdır. Üretilen sonuç , istenilen sonuçla karşılaştırılır.  İstenilen sonuç elde edilmişse birim test doğru , edilememişse birim test hatalı kabul edilir.

Ama Ben Yazılımımı Test Etmeden Yayınlamam

Aslında bakarsanız hepimiz yazılımlarımızı test ediyoruz ama bunlara birim test diyemeyiz.
Test etmek deyince Türkiye’deki bir çok yazılımcının aklına, o yazılımın işini yapar durumda olması geliyor.

Ülkemizde ünlü bir web sitesi yazılımcılar arasında anket yapmıştı, sorulardan bir tanesi de “yazılımınızı test ediyor musunuz” du ?
Yazılımını test! edenlerin oranının %80-90 çıktığını hatırlıyorum. Maalesef yazılımcılarımızın büyük çoğunluğunun testten anladığı veritabanından çektiği veriyi ekranda gösterebiliyor mu diye bakmaktan ibarettir.

Yani yazılımcılarımızın büyük bir oranı test nedir bilmiyor, kodunu debug modda çalıştırıp parametreleri elle girmek ve sonucu gözlemlemek yazılım testi değildir.

Bütünleşme Testleri(Integration Tests)

Birim testlerini anlamak için bilmemiz gereken bir konu da bütünleşme(Integration) testleridir.
Bütünleşme ve birim testleri benzerdir, birbirine karıştırılabilir.

Yaptığımız test, donanımla, veritabanıyla ya da dosya sistemiyle yani dış etkenlerden
biriyle etkileşime giriyorsa testin sonucu bu dış etkenlere bağlı olur. Biz bu durumda yazılım birimini değil bu dış etkenleri de test etmiş oluruz.

Veritabanı bağlantısı birim testimize dahilse o zaman veritabanının durumu da test ediliyor demektir. Veritabanı kapalı olduğu durumda birim test hatalı sonuç  veriyorsa ve çalışır durumda birim test doğru sonuç üretiyorsa burada yazılım birimini değil veritabanını da test ediyoruz demektir.
Birim testler dış etkenlerden tamamen bağımsız olmalıdır.

Bütünleşme testleri faydalı testlerdir yalnız birim testleri ile karıştırmamak gerekir. Bütünleşme testleri dış etkenleri de teste kattığı için daha yavaştır.

Yazılım biriminin dış etkenlerini (zaman, veritabanı vs.) dahil ederek kodun çıktılarını sorguladığımız testler bütünleşme testleridir.

Birim test, yazılım birimini dış etkenlerden temizler ve sadece davranışın test edilmesini sağlar, bütünleşme testleri ise bu testlere dış bağımlılıkları dahil eder.

Tanımı Tekrar Güncellersek:

Birim Test: Test edilecek yazılım biriminin kodlarını çağıran, yazılım biriminin oluşturacağı varsayılan tek bir sonuçla kendisine verilen değeri karşılaştıran kod parçasıdır. Yazılım biriminin kodları değişmediği sürece hep aynı sonucu vermelidir(dış etkenlerden bağımsız).

İlk Birim Testimiz

Birim testler, ilerde inceleyeceğimiz unit test framework  denilen yazılımların yardımıyla yazılmalıdır. Bu makalede birim test mantığını kolayca kavramak için ilk birim testi herhangi bir unit test framework  kullanmadan kendimiz yazacağız.

Test edeceğimiz yazılım birimi basit bir örnek olsun, sadece string bir değer alsın ve bu değeri integer olarak döndürsün, string değer boşşa 0 dönsün.


public class IntegerAritmetic 
{
	public int Dönüştürücü(string rakamYazisi)
	{
		int rakam;
		if (rakamYazisi.Length == 0)
		{
			return 0;
		}
		else if (Int32.TryParse(rakamYazisi, out rakam))
		{
			return rakam ;
		}
		else
		{
		throw new ArgumentException("String değeri rakama dönüştürülemiyor!"
                                                  + rakamYazisi);
		}
	}
}

Şimdi bu sınıfı test edecek sınıfı yazalım. Test edeceğimiz sınıfın ismini kullanıp sonuna “Test” kelimesini de koyarsak bu sınıfın hangi sınıfı test ettiğini daha kolay anlarız.


public class IntegerAritmeticTest 
{
	//Test isimleri anlamlı olmalı, neyi test ediyorsak 
       //fonksiyon isminden anlamalıyız
	public bool BoşStringYollandığındaSıfırDönüyorMu()
	{

		IntegerAritmetic intAritmetic = new IntegerAritmetic();
		int sonuc = intAritmetic.Dönüştürücü("");
		if (sonuc == 0)
		{
			return true;
		}
		return false;
	}
	
	public bool RakamStringYollandığındaSıfırDönüyorMu()
	{

		IntegerAritmetic intAritmetic = new IntegerAritmetic();
		int sonuc = intAritmetic.Dönüştürücü("12");
		if (sonuc != 12)
		{
			Console.WriteLine("Hata oluştu, 12 değeri bekleniyordu.
                        Gelen değer: {0}", sonuc);
			return false;
		}
		return true;
	}
}


Şimdi de Main fonksiyonunun olduğu ve test edici sınıfını üretip , fonksiyonlarını çağırdığımız sınıfı yazalım.

public class BirimTestiÇalıştırıcı
{
	
	static void Main(String[] args)
	{
		try
		{
			IntegerAritmeticTest testler = new IntegerAritmeticTest();
			bool testSonucu = testler.BoşStringYollandığındaSıfırDönüyorMu();
			if (!testSonucu)
			{
				Console.WriteLine("Boş String Yollama tesi hata verdi");
			}


			testSonucu = testler.RakamStringYollandığındaSıfırDönüyorMu();
			if (!testSonucu)
			{
				Console.WriteLine("Rakam String Yollama tesi hata verdi");
			}
		}
		catch (Exception ex)
		{
			Console.WriteLine(ex.Message + ex.Data);
		}
	}     
}

Unit testing framework kullanmadan ilk birim testimizi yazdık.
Kodlardan da anlaşılabileceği gibi ufak fonksiyonellikleri bile test etmek için bir çok kod yazıyoruz. Bir sonraki yazımızda unit testing framework kullanacağız,
işimiz kolaylaşacak.

Kötü Yazılımın Belirtileri-2

Bir önceki yazımda kötü yazılımın belirtilerini başlıkla halinde sıralamıştım. Bu yazı bir önceki yazının devamıdır.

7- Yapay Eşleşme

Genel olarak sınıflarınızda kullanılacak bir değişken tek bir sınıfa konulup oradan kullanılmamalı. Benzer şekilde sadece bir sınıf tarafından
kullanılacak değişken de genel sınıflara konulmamalı. Bu şekilde alakası olmayan birimler uzak durması gereken birimlere erişebilir.

8-Seçici Parametreler

Fonksiyonlara içeride mantıksal dallanmaya neden olacak parametreler(true-false) yollanmamalıdır.

public void MaasHesapla(bool isciMi)
{
	if(isciMi)
	{
	//İşçi maaşı hesapla
	}
	else
	{
	// Memur maaşı hesapla
	}
}

Bunun yerine her işlem için ayrı fonksiyon yazılmalı.

İşçiMaaşıHesapla(){}

MemurMaaşıHesapla(){}

Böylelikle fonksiyonlarımızın sorumluluğunu azaltmış oluruz. Herhangi birinin değişmesi durumunda diğer fonksiyon etkilenmez.

9 – Amacın Gizlenmesi

Kodu yazarken amacımızın olabildiğince açık şekilde ifade eilmesi gerekir bunun da başı doğru isimlendirmeden geçer.
İsimlerdirmelerde hungairan notasyondan uzak durulmalı. Ayrıca ne iş yaptığı belli olmayan rakamlar da olmamalı, kodda başıboş rakamların bulunmamasında büyük fayda vardır.

Hungarian notasyonu ve anlamsız rakamlar bulunduran bir fonksiyon, o rakam ne işe yarıyor acaba?

public int m_Calc()
{
	return iThsWkd * iThsRte + (int) Math.round(0.5 * iThsRte );
}

9 – Sorumluluğun Yanlış Yere Konulması

Veritabanı stringini tutan değişkenin veritabanından veri okumak için yazılan sınıfa değil de dosya okuma sınıfına konulması sorumluluk paylaşımının yanlış yapıldığının göstergesidir.

Bu şekilde değişkenleri yerleştirirken kodu okuyanın beklediği yere koymaya dikkat etmeliyiz, kodu okuyanı şaşırtmamalıyız.

10 – Çok Fazla Static Kullanılması

Bazen sınıfın değişkenini üretmeden sınıfın fonksiyonlarını kullanabilmek için sınıfı static olarak tanımlıyoruz. Bu şekilde sınıfı kolay kulllanıma açabiliriz ama bu sınıf artık arayüzden miras alma, çok şekillilik(polymorphism ) gibi özelliklerden mahrumdur.

Bu yüzden static sınıfları sınırlı sayıda tutmalıyız.

11 – Fonksiyon İsimleri Ne Yaptığını Anlatmalı

Örnek bir kod:


Date newDate = date.add(5);

Burada gün, ay ya da yıl eklediğini göremiyoruz. Bunu anlamak için fonksiyona gidip bakmak gerekiyor, bu da zaman kaybı demek.
Dolayısıyla fonksiyonlara anlamlı isimler verilmelidir, fonksiyonun ne iş yaptığını isminden anlayabilmeliyiz.

12- Rakamlar Yerine İsimler Kullanılmalı

 int faizİşlenmişMaas = maas + maas * 0.2 ;

Böyle bir kodda 0.2 rakamının ne yaptığını biliyor muyuz? Kodu yazan bilebilir ama başkası okuduğunda bu kodu hemen anlayamayacaktır.
Yazılımda en eski kurallardan bir tanesi kodda olabildiğince az rakam kullanmaktır. Rakamlar yerine o rakamların temsil ettiği isimleri yazmalıyız.
Yukarıdaki kodun faiz oranına göre maaşa ekleme yapıtğını düşünürsek 0.2 rakamı yerine FAIZ_ORANI yazmalıyız.


int faizİşlenmişMaas = maas + maas * FAIZ_ORANI ;

görüldüğü gibi şimdi neyi hesaplamaya çalıştığımız daha belli oldu. Hem FAIZ_ORANI isimli değişkeni birden fazla yerde kullandığımızı düşünün, bu değer değiştiğinde koddaki bütün rakam değerlerini(örnekte 0.2) değiştirmek yerine sadece FAIZ_ORANI değişkenini güncellememiz yetecek.

13 – İf-Else Sorguları Fonksiyonlaştırılmalı

Kullandığımız bir dosyayı silmek isteyelim, yalnız bu işi yapmadan evvel dosyayla işimizin bittiğini gösteren bir fonksiyon çağırmamız gerektiğini düşünelim.

Ayrıca dosyayı silmek için de İzinVerildi isimli bir flag değeri olsun.
Bu durumda şöyle bir if sorgusu yazmamız gerekecekti.

if (dosya.isBitti() && dosya.İzinVerildi)

Bunun yerine if-else içindeki mantıksal sorguyu ayrı bir fonksiyon olarak çıkaralım ve sonucu sorgulayalım.


if (SilinsinMi(dosya))

Görüldüğü gibi fonksiyonlu kod daha okunabilir oldu.

14 – If-Else Sorgularında Olumlu Durum Sorgulanmalı

if (SilinsinMi(dosya))

yerine

if (!SilinmesinMi(dosya))

yazmak anlaşılmayı zorlaştıracaktır, o yüzden if() içine her zaman olumlu sorgu yazılmalıdır.

15 – Sınır Değerleri Sarmalanmalı


if (sonSayi + 1 > gelenDeger)
{
    DegeriYolla(adet, sonSayi + 1);
    //...
}

sonSayi + 1 değeri iki ayrı yerde kullanılmış, bunun yerine bu değeri başka bir değer ile sarmalamalıyız.

sinirDeger = sonSayi + 1 ;
if (sinirDeger > gelenDeger)
{
 DegeriYolla(adet, sinirDeger);
 //...
}

Kötü Yazılımın Belirtileri-1

İyi yazılımın özelliklerini bir önceki yazımda ifade etmiştim, projede testlerin olması yazılımın iyi olduğunun belirtilerinden biriydi.

İyi yazılımın belirtileri olduğu gibi kötü yazılımın da belirtileri vardır. Bunları başlıklar halinde sıralayacağım.

Yazılım Yorumları(Comments)

1- Yorumlarda Uygun Olmayan Bilgilerin Olması

Kod değişiklik geçmişi yorum olarak sayfanın başında duruyorsa ve devamlı güncelleniyorsa bu iyiye işaret değildir çünkü  bu bilgi kaynak kod denetim sistemleri denilen TFS,Git gibi sistemlerde tutulmalıdır, isteyen kod değişiklik bilgilerine TFS’e bakarak ulaşmalı,
bu bilgi kodu kirletmemeli.

Aynı şekilde yazar bilgisi, en son değişiklik tarihi vs. gibi bilgiler yorumlarda olmamalı.Yorum sadece teknik konularda veya yazılım tasarımı gibi özel konularda -çok gerektiği zaman-olmalı.

2- Tarihi Geçmiş Yorum

Hiçbir yorum tarihi geçmiş, eski gereksiz yorum kadar silinmeyi hak etmiyor. Bu tür güncellenmemiş yorumlar yazılımcıyı yanlışa yönlendirir çünkü kodun söylediği ile yorumun söylediği farklıdır.

Yorum ya güncellenmeli ya da tamamen silinmeli. En doğrusu yorum yapmamak çünkü zamanla bütün yorumlar eskiyip koddan uzaklaşıyor.

3- Gereksiz Yorum

Herşeyiyle gereksiz bir yorum, sadece okunması gereken yazıyı uzatıyor.

i++ // i'yi artırıyoruz

Başka bir gereksiz bilgi:


/**
* @param satilikEsya
* @return
*/
public void Sat(SatilikEsya satilikEsya)

Fonksiyon parametleri zaten görülüyor, neden aynı bilgiler verildi ki, gereksiz bir yorum. Bu tür gereksiz bilgilerden kurtulmak gerekiyor.

4- Yanlış , Anlaşılması Zor Yorum

Ne dediği belli olmayan, kapalı ve anlaşılması zor , imla hataları olan yorumlar yapmak kabul edilemez. Yorumlar açık ifadelerle, imla kurallarına dikkat edilerek yazılmış olmalı.

5- Kapatılmış Kodlar

Bir kod artık işe yaramıyorsa o kodun silinmesi gerekir, ihtiyaç duyulduğunda zaten kaynak kod denetim sistemlerinden geri alınabilir.

Bu kodların silinememesi kodun yeterince anlaşılamadığı ve hakim olunamadığının göstergesidir, bir korkunun sonucudur.

Aşağıdaki gibi kodların artık kodu kirletmesi engellenmeli .


//public void Sat(SatilikEsya satilikEsya)
//{
//....
//}

Fonksiyonlar

1- Fonksiyona Fazla Sayıda Argüman Yollamak

Fonksiyonlara 3’ten fazla argüman yollamak o fonksiyonda birden fazla iş yapıldığının göstergesidir. Bu da Solid prensiplerinden tek sorumluluk ilkesine (SRP) aykırıdır.

2- Fonksiyon Çıktı Parametreleri(out parametreler)

Out parametreler , fonksiyondan veri almak için kullanılan parametrelerdir, bu tür parametrelerden kaçmak gerekiyor.

Bir fonksiyona parametre yollanıyorsa bu dışarıdan bir değerin içeriye gönderilmesi için olmalıdır. Fonksiyon tek bir iş yapmalı ve o değeri fonksiyon dönüş değeri olarak geri almalı, ayrı bir parametre bu iş için kullanılmamalı.

3- İşaretçi Parametreler

Fonksiyonlara true-false göndermek için parametre yolluyorsanız bu fonksiyonda birden fazla iş yaptığınızın göstergesidir. True ise şunu yap false ise şunu yap gibi..

Bu tür ayırıcı parametrelerden sakınılmalı, her ayrı iş için farklı fonksiyon yazılmalı.

4-Ölmüş Kod

Kullanılmayan kod ölmüştür, bu kodlar silinmeli; gerektiğinde kaynak kod denetim sistemlerinden kodu geri alabiliriz.

Genel

1- Beklenen İşin Yapılmaması

Sınıf, fonksiyon veya değişken isimleri o birimin yaptığı işle uyumlu olmalıdır.
Fonksiyonunuzun ismi VeritabanındanOku ise bu fonksiyon veritabanından okuma yapmalıdır, bu isme sahip olup dosyadan okuma yapıyorsa beklenen işi yapmadığı için kodu yazanla okuyan arasındaki güveni sarsar,kosun anlaşılmasını engeller.

Aynı şekilde kelimeSayaç diye bir değişken kelimeler için sayaç görevini görmüyorsa , isimlendirme ve beklenen iş arasında uyumsuzluk var demektir.

2-Uyarıları Dikkate Almamak

Bir kodda çok fazla uyarı varsa ve bu uyarılar dikkate alınmamışsa kod için hassas davranılmadığının göstergesidir.

Kodun derlenmesi yeterli değildir, yanlış parametre uyarısı, gereksiz değişkenlerin varlığı gibi uyarılar dikkate alınmalıdır. Bu uyarıları dikkate alarak düzenlenmiş kod üstüne düşünülmüş koddur.

3-Kopya Kod

Yazılımda yazılımcının kendini tekrar etmemesi gerektiği vurgulanır ve bu  DRY , “Don’t Repeat Yourself”  prensibiyle isimlendirilmiştir.

Yazılımda birbirine benzeyen kopya kodların olmaması lazım. Şayet birbirine benzeyen kodlar varsa bu işleri yapan kodların bir bütün olarak yeterince anlaşılamadığı, çözümün ortak bir arayüzde soyutlanıp ortak bir çözüm bulunamadığı anlamına gelir.

Birbirinin benzeri kodlar varsa , birisi kafa yorup kodların ortak yönlerini bulup az sayıda kod üretmemiş , kopyala-yapıştır yapmış demektir.

Uzayan if-else sorguları, switch-case durumları kopya kod varlığının göstergelerinden birisidir. Bunlar yerine soyutlama, çok şekillik(polymorphism) gibi çözümler kullanılmalıdır.

Zaten nesne yönelimli programlama da kopya kodu önlemek için geliştirilmiş yazılım anlayışılarından biridir.

Bir sorunu çözecek kodlar tek bir sınıfta toplanır ve o sorun için artık benzer kodlar yazmak yerine bu sınıf kullanılır.

4- Çok Fazla Bilgi Verilmesi

Arayüzün ya da sınıfın bilgilerini çok fazla dışarı açmamak gerekir. Dışarıdan bir kullanıcının sınıfın içeriğini çok fazla bilmesi,sınıfın içeriğini değiştirebilmesi tutarsızlıklara sebep olur.

Arabanın yönünü sadece direksiyonla değil de diresiyonun yanında bir kaç alet ile de değiştirebildiğimizi düşünün, nasıl bir tutarsızlığa sebep olurdu?

Yazılımda da öyledir, arayüzü kısıtlı tutmak ve her bilgiyi dışarı açmamak gerekiyor. Dışarı açılan ve dışarıdan kullanılan her değişken her fonksiyon dış dünya ile bir eşleşme demektir.

Dışarıda ya da içeride yapılan herhangi bir değişiklik durumunda hataya sebep olma olasılığı artar.

5-Dikey Ayrıştırma

Birbiriyle ilişkili birimleri dikey olarak bir arada tutmalıyız. Sınıfımızda bir fonksiyon tanımladığımızı düşünün , sadece bu fonksiyonda kullanılacak özel bir değişkeni bu fonksiyondan onlarca satır uzakta tanımlamamak gerekir.

Değişken nerede kullanılacaksa oraya yakın tanımlanmalı ve kullanılmalıdır. Bu şekilde mantıksal birimler bir arada tutulmuş olur, kodun okunması kolaylaşır.

6-Karışıklık

Yazılımda kullanılmayan değişken  veya fonksiyon olması  kod için uğraşılmadığının göstergesidir. İyi bir yazılımcı kodunu temizlemeli, fazlalıklardan kurtulmalı.

Sade Tasarım

Kent Beck, ismini duyanlar olmuştur, bilgisayar dünyasının efsane yazılımcılarından birisi. Öğrenmekte ve uygulamakta bile güçlük çektiğimiz bir çok yazılım kavramının
yaratıcılarından.

Tasarım desenleri(design patterns) , birim testleri(unit testing) ,çevik yazılım(agile) hepsinde öncü görev almış. Öyle bir adam ki birim test için ilk test framework(SUnit) yazmış, agile manifestoyu imzalayan listede adı var yani yaşayan efsane.
Kent Beck, iyi tasarım yapmak isteyen yazılımcılar için önem sırasına göre 4 tane kural vermiş:

1- Yazılımın koşacak testleri olmalı
2- Yazılımda birbirine benzer , kopya kodlar olmamalı
3- Yazılımcının amacını kodlar açık şekilde ifade etmeli
4- Sınıf ve metod sayısını en aza indirilmeli(fazlalık olmamalı)

Sadece bu 4 kurala uyarak iyi bir yazılım tasarlayabiliriz. Kural sayısı az ama uygulaması zor.

1- Yazılımın Koşacak Testleri Olmalı

Test etmek deyince Türkiye’deki bir çok yazılımcının aklına o yazılımın işini yapar durumda olması geliyor. Ülkemizde ünlü bir web sitesi yazılımcılar arasında anket yapmıştı, sorulardan bir tanesi de “yazılımınızı test ediyor musunuz”du?

Yazılımını test! edenlerin oranının %80-90 çıktığını hatırlıyorum. Maalesef yazılımcılarımızın büyük çoğunluğunun testten anladığı veritabanından çektiği veriyi ekranda gösterebiliyor mu diye bakmaktan ibarettir.

Yani yazılımcılarımızın büyük bir oranı yazılım testi nedir bilmiyor.

7 yıllık yazılım hayatımda yüzlerce yazılımcı gördüm ama bir tane birim testi yazan-yazabilecek kalitede bir yazılımcıya rastlamadım. İyi bir tasarım için mutlaka yazılımın birim, fonksiyonel , entegrasyon vs. testlerinin olması gerekiyor.
Ülkemizde yazılım alanında Instagram, Facebook gibi uygulamaların çıkmaması yazılım mühendislerinin kalitesinin düşük olmasından kaynaklanıyor. Ürettiğimiz yazılımın kalitesi de düşük oluyor çünkü yazılım testi yapılmıyor.

Ülkemizde Kent Beck gibi bir akademisyen çıkıp öğrencilerine  test konusunun önemini anlattı mı?  Kent Beck olmayınca Zukenberg de olmuyor işte. Öyle her ile üniversite açıyorum diye aynı binanın Meslek Lisesi Tabelası indirip Üniversite tabelası yazmakla akedemik hayat gelişmiyor. Her şey akedemik kaliteyle başlıyor.

Türkiye’de çok parlak fikirler üreten kafalar var, mesela eskiden sosyomat.com vardı, çok yaratıcı bir siteydi ama yazılımcılıktaki kalitesizlikleri onları bitirdi.

Çok beğenilen site teknik sebeplerden dolayı uzun süre üye kabul edemediği için popüleritesini zamanla yitirdi. Halbuki ölçeklenebilir yazılımlar üreten teknik bir ekibi olsaydı şimdi dünyada çok farklı yerlerde olacaklarını düşünüyorum.

Yazılım kalitesinin düşmesinin başlıca sebebi teste önem verilmemesi, testlerin değeri hem geliştiriciler hem de yöneticiler tarafından pek bilinmiyor.

Yazılım istenilen şekilde çalışmalıdır, bunu sınamanın tek yolu yazılımı test etmekten geçiyor. Veritabanından ürünleri alan bir sınıfımız olsun, bu sınıf verileri çekiyor ve ekranda gösteriyor acaba tam olarak getirmesi gereken ürünleri mi getiriyor?

Sınıfın bin tane ürün getirdiğini düşünün, bin tanesine ayrı ayrı bakarak evet bunlar doğru ürünler mi diyoruz yoksa evet ürünler getirilmiş bu yeterli mi diyoruz?

Göz yanlış görebilir, insan yanılır, doğru verilerin getirildiğini otomatik-yazılımsal testlerle sınamalıyız. Bu iş için kullanabileceğimiz 20’den fazla test çeşidi var. Birim testleri, fonksiyonel, entegrasyon  testleri vs. Bunlar kullanılarak yazılımın testinin yapılması gerekiyor.

Test edilebilir kod yazmak , yazılımın tasarım kalitesini yükseltir çünkü test edilebilir kod yazmak için SOLID prensiplerine uymamız gerekir. Test edilecek sınfların , fonksiyonların boyutu küçük olmak zorundadır mesela fonksiyon tek bir iş yapmalıdır(Tesk Sorumluluk Prensibi, SRP)
ki tek bir özelliği test edilebilsin.

Sınıflarımız eklemeye açık değişime kapalı(Open Closed Princible) olmalıdır, bunun için arayüzler yoğun bir şekilde kullanılmalıdır.

Kodumuz Dependency injection prensibine uymalıdır çünkü sınıflar arasın eşleşme(coupling) az olmalıdır.
Görüldüğü gibi test yapılabilir bir kod sadece işlevsel olarak değil tasarım olarak da belli bir standarta sahip oluyor.

2- Yazılımda Birbirine Benzer Kodlar Olmamalı

Yazılımlarda neden birbirine benzeyen çok fazla kod var çünkü mevcut kodları değiştirmekten korkuyoruz, benzer kodları kopyalayıp üstünde değişiklikler yapıyoruz.

Kodu değiştirirsek haklı olarak sistemin bozulabileceğinden korkuyoruz. Bu yersiz bir endişe değildir. Bu endişeyi önlemek için yazılıma test yazmalıyız.

Kodu her düzenledikten sonra testlerle sınamaya tabi tutarsak birbirine benzeyen kodlardan kurtulmuş oluruz. Bu şekilde kodumuzu düzenli hale getirebiliriz.

Bir şairin devamlı olarak şiiri üstünde çalıştığını, fazla ya da uymayan kelimeleri sildiğini düşünün, bir müddet sonra şiiri mükemmelleşmez mi?

Yahya Kemal Beyatlı , Türk şiirinde, şiirleri üstünde en çok düşünen şairlerden biridir, 20 yıl sonra dahi içine sinmeyen kelimeleri bazı şiirlerinden silmiştir.

Ürettiğimiz yazılımı düzenleyebilme mevcut yazılımın kalitesini yükseltir, fazlalıklardan kurtuluruz, yazılım testleri buna imkan verir.

3- Yazılımcının Amacını Kodlar Açık Şekilde İfade Etmeli

Yazdığımız kodları bir müddet sonra bazen kendimiz dahi anlamakta güçlük çekiyoruz. Kodu yazarken problemi iyi şekilde anladığımız için yazdığımız kod anlaşılabilir gözüküyor.

Yalnız bir müddet sonra problemi unutup kodun başına gelince ne yaptığımızı anlamakta güçlük çekiyoruz. Bunu önlemek için kodun okunabilir olması gerekir. Sınıf, fonksiyon , değişken isimleri anlamlı olmalıdır.

Sınıfın ismine ilk baktığımızda sınıfın ne için yazıldığını anlamamız gerekiyor. Kod yazıldıktan sonra bir müddet kodun üstünde isimlendirme için vakit harcamak kodun okunabilirliğini artırır.
Yazdığımız kodu ilerde okuyacak kişi yüksek ihtimal yine biz olacağız.

4- Sınıf ve Metod Sayısı En Aza İndirilmeli(Fazlalık Olmamalı)

Sınıfları, fonksiyonları geliştirirken tek sorumluluk prensibine göre yazdık. Doğru bir iş yaptık yalnız bu sınıf sayısını artıracaktır, bruada bir denge olmalıdır.

Sınıf , arayüz sayısı sonsuz şekilde artmamalı.

Sadece fazlalıklar atılmalı, benzer doklar olmamalı.

Görüldüğü gibi bu 4 kurala uyarak sade ve işlevsel bir tasarıma ulaşmış oluyoruz.

Open-Closed Principle

Eklemeye Açık Değişime Kapalı Olma Prensibi

Her yazılımcının korkulu rüyası çalışan yazılımın yapılan güncelleme sonrası çalışamaz duruma gelmesidir. Daha büyük bir korku da  yazılımın çalışır durumda olması ama yapılan güncellemenin ilerde farkedilecek telafi edilmesi zor hatalara , bozukluklara sebep olmasıdır.

Bankacılık sektöründe bu durumla sıklıkla karşılaşılır ve genellikle faturalar milyon dolarlarla ifade edilir.  Dakikalar içinde milyonlarca dolarlık işlemlere onay veren bir yazılımın bir kaç dakika devre dışı kalması bu işlemlerin yapılamaması anlamına gelir, bu bir çeşit “dükkanı kapama” durumudur.

Müşterinin her zaman kullandığı kredi kartının pos cihazında cevap vermemesi, müşterinin başka kartına yönelmesi banka için kabul edilebilemez.

Yazılımlar doğası gereği milyonlarca bileşen içerebilen, yönetilmesi zor sistemlerdir.  İnsanın çok hata yapan bir varlık olduğunu da eklersek yazılımların neden bu kadar hata içerdiği anlaşılıyor.
Geliştirilen yazılımlar değişime açık olmalıdır çünkü yazılımlar her canlı gibi hayatta kalmak istiyorsa müşterilerin isteklerini karşılamak yani değişmek zorundadır.

Yazılımı nasıl değişime açık hale getirebilir?

Yazılımın dışarıdan bakıldığında en temel parçasını sınıflar oluşturur, şayet sınıflarımızı doğru bir şekilde değişime açık hale  getirebilirsek yazılımı da değişime açık hale getirmiş oluruz.
Sınıflarımızın sorumluluklarını sınırlayarak yazılımı değişime daha açık hale getirebiliriz.

Bir sınıfımızın 10 tane sorumluluk sahibi olduğunu düşünün, bir sorumluluk için yapılan herhangi bir güncelleme sonrası sınıfın diğer  sorumlulukları da yüksek ihtimalle etkilenecektir. Bu etkilenmeyi en aza indirmek için sınıfımızın sorumluluk sahası tek noktaya  indirilmelidir, böylelikle değişiklik sonrası etkilenen alan en aza indirilmiş olur.

Eski yazılımı hiç değiştirmeden müşterilerden gelen isteklere cevap vermek mümkün müdür? İdeale en yakın  çözüm bu gözüküyor.

Herhangi bir yazılımda güncellenen alanı ne kadar sınırlı tutarsak sistemin etkilenmesini, olası hata çıkma durumlarını da  azaltmış oluruz. Güncellemeyi sınırlı tutmanın ideal noktası varolan yazılımın kodlarını değiştirmeden yeni özellik ekleyebilmektir..

Eklemeye Açık Değişime Kapalılık Prensibi’nin amacı varolan yazılımı değiştirmeden yeni kodlar ekleyerek sistemin istelilen özelliklere sahip olmasıdır.

Birden Fazla Sorumluluğa Sahip Örnek Sınıfımız


public class Dosya
{
	string dosyaİsmi;
	public Dosya()
	{

	}

	public bool DosyaOku( )
	{
		return true;
	}

	public bool DosyaAdetKadarOku( int harf)
	{
		return true;
	}

	public bool DosyaTasi( )
	{

		return true;
	}

	public bool DosyaSil( )
	{
		return true;
	}
}

Sınıfımız birbirine benzeyen işler yapıyor, sınıf içi bağımlılığı yüksek bu da sorumluluk alanının iyi tanımlandığını  gösteriyor, sınıfın çok farklı sorumlulukları yok.

Fonksiyon isimlerine bakarsak DosyaOku ve DosyaAdetKadarOku isimli iki fonksiyon var.  DosyaAdetKadarOku’nun görevi verilen dosya isminden istenilen harf kadar okuma yapması. DosyaOku fonksiyonuna yakın bir iş yapıyor, bu aslında ikisinin diğer fonksiyonlardan farklı bir sorumluluk(okuma) yüklendiğinin göstergesidir.

İlerde dosya okumayla ilgili yine yan iş geldiğinde bütün  sınıfa dokunmak zorunda kalacağız. Bu durumu farklı işler yapan fonksiyonları farklı bir sınıf içine alarak çözebiliriz. Birbirine benzer iş yapan sınıfları da IDosya isimli bir arayüzden miras aldırarak gruplayabilir ve işleri fazla dağıtmamış oluruz.

Yeni Arayüz ve Sınıflarımız


public interface IDosya
{
	void SetFile(string fileName);
}
public class Okuyucu : IDosya
{
	public bool DosyaOku( )
	{
		return true;
	}

	public bool DosyaAdetKadarOku( int harf)
	{
		return true;
	}
	public void SetFile(string fileName)
	{
	}
}
public class Değiştirici : IDosya
{
	public bool DosyaTasi( )
	{
		return true;
	}

	public bool DosyaSil( )
	{
		return true;
	}

	public void SetFile(string fileName)
	{
	}
}

Birbirine benzer fonksiyonları gruplayarak yeni sınıflar ürettik. Mesela Okuyucu isimli sınıfımız sadece okumayla ilgili işleri yapıyor.

Dosya ile ilgili farklı bir istek geldiğinde mesela FTP’ye yükleme gibi daha farklı bir iş türü geldiğinde IDosya  arayüzünden miras alarak FTPYükleyici isimli yeni bir sınıf tanımlayabiliriz, bu şekilde varolan koda dokunmadan ekleme yaparak müşteriden gelen istekleri karşılamış olacağız.

Sınıf sayısını artırarak, sorumluluk alanlarını sınırlandırarak yazılımımızı değişime açık hale getirdiğimiz gibi arayüzleri kullanarak da yazılımı eklemeye müsait hale getirdik. Yazılımımız varolan kodları güncellemeden ekleme yaparak müşteri isteklerini karşılar hale geldi.