Bir nesneyi veritabanı sorgularıyla nasıl birim test edebilir


153

Birim sınamasının "tamamen harika", "gerçekten harika" ve "her türlü iyi şey" olduğunu duydum, ancak dosyalarımın% 70'i veya daha fazlası veritabanı erişimi içeriyor (bazıları okuma ve yazma) ve nasıl olduğundan emin değilim bu dosyalar için birim testi yazmak.

PHP ve Python kullanıyorum ama veritabanı erişimi kullanan çoğu / tüm diller için geçerli bir soru olduğunu düşünüyorum.

Yanıtlar:


82

Veritabanına yaptığınız çağrıları alay etmenizi öneririm. Alaycılar temelde, bir yöntemi çağırmaya çalıştığınız nesneye benzeyen nesnelerdir, yani arayanlar için aynı özelliklere, yöntemlere vb. Sahip oldukları anlamındadır. Ancak, belirli bir yöntem çağrıldığında yapmak üzere programlandıkları eylemi gerçekleştirmek yerine, bunu tamamen atlar ve yalnızca bir sonuç döndürür. Bu sonuç genellikle sizin tarafınızdan önceden tanımlanır.

Nesnelerinizi alay için ayarlamak üzere, muhtemelen aşağıdaki sahte kodda olduğu gibi bir çeşit kontrol / bağımlılık enjeksiyon desenini tersine çevirmeniz gerekir:

class Bar
{
    private FooDataProvider _dataProvider;

    public instantiate(FooDataProvider dataProvider) {
        _dataProvider = dataProvider;
    }

    public getAllFoos() {
        // instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
        return _dataProvider.GetAllFoos();
    }
}

class FooDataProvider
{
    public Foo[] GetAllFoos() {
        return Foo.GetAll();
    }
}

Şimdi birim testinizde, veritabanına gerçekten çarpmak zorunda kalmadan GetAllFoos yöntemini çağırmanıza izin veren bir FooDataProvider sahte oluşturursunuz.

class BarTests
{
    public TestGetAllFoos() {
        // here we set up our mock FooDataProvider
        mockRepository = MockingFramework.new()
        mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);

        // create a new array of Foo objects
        testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}

        // the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
        // instead of calling to the database and returning whatever is in there
        // ExpectCallTo and Returns are methods provided by our imaginary mocking framework
        ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)

        // now begins our actual unit test
        testBar = new Bar(mockFooDataProvider)
        baz = testBar.GetAllFoos()

        // baz should now equal the testFooArray object we created earlier
        Assert.AreEqual(3, baz.length)
    }
}

Özetle, yaygın bir alay senaryosu. Tabii ki yine de gerçek veritabanı çağrılarınızı da birim test etmek isteyeceksiniz, bunun için veritabanına vurmanız gerekecek.


Bu eski ama zaten DB olan birine yinelenen bir tablo oluşturma hakkında biliyorum. Bu şekilde DB çağrılarının çalıştığını doğrulayabilirsiniz?
fazla

1
PHP'nin PDO'sunu veritabanına en düşük seviye erişimim olarak kullanıyorum, üzerinde bir arayüz çıkardım. Sonra bunun üzerine bir uygulama farkında veritabanı katmanı inşa ettim. Bu, tüm ham SQL sorgularını ve diğer bilgileri içeren katmandır. Uygulamanın geri kalanı bu üst düzey veritabanı ile etkileşime girer. Bunu birim test için oldukça iyi buldum; Uygulama sayfalarımı uygulama veritabanıyla nasıl etkileşime girdiklerini test ediyorum. Uygulama veritabanımı PDO ile nasıl etkileşime girdiğini test ediyorum. Ben PDO hata olmadan çalışır varsayalım. Kaynak kodu: manx.codeplex.com
yasallaştırmak

1
@bretterer - Yinelenen bir tablo oluşturmak entegrasyon testi için iyidir. Birim testi için genellikle veritabanından bağımsız olarak bir kod birimini test etmenizi sağlayacak sahte bir nesne kullanırsınız.
BornToCode

2
Birim testlerinizde veritabanı çağrılarını alay etmenin değeri nedir? Farklı bir sonuç döndürmek için uygulamayı değiştirebileceğiniz için yararlı görünmüyor, ancak birim testiniz (yanlış) geçecek.
bmay2

2
@ bmay2 Yanlış değilsin. Orijinal cevabım uzun zaman önce (9 yıl!) Birçok insanın kodlarını test edilebilir bir şekilde yazmadıkları ve test araçlarının ciddi bir şekilde eksik olduğu zamanlarda yazılmıştır. Bu yaklaşımı artık tavsiye etmem. Bugün sadece bir test veritabanı kurar ve test için gerekli verilerle doldurur ve / veya kodumu tasarlar, böylece bir veritabanı olmadan mümkün olduğunca çok mantık test edebilirim.
Doug R

25

İdeal olarak, nesneleriniz cahil olmalı. Örneğin, istekte bulunacağınız, nesneleri döndürecek bir "veri erişim katmanınız" olmalıdır. Bu şekilde, bu parçayı birim testlerinizin dışında bırakabilir veya tek başına test edebilirsiniz.

Nesneleriniz veri katmanınıza sıkıca bağlıysa, uygun birim sınaması yapmak zordur. birim testin ilk kısmı "birim" dir. Tüm birimler ayrı ayrı test edilebilmelidir.

Benim c # projelerde, tamamen ayrı bir veri katmanı ile NHibernate kullanın. Nesnelerim çekirdek etki alanı modelinde yaşıyor ve uygulama katmanımdan erişiliyor. Uygulama katmanı, hem veri katmanıyla hem de etki alanı modeli katmanıyla konuşur.

Uygulama katmanına bazen "İş Katmanı" da denir.

PHP kullanıyorsanız, SADECE veri erişimi için belirli bir sınıf kümesi oluşturun . Nesnelerinizin nasıl kalıcı olduklarını bilmediklerinden emin olun ve uygulama sınıflarınızdaki ikisini birbirine bağlayın.

Başka bir seçenek, alaycı / taslakları kullanmak olacaktır.


Bunu her zaman kabul ettim, ancak uygulamada son teslim tarihleri ​​ve "tamam, şimdi sadece bir özellik daha ekleyin, bugün 14:00 ile" bu başarılması en zor şeylerden biri. Bununla birlikte, bu tür bir şey yeniden düzenlemenin birincil hedefidir, eğer patronum hiç karar vermezse, tamamen yeni iş mantığı ve tabloları gerektiren 50 yeni ortaya çıkan sorun düşünmedi.
Darren Ringer

3
Nesneleriniz veri katmanınıza sıkıca bağlıysa, uygun birim sınaması yapmak zordur. birim testin ilk kısmı "birim" dir. Tüm birimler ayrı ayrı test edilebilmelidir. güzel açıklama
Amitābha

11

Veritabanı erişimi olan bir nesneyi birim olarak sınamanın en kolay yolu işlem kapsamlarını kullanmaktır.

Örneğin:

    [Test]
    [ExpectedException(typeof(NotFoundException))]
    public void DeleteAttendee() {

        using(TransactionScope scope = new TransactionScope()) {
            Attendee anAttendee = Attendee.Get(3);
            anAttendee.Delete();
            anAttendee.Save();

            //Try reloading. Instance should have been deleted.
            Attendee deletedAttendee = Attendee.Get(3);
        }
    }

Bu, temelde bir işlem geri dönüşü gibi veritabanının durumunu geri döndürür, böylece testi herhangi bir yan etkisi olmadan istediğiniz kadar çalıştırabilirsiniz. Bu yaklaşımı büyük projelerde başarıyla kullandık. Yapımızın çalışması biraz uzun sürüyor (15 dakika), ancak 1800 birim test yaptırmak korkunç değil. Ayrıca, oluşturma süresi bir endişe ise, oluşturma işlemini, biri src oluşturmak için, diğeri sonradan birim testleri, kod analizi, paketleme vb.


1
+1 Veri Erişimi katmanlarını birim test ederken çok zaman kazandırır. TS'nin genellikle istenmeyebilecek
MSDTC'ye

Asıl soru PHP ile ilgili, bu örnek C # gibi görünüyor. Ortamlar çok farklı.
yasallaştır

2
Sorunun yazarı, bir DB ile ilgisi olan tüm diller için geçerli olan genel bir soru olduğunu belirtti.
Vedran

9
ve bu sevgili dostlar, uyum testleri
AA.

10

Bir ton "iş mantığı" sql operasyonunu içeren orta kademe sürecimizi test etmeye başladığımızda belki de deneyimlerimizin tadını çıkarabilirim.

İlk olarak, herhangi bir makul veritabanı bağlantısını "yuvalamamıza" izin veren bir soyutlama katmanı oluşturduk (bizim durumumuzda tek bir ODBC tipi bağlantıyı destekledik).

Bir kez bu yerdeydi, daha sonra kodumuzda böyle bir şey yapabildik (C ++ ile çalışıyoruz, ancak fikri anladığınızdan eminim):

GetDatabase (). ExecuteSQL ("foo (blah, blah) içine yerleştirin")

Normal çalışma zamanında, GetDatabase () ODBC üzerinden tüm veritabanımızı (sorgular dahil) besleyen bir nesneyi doğrudan veritabanına döndürür.

Daha sonra bellek içi veritabanlarına bakmaya başladık - en iyisi uzun zamandır SQLite gibi görünüyor. ( http://www.sqlite.org/index.html ). Kurulumu ve kullanımı son derece basittir ve GetDatabase'i () alt sınıfı ve geçersiz kılmamıza izin vererek, yapılan her test için oluşturulan ve imha edilen bir bellek içi veritabanına sql iletmemize izin verdi.

Hala bunun ilk aşamalarındayız, ancak şu ana kadar iyi görünüyor, ancak gerekli tüm tabloları oluşturduğumuzdan ve bunları test verileriyle doldurduğumuzdan emin olmalıyız - ancak burada iş yükünü biraz azalttık bizim için çok şey yapabilen genel bir yardımcı fonksiyonlar dizisi.

Genel olarak, TDD sürecimizle son derece yardımcı oldu, çünkü bazı hataları düzeltmek için oldukça zararsız değişiklikler yapmak, sql / veritabanlarının doğası nedeniyle sisteminizin diğer (tespit edilmesi zor) alanlarında oldukça garip etkiler yaratabilir.

Açıkçası, deneyimlerimiz bir C ++ geliştirme ortamı etrafında odaklanmıştır, ancak eminim PHP / Python altında benzer bir şey alabilirsiniz eminim.

Bu yardımcı olur umarım.


9

Sınıflarınızı birim olarak test etmek istiyorsanız veritabanı erişimini taklit etmelisiniz. Sonuçta, bir birim testte veritabanını test etmek istemezsiniz. Bu bir entegrasyon testi olurdu.

Çağrıları soyutlayın ve ardından beklenen verileri döndüren bir sahte ekleyin. Sınıflarınız sorguları yürütmekten daha fazlasını yapmazsa, bunları test etmeye bile değmeyebilir ...


6

XUnit Test Patterns kitabı , veritabanına çarpan birim sınama kodunu işlemenin bazı yollarını açıklar. Bunu yapmak istemediğini söyleyen diğer insanlara katılıyorum çünkü yavaş, ama bir ara yapman gerekiyor IMO. Daha üst düzey şeyleri test etmek için db bağlantısını atlamak iyi bir fikirdir, ancak gerçek veritabanıyla etkileşim kurmak için yapabileceğiniz şeyler hakkında öneriler için bu kitaba göz atın.


4

Sahip olduğunuz seçenekler:

  • Birim sınamalarına başlamadan önce veritabanını silen bir komut dosyası yazın, sonra db'yi önceden tanımlanmış veri kümesiyle doldurun ve sınamaları çalıştırın. Bunu her testten önce de yapabilirsiniz - yavaş olacaktır, ancak daha az hataya eğilimli olacaktır.
  • Veritabanını enjekte edin. (Sahte Java dilinde örnek, ancak tüm OO dilleri için geçerlidir)

    sınıf Veritabanı {
     herkese açık Sonuç sorgusu (Dize sorgusu) {... real db here ...}
    }

    MockDatabase sınıfı Veritabanı { public Result query (Dize sorgusu) { dönüş "sahte sonuç"; } }

    class ObjectThatUsesDB { public ObjectThatUsesDB (Veritabanı db) { this.database = db; } }

    şimdi üretimde normal veritabanı kullanırsınız ve tüm testler için geçici olarak oluşturabileceğiniz sahte veritabanını enjekte edersiniz.

  • Kodun çoğunda DB kullanmayın (bu zaten kötü bir uygulamadır). Sonuçlarla dönmek Useryerine normal nesneleri döndürecek (yani bir demet yerine geri dönecek {name: "marcin", password: "blah"}) bir "veritabanı" nesnesi oluşturun, tüm testlerinizi geçici olarak oluşturulmuş gerçek nesnelerle yazın ve bu dönüşümü sağlayan bir veritabanına bağlı büyük bir test yazın Tamam çalışıyor.

Elbette bu yaklaşımlar birbirini dışlamaz ve bunları istediğiniz gibi karıştırabilir ve eşleştirebilirsiniz.


3

Projenizde yüksek bir uyum ve gevşek bağlantı varsa, veritabanı erişiminizi birim test etmek yeterince kolaydır. Bu şekilde, her sınıfın yaptığı şeyleri, her şeyi bir kerede test etmek zorunda kalmadan test edebilirsiniz.

Örneğin, kullanıcı arabirimi sınıfınızı birim sınaması yaparsanız, yazdığınız sınamalar, bu işlevin arkasındaki iş mantığı veya veritabanı eylemini değil, yalnızca kullanıcı arabirimi içindeki mantığı beklendiği gibi doğrulamaya çalışmalıdır.

Gerçek veritabanı erişimini birim sınamak istiyorsanız, ağ yığınına ve veritabanı sunucunuza bağlı olacağınız için daha fazla entegrasyon sınamasıyla sonuçlanacaksınız, ancak SQL kodunuzun istediklerinizi yaptığını doğrulayabilirsiniz. yapmak.

Kişisel olarak benim için birim testin gizli gücü, uygulamalarımı onlarsız yapabileceğimden çok daha iyi bir şekilde tasarlamamı sağlamasıydı. Çünkü bu "bu işlev her şeyi yapmalı" zihniyetinden kopmamı sağladı.

Üzgünüm PHP / Python için belirli bir kod örnekleri yok, ama bir .NET örneği görmek istiyorsanız, ben bu aynı testi yapmak için kullanılan bir tekniği açıklayan bir yazı var.


2

Ben genellikle nesneleri test (ve varsa ORM) ve db test arasında testler kırmaya çalışın. Ben veri erişim çağrıları alay ederek şeylerin nesne tarafını test ederken ben deneyimlerime göre, genellikle oldukça sınırlı olan db ile nesne etkileşimlerini test ederek şeylerin db tarafını test.

Ben bir test db oluşturmak veya anında test veri oluşturmak zorunda değildi bu yüzden veri erişim kısmı alay başlayana kadar yazma birimi testleri ile sinirli alırdım. Verileri alay ederek, bunların tümünü çalışma zamanında oluşturabilir ve nesnelerinizin bilinen girdilerle düzgün çalıştığından emin olabilirsiniz.


2

Bunu PHP'de hiç yapmadım ve Python'u hiç kullanmadım, ama yapmak istediğiniz şey veritabanına yapılan çağrıları alay etmek. Bunu yapmak için bazı IoC uygulayabilirsiniz 3. parti aracı veya kendiniz yönetmek ister, o zaman o sahte aramanın sonucunu kontrol edecek nerede veritabanı arayanın bazı sahte uygulayabilir.

Basit bir IoC formu sadece Arayüzler kodlanarak yapılabilir. Bu, kodunuzda bir tür nesne yönelimi gerektirir, bu yüzden yaptığınız şey için geçerli olmayabilir (tüm gitmek zorunda olduğum için PHP ve Python'dan bahsettiğinizden eminim)

Umarım bu yardımcı olur, başka bir şey yoksa şimdi arayacağınız terimler var.


2

Veritabanı sonrası ilk erişimin bir arabirim uygulayan bir DAO katmanına ayrılması gerektiğini kabul ediyorum. Ardından, mantığınızı DAO katmanının bir saplama uygulamasına karşı test edebilirsiniz.


2

Veritabanı motorunu soyutlamak için alaycı çerçeveler kullanabilirsiniz . PHP / Python bazı var mı bilmiyorum ama yazılan diller için (C #, Java vb) bol seçenek vardır

Aynı zamanda, bu veritabanı erişim kodunu nasıl tasarladığınıza da bağlıdır, çünkü bazı tasarımlar birim testi yapmak için önceki gönderilerin belirttiği gibi diğerlerinden daha kolaydır.


2

Birim testleri için test verilerinin ayarlanması zor olabilir.

Java söz konusu olduğunda, birim testi için Spring API'leri kullanıyorsanız, işlemleri birim düzeyinde kontrol edebilirsiniz. Başka bir deyişle, veritabanı güncellemelerini / eklemeleri / silmelerini ve değişiklikleri geri almayı içeren birim testleri yürütebilirsiniz. Yürütmenin sonunda, yürütmeye başlamadan önceki her şeyi veritabanında bırakırsınız. Bana göre, alabileceği kadar iyi.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.