Python unittest
- Yazılım projeleri büyüdükçe geliştirme ve maintain(devamlılığı sağlamak) etmek zorlaşmaktadır. Bunun yanısıra projede herhangi bir değişiklik (refactoring, yeni modül, özellik(feature) ekleme vb.) yapıldıkça değişikliklerin var olan sistemi etkilemediğinden yani bozmadığından emin olmamız gerekmektedir. Binlerce satır kod yüzlerce fonksiyonun olduğu bir projeyi elle test etmek pek akıllıca değil. Zaman israfının yanında herhangi bir yeri unutmadığımızdan emin olmanın garantisi de yok. Sözü fazla uzatmadan yazılım projelerinde unit test yaparak zaman israfının ve "acaba ?"ların önüne geçebiliyoruz.
- Yazılım projelerinde test konusuna yeni başladığım için ve öğretirken daha fazla öğrendiğime inandığım için bu makaleyi yazmayı istedim.
- Şimdi aşağıdaki örnek ile başlayalım.
dort_islem.py
adında bir modül var elimizde.
xxxxxxxxxx
def topla(x, y):
return x+y
def cikar(x, y):
return x-y
def carp(x, y):
return x*y
def bol(x, y):
if y == 0:
raise ValueError("Bir sayı sıfıra(0) bölünmez!")
return x/y
- Örnek projemiz bu basit modül olsun. Şimdi
test_dort_islem.py
adında bir dosya oluşturup aşağıdaki kodu ekleyelim.
xxxxxxxxxx
#test_dort_islem.py
import unittest
import dort_islem
class TestDortIslem(unittest.TestCase):
def test_toplama(self):
sonuc = dort_islem.topla(44, 46)
self.assertEqual(sonuc, 90)
- Çalıştırmak için
python 3.X
versiyonunu kullanıyoruz. - Eğer
python3 test_dort_islem.py
yazarsanız terminalden herhangi bir şey göremeyeceksiniz. python3 -m unittest test_dort_islem.py
diyerek çalıştıralım. Sonuç aşağıdaki gibi bir çıktı ise tebrikler! ilk testinizi başarıyla yaptınız.
xxxxxxxxxx
adnan@ce:~/Desktop/blogpost/testce$ python3 -m unittest test_dort_islem.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
- Yani bu basit kodu
print(topla(44,46))
diyerek test ederdim, ne gerek var bu kadar şeye demeyin. Peki ne yaptık ki burada? Zaten durum belli, sonuç belli... - Senaryoyu şöyle düşünelim: "Topla metodundan daha büyük ve karmaşık yüzlerce binlerce metod arasından bir metodu refactoring yaptık veya bu metod şu datayı da versin gibi bir değişiklik yaptık.".
Yapılan değişiklik sistemi etkilememiştir değil mi ?
diye endişelenmemek içinpython3 -m unittest benim_guzel_projemin_guzel_modulu.py
diyerek test ediyoruz ve rahatlıyoruz. - Hata verirse ne güzel işte! Yaptığımız değişiklik sonucunda sistemi etkileyen ve önceden doğru düzgün veriyi döndüren metod artık vermiyor, sebebi de budur diyen bir yardımcımız var. Hiç hata vermediyse zaten "on numara değişiklik yapmışsın helal!" derler(belki).
Test metodlarının isimleri test_method_adi gibi
test
kelimesi ile başlamalıdır. Yoksaunittest
modülü testi çalıştırmayacaktır.
Daha fazla Test
- Şimdi
test_dort_islem.py
dosyasını biraz daha geliştirelim.
xxxxxxxxxx
import unittest
import dort_islem
class TestDortIslem(unittest.TestCase):
def test_toplama(self):
self.assertEqual(dort_islem.topla(44, 46), 90)
self.assertEqual(dort_islem.topla(44, -46), -2)
self.assertEqual(dort_islem.topla(-44, -46), -90)
def test_cikarma(self):
self.assertEqual(dort_islem.cikar(44, 23), 21)
self.assertEqual(dort_islem.cikar(44, -23), 67)
self.assertEqual(dort_islem.cikar(-44, 23), -67)
def test_carpma(self):
self.assertEqual(dort_islem.carp(5, 3), 15)
self.assertEqual(dort_islem.carp(44, 0), 0)
self.assertEqual(dort_islem.carp(-4, -3), 12)
def test_bolme(self):
self.assertEqual(dort_islem.bol(44, 2), 22)
self.assertEqual(dort_islem.bol(44, -11), -4)
self.assertEqual(dort_islem.bol(-44, -88), 0.5)
if __name__ == '__main__':
unittest.main()
- Bu dosyayı
python3 test_dort_islem.py
diyerek çalıştırırsanız. Aşağıdaki gibi bir çıktı alacaksınız.
xxxxxxxxxx
adnan@ce:~/Desktop/blogpost/testce$ python3 test_dort_islem.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
- Dört nokta (. . . .), dört adet test metodu olduğunu gösteriyor ve hepsi başarıyla testi geçti.
Biraz da hata mesajı görelim
dort_islem.py
içerisinde çarpma metodunu aşağıdaki gibi değiştirelim.
xxxxxxxxxx
def carp(x, y):
return x**y # dikkat x*y idi. x**y yaptık
- Sonuç
xxxxxxxxxx
adnan@ce:~/Desktop/blogpost/testce$ python3 test_dort_islem.py
.F..
======================================================================
FAIL: test_carpma (__main__.TestDortIslem)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_dort_islem.py", line 17, in test_carpma
self.assertEqual(dort_islem.carp(5, 3), 15)
AssertionError: 125 != 15 # İŞTE BURAYA DİKKAT
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (failures=1)
- İşte böyledir dünya... diye edebiyat yapmaya başlamayalım. Elin oğlu veya siz gelip projede bir metodu değiştirdiniz. Yanarlı dönerli bir metod olacaktı ancak bir de bakıyorsunuz çarşı karışmış. Neyse ki unittest ile önceden test yazmıştınız da böyle sıkıntılar geliştirme(development) sürecinden gerçek projeye(production) geçmeden yakaladınız.
- Hala elimle test ederdim diyorsanız siz bilirsiniz :)
Vay be unit test çok iyiymiş!
- Diyecekken, "kul yapısı illaki eksik gedik olur" sözünü unutmuyoruz. Aşağıdaki durumu da göz önüne alalım. Önce
dort_islem.py
dosyasında aşağıdaki değişikliği yapalım.
xxxxxxxxxx
def bol(x, y):
if y == 0:
raise ValueError("Bir sayı sıfıra(0) bölünmez!")
return x//y # dikkat: x/y idi. x//y yaptık
- Şimdi
test_dort_islem.py
dosyasında aşağıdaki senaryoları test edelim.
def test_bolme(self):
self.assertEqual(dort_islem.bol(44, 2), 22)
self.assertEqual(dort_islem.bol(44, -11), -4)
self.assertEqual(dort_islem.bol(-44, -4), 11)
- Değişiklik bu senaryolar için bir hata oluşturmadı ancak aşağıdaki satır eklenirse
self.assertEqual(dort_islem.bol(5, 2), 2.5) # yeni senaryo
- Sonuç
adnan@ce:~/Desktop/blogpost/testce$ python3 test_dort_islem.py
F...
======================================================================
FAIL: test_bolme (__main__.TestDortIslem)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_dort_islem.py", line 25, in test_bolme
self.assertEqual(dort_islem.bol(5, 2), 2.5) # yeni senaryo
AssertionError: 2 != 2.5 # İŞTE HATA BURADA
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (failures=1)
- Yukarıdaki durumdan çıkaracağımız ders: Olabildiğince kıyı köşede kalmış durumlar, beklenmeyen durumlar için de test senaryosu yazmak gereklidir
- Yani her zaman kullanıcılardan bekleyeceğimiz senaryo ile test etmemek gerekli.
TRY TO HACK YOUR PROJECT!
:D
Sona gelirken
- hep
assertEqual
çalıştık. Yeni bir şey deneyelim. **Bölme metodunu eski halinereturn x/y
döndürelim. Şimdi aşağıdaki senaryoyu deneyelim.
#test_dort_islem.py
def test_bolme(self):
self.assertEqual(dort_islem.bol(44, 2), 22)
self.assertEqual(dort_islem.bol(44, -11), -4)
self.assertEqual(dort_islem.bol(-44, -4), 11)
self.assertEqual(dort_islem.bol(5, 2), 2.5) # yeni senaryo
# Sıfıra bölünme hatası bekliyorum
self.assertRaises(ValueError, dort_islem.bol, 44,0)
- Çalıştıralım ve sonuç:
xxxxxxxxxx
adnan@ce:~/Desktop/blogpost/testce$ python3 test_dort_islem.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
Sıfıra bölünmez
tabii ki hata üretti. Biz de hata bekliyorduk beklediğimizi aldık ve test başarıyla geçildi.
Sonuç
Sağda solda Unit Testing, Yazılımda Test vb. sözlerini çok duyarız. Buna neden ihtiyacımız var diye merak etmeye kalmadan kendi web projemde gerçekten ihtiyaç olmaya başlayınca başlangıç seviyesinde bir giriş yaptım ve ben de testlerimi yazmaya başladım. Profesyonel yazılım projelerinde test olmazsa olmaz hale gelmiştir. Umarım önce bana sonra da sizlere faydası olur bu makalenin... Öğrendikçe paylaşmaya devam edeceğim inşallah.
Faydalı kaynaklar
Müthiş bir test eğitimi için lütfen sabırla izleyiniz:
Yorumlar