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.
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!
İlk Yorumu Siz Yapın