0

Python'da Doctest'ler Nasıl Yazılır

Giriş

Dokümantasyon ve testler, her verimli yazılım geliştirme sürecinin temel bileşenleridir. Kodun kapsamlı bir şekilde belgelenmesi ve test edilmesi, bir programın beklenildiği gibi çalışmasını sağlamakla kalmaz, aynı zamanda programcılar arasında işbirliğini ve kullanıcı kabulünü de destekler. Bir programcı, kod yazmadan önce dokümantasyon ve testler yazarak iyi bir hizmet alabilir. Bu gibi bir süreç izlemek, kodlanan işlevin (örneğin) iyi düşünülmüş ve olası uç durumları ele aldığını garanti eder.

Python’un standart kütüphanesi, doctest adında bir test framework modülü ile donatılmıştır. doctest modülü, yorumlar içinde etkileşimli Python oturumları gibi görünen metin parçalarını programatik olarak arar. Daha sonra, bu oturumları çalıştırarak doctest tarafından referans verilen kodun beklendiği gibi çalıştığını doğrular.

Ayrıca, doctest kodumuz için giriş-çıkış örnekleri sağlayarak dokümantasyon oluşturur. doctest yazma yaklaşımınıza bağlı olarak, bu, Python Standart Kütüphane dokümantasyonunda açıklandığı gibi “‘anlatımlı test’ veya ‘çalıştırılabilir dokümantasyon’”a daha yakın olabilir.


Doctest Yapısı

Bir Python doctest, sanki bir yorummuş gibi, """ (üç tırnak işareti) dizisi ile başlar ve biter.

Bazen, doctestler bir işlev örneği ve beklenen çıktıyla yazılır, ancak işlevin ne yapması gerektiği hakkında bir yorum eklemek de tercih edilebilir. Bir yorum eklemek, programcı olarak hedeflerinizi netleştirmenizi sağlar ve gelecekte kodu okuyan kişinin bunu iyi anlamasını sağlar. Unutmayın, kodu okuyan gelecekteki programcı muhtemelen siz olabilirsiniz.

Bilgi: Bu eğitimdeki örnek kodu takip etmek için, yerel sisteminizde bir Python etkileşimli kabuğu açarak python3 komutunu çalıştırın. Daha sonra örnekleri kopyalayarak, yapıştırarak veya >>> isteminden sonra ekleyerek düzenleyebilirsiniz.

İki sayıyı toplayan bir işlev için bir doctestin matematiksel bir örneği aşağıdadır:

"""
İki tam sayı verildiğinde, toplamını döndür.

>>> add(2, 3)
5

Bu örnekte, bir açıklama satırı ve iki tam sayı girdi değeri için add() işlevinin bir örneği bulunmaktadır. Gelecekte, işlevin ikiden fazla tam sayı ekleyebilmesini isterseniz, doctesti işlevin girdilerine uyacak şekilde revize etmeniz gerekecektir.

Şu ana kadar, bu doctest bir insan için oldukça okunabilir. İşlevden gelen ve işlevden çıkan her değişkeni açıklamak için makine tarafından okunabilir parametreler ve bir dönüş açıklaması ekleyerek bu docstring'i daha da yineleyebilirsiniz.

Burada, işleve geçirilen iki argüman ve döndürülen değer için docstring'ler ekleyeceğiz. Docstring, her bir değerin veri türlerini not edecektir - a parametresi, b parametresi ve döndürülen değer - bu durumda hepsi tam sayılardır.

"""
İki tam sayı verildiğinde, toplamını döndür.

:param a: int
:param b: int
:return: int

>>> add(2, 3)
5

Bu doctest şimdi bir işlev içine entegre edilip test edilmeye hazırdır.


Bir Doctest'i İşleve Entegre Etmek

Doctestler bir işlevin def ifadesinden sonra ve işlevin kodundan önce yer alır. Bu, işlevin başlangıç tanımını takip ettiğinden, Python’un konvansiyonlarını izleyerek girintili olacaktır.

Bu kısa işlev, doctestin nasıl entegre edildiğini gösterir.

def add(a, b):
    """
    İki tam sayı verildiğinde, toplamını döndür.

    :param a: int
    :param b: int
    :return: int

    >>> add(2, 3)
    5
    """
    return a + b

Kısa örneğimizde, programımızda sadece bu bir işlev var, bu yüzden doctest modülünü içe aktarmamız ve doctestin çalışması için bir çağrı ifadesine sahip olmamız gerekecek.

İşlevimizin öncesinde ve sonrasında şu satırları ekleyeceğiz:

import doctest
...
doctest.testmod()

Bu noktada, kodu bir program dosyasına kaydetmek yerine Python kabuğunda test edelim. Kullandığınız komut satırı terminalinde (IDE terminali dahil) python3 komutunu kullanarak bir Python 3 kabuğuna erişebilirsiniz (veya sanal bir kabuk kullanıyorsanız python).

$ python3

Bu yolu izlerseniz, ENTER tuşuna bastığınızda şu çıktıyı alacaksınız:

Output
Type "help", "copyright", "credits" or "license" for more information.
>>>

>>> isteminden sonra kod yazmaya başlayabilirsiniz.

Tam örnek kodumuz, doctest ile birlikte add() işlevi, docstring'ler ve doctesti çalıştırmak için bir çağrı içerir. Denemek için Python yorumlayıcınıza yapıştırabilirsiniz:

import doctest

def add(a, b):
    """
    İki tam sayı verildiğinde, toplamını döndür.

    :param a: int
    :param b: int
    :return: int

     >>> add(2, 3)
     5
     """
     return a + b

doctest.testmod()

Kodu çalıştırdığınızda şu çıktıyı alacaksınız:

Output
TestResults(failed=0, attempted=1)

Bu, programımızın beklendiği gibi çalıştığı anlamına gelir!

Yukarıdaki programı return a + b satırını return a * b olarak değiştirerek, işlevi tam sayıları çarpıp sonucunu döndürecek şekilde değiştirdiğimizde, bir hata bildirimi alacaksınız:

Output
**********************************************************************
File "__main__", line 9, in __main__.add
Failed example:
    add(2, 3)
Expected:
     5
Got:
     6
**********************************************************************
1 items had failures:
   1 of    1 in __main__.add
***Test Failed*** 1 failures.
TestResults(failed=1, attempted=1)

Yukarıdaki çıktıda, doctest modülünün ne kadar yararlı olduğunu anlamaya başlayabilirsiniz; çünkü a ve b çarpıldığında ne olduğunu tam olarak açıklar ve örnek durumda 6 sonucunu döndürür.

Birden fazla örnekle denemek isteyebilirsiniz. Her iki değişkenin de 0 değerine sahip olduğu bir örnekle deneyelim ve programı tekrar + operatörü ile toplama işlemine değiştirelim.


import doctest

def add(a, b):
     """
     İki tam sayı verildiğinde, toplamını döndür.

     :param a: int
     :param b: int
     :return: int

      >>> add(2, 3)
      5
      >>> add(0, 0)
      0
      """
      return a + b

doctest.testmod( )

Bu kodu çalıştırdığımızda, Python yorumlayıcısından şu geri bildirimi alacağız:

Output
TestResults(failed=0, attempted=2)

Burada, çıktı doctestin iki testi denediğini, add(2, 3) ve add(0, 0) satırlarında olduğunu ve her ikisinin de geçtiğini belirtiyor.

Yine, programı + operatörü yerine * operatörü ile çarpma işlemi yapacak şekilde değiştirdiğimizde, doctest modülüyle çalışırken uç durumların önemli olduğunu öğrenebiliriz; çünkü ikinci örnek olan add(0, 0), toplama veya çarpma işlemi olsun aynı değeri döndürecektir.

import doctest

def add(a, b):
     """
     İki tam sayı verildiğinde, toplamını döndür.

     :param a: int
     :param b: int
     :return: int

      >>> add(2, 3)
      5
      >>> add(0, 0)
      0
      """
      return a * b

doctest.testmod()

Kodu tekrar çalıştırdığımızda, şu çıktıyı alacağız:

Output
**********************************************************************
File "__main__", line 9, in __main__.add
Failed example:
     add(2, 3)
Expected:
    5
Got:
    6
**********************************************************************
1 items had failures:
   1 of   2 in __main__.add
***Test Failed*** 1 failures.
TestResults(failed=1, attempted=2)

Programı değiştirdiğimizde, örneklerden yalnızca biri başarısız oluyor, ancak daha önce olduğu gibi tam olarak tanımlanıyor. add(2, 3) örneği yerine add(0, 0) örneğiyle başlasaydık, programımızın küçük bileşenleri değiştiğinde başarısızlık fırsatları olduğunu fark etmemiş olabiliriz.


Programlama Dosyalarında Doctest Kullanımı

Şimdiye kadar, örneği Python etkileşimli terminalinde kullandık. Şimdi tek bir kelimedeki ünlü harflerin sayısını sayacak bir program dosyasında bunu kullanalım.

Bir programda, programlama dosyamızın altındaki if __name__ == "__main__": bölümünde doctest modülünü içe aktarabilir ve çağırabiliriz.

Metin editörümüzde — counting_vowels.py — adlı yeni bir dosya oluşturacağız. Komut satırında nano kullanabilirsiniz:

$ nano counting_vowels.py

Fonksiyonumuzu count_vowels (ünlüleri_say) olarak tanımlayarak başlayabilir ve fonksiyona kelime parametresini geçebiliriz.

def count_vowels(word):

Fonksiyonun gövdesini yazmadan önce, fonksiyonun ne yapmasını istediğimizi doctest içinde açıklayalım.

counting_vowels.py

def count_vowels(word):
     """
     Verilen tek bir kelimedeki toplam ünlü harf sayısını döndürün.

Şimdiye kadar gayet iyi, oldukça spesifik olduk. Kelime parametresinin veri tipini ve döndürmek istediğimiz veri tipini belirtelim. İlk durumda bu bir string, ikinci durumda ise bir integer.

counting_vowels.py

def count_vowels(word):
     """
     Verilen tek bir kelimedeki toplam ünlü harf sayısını döndürün.

     :param word: str
     :return: int

Sonra örnekler bulalım. Ünlü harfleri olan tek bir kelime düşünün ve bunu doküman stringine yazın.

Peru'daki Cusco şehrini seçelim. “Cusco”da kaç ünlü harf var? İngilizce'de ünlü harfler genellikle a, e, i, o ve u olarak kabul edilir. Burada u ve o'yu ünlü harfler olarak sayacağız.

Cusco için testi ve döndürmek istediğimiz 2 sayısını programımıza ekleyeceğiz.

counting_vowels.py

def count_vowels(word):
     """
     Verilen tek bir kelimedeki toplam ünlü harf sayısını döndürün.

     :param word: str
     :return: int

      >>> count_vowels('Cusco')
      2

Yine, birden fazla örneğe sahip olmak iyi bir fikirdir. Daha fazla ünlü harf içeren bir başka örnek ekleyelim. Filipinler'deki Manila şehriyle devam edeceğiz.

counting_vowels.py

def count_vowels(word):
     """
     Verilen tek bir kelimedeki toplam ünlü harf sayısını döndürün.

     :param word: str
     :return: int

      >>> count_vowels('Cusco')
      2

      >>> count_vowels('Manila')
      3
      """

Bu doctest'ler harika görünüyor, şimdi programımızı kodlayabiliriz.

Bir değişken başlatmakla başlayacağız — total_vowels (toplam_ünlü) — ünlü harf sayısını tutmak için. Daha sonra kelimenin harfleri arasında dolaşmak için bir döngü oluşturacağız ve ardından her harfin ünlü olup olmadığını kontrol etmek için bir koşullu ifade ekleyeceğiz. Döngü boyunca ünlü harf sayısını artıracağız, ardından kelimedeki toplam ünlü harf sayısını total_vowels değişkenine döndüreceğiz. Programımız şu şekilde olmalıdır, doctest olmadan:

def count_vowels(word):
    total_vowels = 0
    for letter in word:
        if letter in 'aeiou':
            total_vowels += 1
    return total_vowels

Bu konular hakkında daha fazla rehberliğe ihtiyacınız varsa, lütfen How To Code in Python kitabımıza göz atın.

Sonra, programın altına main bölümümüzü ekleyeceğiz ve doctest modülünü içe aktararak çalıştıracağız:

if __name__ == "__main__":
     import doctest
     doctest.testmod()

Bu noktada, programımız şu şekildedir:

counting_vowels.py

def count_vowels(word):
     """
     Verilen tek bir kelimedeki toplam ünlü harf sayısını döndürün.

     :param word: str
     :return: int

      >>> count_vowels('Cusco')
      2

      >>> count_vowels('Manila')
      3
      """
      total_vowels = 0
      for letter in word:
           if letter in 'aeiou':
                total_vowels += 1
       return total_vowels

if __name__ == "__main__":
     import doctest
      doctest.testmod()

Programı python (veya sanal ortamınıza bağlı olarak python3) komutunu kullanarak çalıştırabiliriz:

$ python counting_vowels.py

Programınız yukarıdakiyle aynıysa, tüm testler geçmiş olmalı ve herhangi bir çıktı almazsınız. Bu, testlerin geçtiği anlamına gelir. Bu sessiz özellik, programları başka amaçlar için çalıştırırken kullanışlıdır. Test etmek için özellikle çalıştırıyorsanız, aşağıdaki gibi -v bayrağını kullanmak isteyebilirsiniz:

$python counting_vowels.py -v

Bunu yaptığınızda şu çıktıyı almalısınız:

Çıktı

Trying:
    count_vowels('Cusco')
Expecting:
    2
ok
Trying:
    count_vowels('Manila')
Expecting:
    3
ok
1 items had no tests:
     __main__
1 items passed all tests:
   2 tests in __main__.count_vowels
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

Mükemmel! Test başarıyla geçti. Yine de kodumuz henüz tüm kenar durumlar (edge case) için optimize edilmiş olmayabilir. Kodumuzu güçlendirmek için doctest'leri nasıl kullanacağımızı öğrenelim.


Doctest'leri Kullanarak Kodu İyileştirmek

Bu noktada, çalışan bir programımız var. Belki henüz en iyi program değil, bu yüzden bir kenar durumu (edge case) bulmaya çalışalım. Büyük harfli bir ünlü harf eklersek ne olur?

Doctest'e başka bir örnek ekleyin, bu sefer Türkiye'deki İstanbul şehri için deneyelim. Manila gibi, İstanbul da üç ünlü harfe sahiptir.

İşte yeni örnekle güncellenmiş programımız:

counting_vowels.py

def count_vowels(word):
     """
     Verilen tek bir kelimedeki toplam ünlü harf sayısını döndürün.

     :param word: str
     :return: int

      >>> count_vowels('Cusco')
      2

      >>> count_vowels('Manila')
      3

      >>> count_vowels('Istanbul')
      3
      """
      total_vowels = 0
      for letter in word:
          if letter in 'aeiou':
              total_vowels += 1
       return total_vowels

if __name__ == "__main__":
      import doctest
      doctest.testmod()

Programı tekrar çalıştıralım.

$ python counting_vowels.py

Bir kenar durumu belirledik! Aldığımız çıktı şu:

Çıktı

**********************************************************************
File "counting_vowels.py", line 14, in __main__.count_vowels
Failed example:
    count_vowels('Istanbul')
Expected:
    3
Got:
    2
**********************************************************************
1 items had failures:
   1 of   3 in __main__.count_vowels
***Test Failed*** 1 failures.

Yukarıdaki çıktı, 'Istanbul' testinin başarısız olduğunu gösteriyor. Programa üç ünlü harf saymayı beklediğimizi söyledik, ancak program yalnızca iki tane saydı. Burada ne yanlış gitti?

if letter in 'aeiou': satırında yalnızca küçük harfli ünlü harfleri geçtik. aeiou stringini büyük ve küçük harfli ünlü harfler içerecek şekilde 'AEIOUaeiou' olarak değiştirebiliriz ya da daha zarif bir şey yapıp kelimenin word içinde saklanan değerini lower() ile küçük harfe çevirebiliriz. İkincisini yapalım.

counting_vowels.py

def count_vowels(word):
    """
    Verilen tek bir kelimedeki toplam ünlü harf sayısını döndürün.

    :param word: str
    :return: int

     >>> count_vowels('Cusco')
     2

     >>> count_vowels('Manila')
     3

     >>> count_vowels('Istanbul')
     3
     """
     total_vowels = 0
     for letter in word.lower():
          if letter in 'aeiou':
              total_vowels += 1
       return total_vowels

if __name__ == "__main__":
    import doctest
     doctest.testmod( )

Şimdi, programı çalıştırdığımızda tüm testler geçmelidir. Bunu, verbose (ayrıntılı) bayrağı ile python counting_vowels.py -v komutunu çalıştırarak tekrar onaylayabilirsiniz. Yine de, bu program muhtemelen olabileceği en iyi program değil ve tüm köşe durumlarını dikkate almıyor olabilir. Avustralya'daki Sydney şehri için word parametresine Sydney değerini geçirirsek ne olur? Üç sesli harf mi yoksa bir mi bekleriz? İngilizcede, y bazen sesli harf olarak kabul edilir. Ayrıca, Almanya'daki Würzburg şehri için word parametresine Würzburg değerini geçirirseniz ne olur? ü harfi sayılacak mı? Sayılmalı mı? Diğer İngilizce olmayan kelimeleri nasıl ele alacaksınız? UTF-16 veya UTF-32 gibi farklı karakter kodlamalarını kullanan kelimeleri nasıl ele alacaksınız? Bir yazılım geliştirici olarak, örnek programda hangi karakterlerin sesli harf olarak sayılması gerektiğine karar vermek gibi zor kararlar vermeniz gerekecek. Bazen doğru veya yanlış bir cevap olmayabilir. Çoğu durumda, olasılıkların tüm kapsamını dikkate almayabilirsiniz. Bu nedenle, doctest modülü olası köşe durumlarını düşünmeye başlamak ve ön belgelendirme yapmak için iyi bir araçtır, ancak nihayetinde sağlam programlar oluşturmak için insan kullanıcı testlerine ve çok muhtemel işbirlikçilere ihtiyacınız olacaktır.


Sonuç

Bu öğretici, doctest modülünü yalnızca yazılımı test etme ve belgelendirme yöntemi olarak değil, aynı zamanda programlamayı belgeleyerek, test ederek ve ardından kod yazarak başlamadan önce düşünme yöntemi olarak tanıttı. Test yazmamak yalnızca hatalara değil, aynı zamanda yazılımın başarısız olmasına da yol açabilir. Kod yazmadan önce test yazma alışkanlığı, diğer geliştiricilere ve son kullanıcılara hizmet eden verimli yazılımları destekleyebilir.


Lisa Tagliaferri tarafından yazılan How To Write Doctests in Python Program makalesinin düzenlenmiş çevirisi


Daha Fazla Oku:



Yorumlar

yorum Yap

Makale kategorileri