İçeriğe geç

Django Testing Tools Model Bakery

Giriş.

Test yazmak yazılım geliştirme sürecinin hayati bir adımı. Test edilebilir her şeyi test etmek geliştirilen uygulamanın bekleneni yaptığını, yeni eklenen bir özelliğin veya refactor edilen kısımların uygulama döngünüzü kırmadığını kanıtlayan, olası buglarda bunu fixleme sürenizi fazlasıyla aşağıya çeken, üzerinde durulması gereken bir iş.

Bu ufak girişin ardından, bugün, Django uygulamalarınızda test yazarken bir çok kere karşılaşılan random data üretme ve kullanma üzerine konuşacağız.

Aşağıdaki gibi  modellerimiz olduğunu varsayalım, ve siz bir takım geliştirmelerin kontrolünü yapmak için nesnelere ihtiyaç duyuyorsunuz.

class Driver(models.Model):

    name = models.CharField(max_length=100)
    team_name = models.CharField(max_length=100)
    
    def __str__(self) -> str:
        return self.name

class Race(models.Model):
    name = models.CharField(max_length=255)
    start = models.DateTimeField()
    country = models.CharField(max_length=100)
    drivers = models.ManyToManyField("your_app.Driver")


class Car(models.Model):
    code = models.CharField(max_length=10, unique=True)
    driver = models.OneToOneField("your_app.Driver", on_delete=models.CASCADE)
    brand = models.CharField(max_length=30)

Aşağıdaki gibi yardımcı metodlar ile ihtiyaç duyulan anlarda, nesnelerinizi oluşturabilirsiniz.

class DriverTestCase(TestCase):
    def setUp(self) -> None:
        self.driver_1 = self._create_driver("Max Verstappen", "Red Bull")
        self.driver_1_car = self._create_car_of_driver(
            driver=self.driver_1, brand="Honda", code="A9154"
        )
        self.driver_2 = self._create_driver("Tommi Makinen", "Mitsubishi RallyArt")
        self.driver2_car = self._create_car_of_driver(
            driver=self.driver_2, code="A011", brand="Mitsubishi"
        )

    @staticmethod
    def _create_driver(name: str, team_name: str):
        return Driver.objects.create(name=name, team_name=team_name)

    @staticmethod
    def _create_car_of_driver(driver: Driver, code: str, brand: str):
        return Car.objects.create(driver=driver, code=code, brand=brand)

Fakat büyüyen projelerde, testler için daha fazla random dataya, veya benzersiz değerlere sahip olduğundan emin olduğumuz nesnelere ihtiyacımız olur. Daha da önemlisi oluşturacağımız bir nesnenin ilişkisel olarak bağlı olduğu çok fazla kısım olabilir. Bu gibi durumlarda tüm ilişkileri takip etmekten ve yaratılıcığımızı zorlamaktansa model_bakery kütüphanesini kullanabiliriz.

Model bakery.

model_bakery kütüphanesi ihtiyaç duyulan random nesneleri oluşturuken kullanılabilecek çok işe yarar bir kütüphane. Bir kaç satırla birden fazla nesneyi, hangi değerleri vereceğinizi veya ilişkisel alanları dert etmeden random data oluşturmanıza yardımcı olur.

Öncelikle kütüphaneyi yükleyelim ve kullanımına göz atalım.

$ pip install model_bakery

model_bakery kütüphanesi bir nesne oluşturmak için size 2 adet metod sunar. make ve prepare. make metodu doğrudan üzerinde çalışılacak olan nesneyi oluşturur iken, prepare metodu nesneyi kalıcı olarak oluşturmadan, referans tipte oluşturmanızı sağlar. İhtiyacınıza göre 2 kullanım şeklinden birini seçebilirsiniz.

Fakat bilgi olması açısından, Django 1.8 sonrası One to One ve Foreign key alanlarında referans tipinde nesneleri kabul etmemekte.

 

Genel kullanım örnekleri.

Hızlıca make metoduna göz atalım.

# django shell.

from model_bakery import baker

driver = baker.make(Driver)

driver.__dict__
 
{'_state': <django.db.models.base.ModelState at 0x7fbb3f40c9d0>,
 'id': 3,
 'name': 'abjvZikvNptUALwQqDSpIKlvwCPkWzXdbbrRZhGsdDnfQYQxokjDsHFPPFdoDzUKKpLErxKgQUqRtfnTnoFZMDFhJyfdHVVjSoKs',
 'team_name': 'bWYUABVKLYiuBnnkBxjUFywqnqpoGbXOPRTxVUkAyXpppjgTezhGjyyurhRsdDLKJaJXVMTqjnGoaHLKImWNDRZCogIqvvhchiKE'}

make metodu parametre olarak geçilen modelin alanları üzerinde dolaşıp, opsiyonel olmayan tüm alanları alanın tipine uygun random data ile doldurarak sürücüyü oluşturdu. Mucit bu! 🙂 Fakat bazı durumlarda oluşturulan nesneyi takip etmek için bazı alanlara spesifik değerler vermek isteyebiliriz.

local joke about tool

Belirlenmiş alanlar ile random data oluşturulmasının önüne geçmek.

Random olarak doldurulmasını istediğiniz alanları kwargs olarak geçerseniz, baker sizin için nesneyi oluştururken kullanacaktır.

# django shell.

driver2 = baker.make(Driver, name="Max Verstappen")

driver2.__dict__
 
{'_state': <django.db.models.base.ModelState at 0x7fbb3fd21370>,
 'id': 4,
 'name': 'Max Verstappen',
 'team_name': 'FmniswKZOGiUUiLBDQptFAISEimSrNJtVWeYUnhCXNHoGabfNXYQSGuzOuBWGYtRrQaGnfwfozgiAfgttNxQXVIYMJxKXBUCIbyo'}

Birden fazla nesne oluşturmak.

Tek seferde birden fazla nesne oluşturmak isterseniz _quantity parametresini güncellemeniz yeterli.

drivers = baker.make(Driver, _quantity=3)

assert len(drivers) == 3

İlişkisel alanları yönetmek.

car = baker.make(Car)

assert car.driver is not None
car.__dict__
 
{'_state': <django.db.models.base.ModelState at 0x7fbb3dc68fd0>,
 'id': 3,
 'code': 'gcFXUoTMVe',
 'driver_id': 11,
 'brand': 'ccFSAzwSgQIolVCVXnOGuChnNUptQb'}

model_bakery üzerinde çalıştığı modelden nesneler türetirken, opsiyonel olmayan ilişkisel alanlar için (m2m hariç) gerekli nesneleri de otomatik olarak oluşturur ve ilişkileri tanımlar.

Ayrıca oluşturduğunuz nesne üzerindeki ilişkisel alanlarda tutulan değerlere de, Django field lookup syntaxı ile değer atayabilirsiniz.

car2 = baker.make(Car, driver__team_name="Ford")

assert car2.driver.team_name == "Ford"

m2m alanlarını oluşturmak.

model_bakery m2m alanlarını otomatik olarak oluşturmadığından bahsetmiştik. Fakat make_m2m parametresini True geçer iseniz, sizin için 5 adet m2m alanında bağlı olan modelin nesnesini oluşturacaktır.

race = baker.make(Race, make_m2m=True)

assert race.drivers.count() == 5

m2m ilişkilerini açıkca belirtmek.

drivers_of_race = baker.make(Driver, _quantity=10)

race_turkey = baker.make(Race, drivers=drivers_of_race)

assert race_turkey.drivers.count() == 10

Unique alan değerlerinin yönetimi.

model_bakery nesneleri oluşturur iken tekrar etme şansı çok düşük değerler oluştursa da bazı durumlarda açıkca değerini belirttiğimiz alanların unique ve anlaşılır olmasını isteyebiliriz.

cars = baker.make(Car, code=baker.seq("Race Code ", increment_by=10) , _quantity=6)

for car in cars: print(car.code)
Race Code 10
Race Code 20
Race Code 30
Race Code 40
Race Code 50
Race Code 60

baker.seq metodu ile açıkca belirtilen alanı bir dizinin içine sokabilir, ilk parametrede geçilen değeri increment_by parametresi ile birleştirerek oluşturulan değerin unique ve anlaşılır olmasını sağlayabiliriz.

Opsiyonel alanların oluşturulması.

model_bakery opsiyonel alanlarını otomatik olarak doldurmaz fakat, test sırasında gerekli olması durumunda, o_fill_optional parametresine True değeri geçerek oluşturmasını talep edebiliriz.

baker.make(Car, _fill_optional=True)

Lezzetli tarifler oluşturmak. 🙂

model_bakery ile nesneleri oluşturur iken tekrar eden kısımlar için belirli şablonlar(tarif) oluşturarak kendimizi tekrar etmenin önüne geçebiliriz.

class Driver(models.Model):
    name = models.CharField(max_length=100)
    team_name = models.CharField(max_length=100)
    is_active = models.BooleanField() # new

Sürücü modelimizde yeni bir alan bulunsun, ve sürücülerin aktif ve inaktiflik durumlarını belirleyen şablonlar oluşturalım. model_bakery şablonlarınızı kullanmak istediğinizde uygulama dizinleriniz altında yaşayan, baker_recipes dosyalarını kontrol eder. Buradaki şablonları tanır ve kullanmanıza olanak sağlar.

# your_app.baker_recipes.py

from your_app.models import Driver
from model_bakery.recipe import Recipe


active_driver = Recipe(Driver, is_active=True)

inactive_driver = Recipe(Driver, is_active=False)

Bu şablonları kullanmaya göz atacak olursak,

Şablonlardan nesneler oluşturmak.

active = baker.make_recipe('your_app.active_driver')

assert active.is_active == True

inactive = baker.make_recipe('your_app.inactive_driver')

assert inactive.is_active == False

Tariflerden nesneler oluşturur iken yukarıda incelediğimiz özellikleri de kullanabilirsiniz. Değerleri ezebilir, çoklu nesne oluşturabilir vs.

baker.make_recipe('your_app.active_driver', is_active=False, name=baker.seq('Egehan ', increment_by=11), _quantity=3)
[<Driver: Egehan 11>, <Driver: Egehan 22>, <Driver: Egehan 33>]

Sonuç.

model_bakery test yazma sırasında işinizi fazlasıyla kolaylaştıran, süreci hızlandıran harika bir kütüphane. Nesneleri ve random dataları dert etmeden sadece testinize odaklanmanıza olanak veriyor.

Genel kullanım örneklerine, sizi hayatta tutabilecek ve başlamanıza olanak verecek kadar göz attık fakat kütüphane hakkında daha fazla bilgi sahibi olmak isterseniz dökümantasyon linki burada.

Django ve test ile ilgili bu blogda daha fazla okumak isterseniz o link de burada.

Görüşmek üzere, iyi testler!

Tarih:Blog

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle