Veritabanı güdümlü uygulamalar için birim testi yapmak için en iyi strateji hangisidir?


346

Arka uçta değişen karmaşıklık veritabanları tarafından yönlendirilen birçok web uygulamasıyla çalışıyorum. Genellikle, iş ve sunum mantığından ayrı bir ORM katmanı vardır . Bu, birim testini iş mantığını oldukça basit hale getirir; işler ayrı modüllerde uygulanabilir ve test için gerekli olan her türlü veri nesne alaycılığı ile taklit edilebilir.

Ancak ORM ve veritabanının kendisini test etmek her zaman sorun ve uzlaşmalarla doludur.

Yıllar boyunca, hiçbiri beni tamamen tatmin etmeyen birkaç strateji denedim.

  • Bilinen verilerle bir test veritabanı yükleyin. ORM'ye karşı testler yapın ve doğru verilerin geri geldiğini onaylayın. Buradaki dezavantaj, test DB'nizin uygulama veritabanındaki şema değişikliklerine ayak uydurması ve senkronizasyondan çıkmasıdır. Ayrıca yapay verilere dayanır ve aptal kullanıcı girişi nedeniyle oluşan hataları ortaya çıkarmayabilir. Son olarak, test veritabanı küçükse, eksik bir dizin gibi verimsizlikleri göstermez. (Tamam, sonuncusu gerçekten hangi birim testinin kullanılması gerektiği değil, acıtmıyor.)

  • Üretim veritabanının bir kopyasını yükleyin ve buna karşı test edin. Buradaki sorun, herhangi bir zamanda üretim veritabanında ne olduğu hakkında hiçbir fikriniz olmayabilir; veriler zaman içinde değişirse testlerinizin yeniden yazılması gerekebilir.

Bazı insanlar bu stratejilerin her ikisinin de belirli verilere dayandığını ve bir birim testin sadece işlevselliği test etmesi gerektiğini belirtmiştir. Bu amaçla şunu gördüm:

  • Sahte bir veritabanı sunucusu kullanın ve yalnızca ORM'nin belirli bir yöntem çağrısına yanıt olarak doğru sorguları gönderdiğini denetleyin.

Varsa, veritabanına dayalı uygulamaları test etmek için hangi stratejileri kullandınız? Sizin için en iyi olan neydi?


Benzersiz dizinler gibi durumlar için hala bir test ortamında veritabanı dizinleri olması gerektiğini düşünüyorum.
dtc

Ben pesonally burada bu soruyu umursamıyorum ama biz kurallara giderseniz, bu soru için değil stackoverflow daha çok içindir softwareengineering.stackexchange web.
ITExpert

Yanıtlar:


155

Aslında ilk yaklaşımınızı oldukça başarılı bir şekilde kullandım, ancak bazı problemlerinizi çözeceğini düşündüğümden biraz farklı şekillerde:

  1. Herkesin bir kullanıma alma işleminden sonra geçerli veritabanı şemasını oluşturabilmesi için tüm şemayı ve komut dosyalarını kaynak denetiminde oluşturmak için saklayın. Ayrıca, örnek verileri oluşturma işleminin bir parçası olarak yüklenen veri dosyalarında saklayın. Hatalara neden olan verileri keşfettikçe, hataların tekrar ortaya çıkmadığını kontrol etmek için örnek verilerinize ekleyin.

  2. Veritabanı şemasını oluşturmak, örnek verileri yüklemek ve testleri çalıştırmak için sürekli bir tümleştirme sunucusu kullanın. Bu şekilde test veritabanımızı senkronize tutarız (her test çalışmasında yeniden oluşturur). Bu, CI sunucusunun kendi özel veritabanı örneğine erişimi ve sahipliğini gerektirmesine rağmen, db şemamızın günde 3 kez oluşturulduğunu, muhtemelen teslimattan hemen önce bulunamayacak olan hataları bulmaya önemli ölçüde yardımcı olduğunu söylüyoruz (daha sonra değilse) ). Her işlemden önce şemayı yeniden oluşturduğumu söyleyemem. Kimse var mı? Bu yaklaşımla gerekmeyecek (belki de yapmalıyız, ama birisi unutursa büyük bir sorun değil).

  3. Grubum için, kullanıcı girişi uygulama seviyesinde (db değil) yapılır, bu yüzden standart birim testleri ile test edilir.

Üretim Veritabanı Kopyasını Yükleme:
Bu benim son işimde kullanılan yaklaşımdı. Birkaç sorunun büyük bir acı nedeniydi:

  1. Kopya, üretim sürümünden güncelliğini yitirir
  2. Kopyanın şemasında değişiklikler yapılacak ve üretim sistemlerine yayılmayacaktı. Bu noktada birbirinden farklı şemalarımız olacaktı. Eğlenceli değil.

Alay Veritabanı Sunucusu:
Bunu şu anki işimde de yapıyoruz. Her işlemden sonra, sahte db erişimcilerinin enjekte edildiği uygulama koduna karşı birim testleri yürütüyoruz. Sonra günde üç kez yukarıda açıklanan tam db derlemesini yürütürüz. Her iki yaklaşımı da kesinlikle tavsiye ederim.


37
Bir üretim veritabanı kopyasının yüklenmesinin güvenlik ve gizlilik etkileri de vardır. Bir kez büyür, bir kopyasını alıp geliştirme ortamınıza koymak çok önemli olabilir.
WW.

dürüst olmak gerekirse, bu çok büyük bir acıdır. Ben test için yeni ve ben de test etmek istiyorum bir orm yazdı. İlk yönteminizi zaten kullandım, ancak test birimini yapmadığını okuyun. Belirli bir db motor işlevselliği kullanıyorum ve bu yüzden bir DAO ile alay etmek zor olacak. Çalıştığım ve diğerlerinin kullandığı için şu anki yöntemimi kullanacağımı düşünüyorum. Otomatik testler rock btw. Teşekkürler.
ayaz

2
İki farklı büyük projeyi yönetiyorum, bunlardan birinde bu yaklaşım mükemmeldi, ama bunu diğer projede uygulamaya çalışırken çok fazla sıkıntı yaşıyoruz. Bu yüzden, testlerin yürütülmesi için her seferinde şemanın ne kadar kolay yeniden oluşturulabileceğine bağlı olduğunu düşünüyorum, şu anda bu son sorun için yeni bir çözüm bulmak için çalışıyorum.
Çapraz

2
Bu durumda, geçişleri çalıştırabilecek bir şey olan Roundhouse gibi bir veritabanı sürümleme aracı kullanmak kesinlikle buna değer. Bu, herhangi bir DB örneğinde çalıştırılabilir ve şemaların güncel olduğundan emin olmalıdır. Ayrıca, geçiş komut dosyaları yazıldığında, taşıma ve verileri senkronize halde tutarak test verileri de yazılmalıdır.
jedd.ahyoung

maymun yama ve alay daha iyi kullanın ve yazma işlemleri kaçının
Nickpick

56

Ben her zaman bu nedenlerle bir bellek içi DB (HSQLDB veya Derby) karşı testleri çalıştırıyorum:

  • Test DB'nizde hangi verilerin saklanacağını ve nedenini düşünmenizi sağlar. Üretim DB'nizi bir test sistemine çekmek, "Ne yaptığımı veya neden yaptığım hakkında hiçbir fikrim yok ve bir şey bozulursa, ben değildim!" ;)
  • Veritabanının yeni bir yerde çok az çaba ile yeniden oluşturulabilmesini sağlar (örneğin, üretimden bir hatayı çoğaltmamız gerektiğinde)
  • DDL dosyalarının kalitesine çok yardımcı olur.

Bellek içi DB, testler başladıktan sonra yeni verilerle yüklenir ve çoğu testten sonra, kararlı kalması için ROLLBACK'i çağırırım. DAİMA test DB'sindeki verileri sabit tutun! Veriler her zaman değişirse test edemezsiniz.

Veriler SQL'den, bir şablon DB'den veya bir döküm / yedeklemeden yüklenir. Dökümleri okunabilir bir formatta ise tercih ederim çünkü VCS'ye koyabilirim. Bu işe yaramazsa, bir CSV dosyası veya XML kullanırım. Çok büyük miktarda veri yüklemem gerekirse ... Asla muazzam miktarda veri yüklemeniz gerekmez :) Birim testleri için değil. Performans testleri başka bir konudur ve farklı kurallar geçerlidir.


1
Bellek (özellikle) bir bellek içi DB kullanmanın tek nedeni hız mıdır?
rinogo

2
Sanırım başka bir avantaj onun "atılabilir" doğası olabilir - kendiniz sonra temizlemek gerek; sadece bellek içi DB'yi öldürün. (Ancak bunu gerçekleştirmenin başka yolları da var, örneğin bahsettiğiniz
GERİ BİLDİRİM

1
Avantajı, her testin stratejisini bireysel olarak seçebilmesidir. Çocuk iş parçacığıyla ilgili çalışmalar yapan testlerimiz var;
Aaron Digulla

@Aaron: Biz de bu stratejiyi takip ediyoruz. Bellek içi modelin gerçek db ile aynı yapıya sahip olduğunu iddia etme stratejinizin ne olduğunu bilmek ister misiniz?
Guillaume

1
@Guillaume: Tüm veritabanlarını aynı SQL dosyalarından oluşturuyorum. H2, büyük veritabanlarının SQL özelliklerinin çoğunu desteklediğinden bunun için harikadır. Bu işe yaramazsa, orijinal SQL'i alıp bellek içi veritabanı için SQL'e dönüştüren bir filtre kullanırım.
Aaron Digulla

14

Bu soruyu uzun zamandır soruyorum, ama bence gümüş kurşun yok.

Şu anda yaptığım şey, DAO nesnelerini alay etmek ve veritabanında yaşayabilecek ilginç veri durumlarını temsil eden nesnelerin iyi bir koleksiyonunun bellek temsilini tutmaktır.

Bu yaklaşımla gördüğüm temel sorun, yalnızca DAO katmanınızla etkileşime giren kodu kapsamanız, ancak DAO'nun kendisini asla test etmemeniz ve deneyimlerime göre bu katmanda da çok sayıda hata olduğunu görüyorum. Ayrıca, veri tabanına karşı çalışan (TDD kullanmak veya yerel olarak hızlı test yapmak amacıyla) birkaç birim sınama tutuyorum, ancak bu amaçla hiçbir zaman veri tabanı tutmuyoruz, çünkü bu amaçla bir veritabanını tutmuyoruz ve ben CI sunucusunda çalışan testlerin kendi kendine yetmesi gerektiğini düşünün.

Çok ilginç bulduğum, ancak her zaman değmediği için biraz zaman harcadığım başka bir yaklaşım, sadece birim testinde çalışan gömülü bir veritabanında üretim için kullandığınız şemayı oluşturmaktır.

Bu yaklaşımın kapsamınızı geliştirdiği konusunda bir soru olmasa da, hem mevcut DBMS'nizle hem de gömülü değiştirme ile çalışmasını sağlamak için ANSI SQL'e olabildiğince yakın olmanız gerektiğinden birkaç dezavantaj vardır.

Kodunuzla daha alakalı ne düşünürseniz düşünün, DbUnit gibi daha kolay hale getirebilecek birkaç proje var .


13

(Örneğin Hatta öyle ya da içinde veritabanı alay izin araçlar varsa jOOQ 'ler MockConnection, görülebilir ki bu cevap , ben tavsiye ediyorum - ben jOOQ en satıcı için çalışmak reddi) değil karmaşık olan daha büyük veritabanlarını alay etmek sorguları.

Sadece ORM'nizi entegrasyon test etmek isteseniz bile, bir ORM'nin veritabanınıza çok karmaşık bir dizi sorgu yayınladığını unutmayın.

  • sözdizimi
  • karmaşa
  • sipariş (!)

Gerçekte sahte kukla veri üretmek için tüm bu şeyleri alay etmek, aslında sahte olanın içinde iletilen SQL ifadelerini yorumlayan küçük bir veritabanı oluşturmadığınız sürece oldukça zordur. Bunu söyledikten sonra, entegrasyon testlerinizi çalıştırabileceğiniz iyi bilinen verilerle kolayca sıfırlayabileceğiniz iyi bilinen bir entegrasyon testi veritabanı kullanın.


5

Ben ilk (kodu bir test veritabanı karşı çalışan) kullanın. Bu yaklaşımla gündeme getirdiğini gördüğüm tek önemli sorun, veritabanımda bir sürüm numarası tutarak ve tüm şema değişikliklerini her sürüm artışı için geçerli olan bir komut dosyasıyla gerçekleştirerek senkronize olan şemaların olasılığıdır.

Ben de ilk önce benim test ortamına karşı tüm değişiklikleri (veritabanı şeması dahil) yapmak, bu yüzden başka bir yol olur: Tüm testler geçtikten sonra, üretim ana bilgisayarına şema güncelleştirmelerini uygulayın. Ayrıca, gerçek üretim kutularına dokunmadan önce db güncellemesinin düzgün çalıştığını doğrulayabilmem için geliştirme sistemimde ayrı bir test vs uygulama veritabanları tutuyorum.


3

İlk yaklaşımı kullanıyorum, ancak bahsettiğiniz sorunları ele almanızı sağlayan biraz farklı.

DAO'lar için testler yapmak için gereken her şey kaynak kontrolündedir. DB oluşturmak için şema ve komut dosyaları içerir (docker bunun için çok iyidir). Gömülü DB kullanılabilirse - hız için kullanıyorum.

Açıklanan diğer yaklaşımlarla önemli fark, test için gerekli verilerin SQL komut dosyalarından veya XML dosyalarından yüklenmemesidir. Her şey (etkin bir şekilde sabit olan bazı sözlük verileri hariç), yardımcı işlevler / sınıflar kullanılarak uygulama tarafından oluşturulur.

Temel amaç test tarafından kullanılan verileri yapmaktır

  1. teste çok yakın
  2. açık (veri için SQL dosyaları kullanmak, hangi testin hangi test tarafından kullanıldığını görmeyi çok sorunlu hale getirir)
  3. testleri ilgisiz değişikliklerden ayırın.

Temel olarak, bu yardımcı programların yalnızca testin kendisi için test için gerekli olan şeyleri bildirerek belirtmesine izin verdiği ve alakasız şeyleri atladığı anlamına gelir.

Uygulamada ne anlama geldiğine dair bir fikir vermek için, Comments ile Posts arasında yazan bazı DAO testini göz önünde bulundurun Authors. Böyle bir DAO için CRUD işlemlerini test etmek için DB'de bazı veriler oluşturulmalıdır. Test şöyle görünecektir:

@Test
public void savedCommentCanBeRead() {
    // Builder is needed to declaratively specify the entity with all attributes relevant
    // for this specific test
    // Missing attributes are generated with reasonable values
    // factory's responsibility is to create entity (and all entities required by it
    //  in our example Author) in the DB
    Post post = factory.create(PostBuilder.post());

    Comment comment = CommentBuilder.comment().forPost(post).build();

    sut.save(comment);

    Comment savedComment = sut.get(comment.getId());

    // this checks fields that are directly stored
    assertThat(saveComment, fieldwiseEqualTo(comment));
    // if there are some fields that are generated during save check them separately
    assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));        
}

Bunun, SQL komut dosyalarına veya test verileri içeren XML dosyalarına göre çeşitli avantajları vardır:

  1. Kodun bakımı çok daha kolaydır (örneğin Yazar gibi birçok testte referans verilen bazı varlıklara zorunlu bir sütun eklemek, çok fazla dosya / kayıt değiştirmeyi gerektirmez, yalnızca oluşturucu ve / veya fabrikada bir değişiklik yapılmasını gerektirmez)
  2. Spesifik testin gerektirdiği veriler, başka bir dosyada değil, testin kendisinde açıklanmıştır. Bu yakınlık, test anlaşılırlığı açısından çok önemlidir.

Geri Alma ve Tamamlama

Testlerin yürütüldükleri zaman yapılmasını daha uygun buluyorum. İlk olarak, DEFERRED CONSTRAINTStaahhüt asla gerçekleşmezse bazı efektler (örneğin ) kontrol edilemez. İkinci olarak, bir test başarısız olduğunda, veri geri alma tarafından geri döndürülmediği için DB'de incelenebilir.

Bu, testin bozuk bir veri üretebileceği ve bu durumun diğer testlerde başarısızlığa yol açabileceği bir dezavantajı vardır. Bununla başa çıkmak için testleri izole etmeye çalışıyorum. Yukarıdaki örnekte her test yeni Authoryaratılabilir ve bununla ilgili diğer tüm varlıklar yaratılır, bu nedenle çarpışmalar nadirdir. Potansiyel olarak kırılabilen ancak bir DB seviyesi kısıtlaması olarak ifade edilemeyen kalan değişmezlerle başa çıkmak için, her bir testten sonra çalıştırılabilecek hatalı koşullar için bazı programatik kontroller kullanıyorum (ve bunlar CI'de çalıştırılıyor, ancak genellikle performans için yerel olarak kapatılıyor) nedeniyle).


Varlıkları ve sql komut dosyaları yerine orm kullanarak veritabanını tohumlarsanız, modelinizde değişiklikler yaparsanız, derleyicinin tohum kodunu düzeltmeye zorlaması avantajı da vardır. Sadece elbette statik yazı dili kullanıyorsanız önemlidir.
daramasala

Açıklığa kavuşturmak için: uygulamanız boyunca veya yalnızca testleriniz için yardımcı program işlevlerini / sınıflarını mı kullanıyorsunuz?
Ella

@Ella bu yardımcı işlevler genellikle test kodu dışında gerekli değildir. Örneğin düşünün PostBuilder.post(). Gönderinin tüm zorunlu nitelikleri için bazı değerler üretir. Üretim kodunda buna gerek yoktur.
Roman Konoval

2

JDBC tabanlı proje için (doğrudan veya dolaylı olarak, örneğin JPA, EJB, ...) tüm veritabanını değil (bu durumda gerçek bir RDBMS üzerinde bir test db kullanmak daha iyi olur), ancak yalnızca JDBC düzeyinde mockup'u kullanabilirsiniz. .

Avantajı bu şekilde gelen soyutlamadır, çünkü JDBC verileri (sonuç kümesi, güncelleme sayısı, uyarı, ...) arka uç ne olursa olsun aynıdır: ürününüz db, bir test db veya her test için sağlanan bazı mockup verileri durum.

JDBC bağlantısı her durum için alay edildiğinde test db'yi yönetmeye gerek yoktur (temizleme, her seferinde sadece bir test, fikstürleri yeniden yükle, ...). Her mockup bağlantısı izole edilmiştir ve temizlemeye gerek yoktur. JDBC değişimini taklit etmek için her test durumunda yalnızca minimum gerekli fikstür sağlanmıştır, bu da tüm bir test db'yi yönetmenin karmaşıklığını önlemeye yardımcı olur.

Acolyte, bu tür bir model için bir JDBC sürücüsü ve yardımcı programı içeren çerçevemdir: http://acolyte.eu.org .

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.