Birim testleri tasarımı nasıl kolaylaştırır?


43

Meslektaşımız, ünite testleri yazmamızı, tasarımımızı ve refactor işlerimizi geliştirmemize gerçekten yardımcı olduğunu söylüyor, ancak nasıl olduğunu bilmiyorum. Bir CSV dosyası yüklüyor ve onu ayrıştırıyorsam, birim testi (alanlardaki değerleri doğrulayarak) tasarımımı doğrulamama nasıl yardımcı olur? Bağlanma ve modülerlikten bahsetti, ancak bana göre pek bir anlam ifade etmiyor - ama yine de çok teorik bir geçmişim yok.

Bu, yinelenen olarak işaretlediğiniz soru ile aynı değil, sadece "yardımcı olur" diyen teoriye değil, bunun nasıl yardımcı olduğuna dair gerçek örneklerle ilgilenirim. Aşağıdaki yanıtı ve yorumu beğeniyorum ancak daha fazla bilgi edinmek istiyorum.



3
Aşağıdaki cevap gerçekten bilmeniz gereken tek şey. Bütün gün kök bağımlılık enjekte edilen fabrika fabrika fabrikalarını gün boyu yazanların yanında oturmak, doğru çalışan, doğrulaması kolay ve zaten belgelendirilmiş birim testlerine karşı sessizce basit kod yazan bir adamdır.
Robert Harvey,

4
@gnat birim testi yapıyor otomatik olarak TDD anlamına gelmez, farklı bir soru
Joppe

11
"birim testi (alanlardaki değerleri onaylayın )" - birim testlerini giriş onaylaması ile birleştiriyor gibi görünüyorsunuz.
jonrsharpe

1
@jonrsharpe Bir CSV dosyasını ayrıştırma kodu olduğu göz önüne alındığında, belirli bir CSV dizesinin beklenen çıktıyı verdiğini doğrulayan gerçek bir birim testinden bahsediyor olabilir.
JollyJoker

Yanıtlar:


3

Ünite testleri sadece tasarımı kolaylaştırmakla kalmaz, aynı zamanda temel faydalarından biridir.

İlk önce test yazma modülerliği ve temiz kod yapısını ortaya çıkarır.

İlk önce kodunuzu yazdığınızda, belirli bir kod biriminin "koşullarının", kodunuzda varsaydığınızda doğal olarak bağımlılıklara (genellikle alay veya saplamalar aracılığıyla) itildiğini göreceksiniz.

"Verilen koşul x, davranış y'yi bekle", genellikle, x( testin mevcut bileşenin davranışını doğrulaması gereken bir senaryodur ) tedarik yetmeye zorlanacak ve bir arama doğrulanacak olan sahte olacaktır. Testin sonu (bir "dönmesi gerekir" olmadıkça y, bu durumda test sadece dönüş değerini açıkça doğrular).

Sonra, bu birim belirtildiği gibi davrandığında , keşfettiğiniz bağımlılıkları (ve xve için y) yazmaya devam edersiniz.

Bu, temiz, modüler bir kodun yazılmasını çok kolay ve doğal bir işlem haline getirir; aksi halde sorumlulukları ve çift davranışlarını bulanıklaştırmadan birlikte gerçekleştirmek genellikle kolaydır.

Testleri daha sonra yazmak, kodunuzun ne zaman zayıf yapılandırıldığını size söyleyecektir.

Bir kod parçası için testler yazmak zorlaştığından, saplanacak ya da alay edilecek çok fazla şey olduğu için ya da şeyler birbirine çok sıkı bir şekilde bağlı olduğu için, kodunuzda yapılacak iyileştirmelerin olduğunu biliyorsunuzdur.

Tek bir ünitede çok fazla davranış olduğu için “testleri değiştirmek” bir yük haline geldiğinde, kodunuzda (veya sadece test yazma yaklaşımınızda - fakat bu genellikle benim deneyimimde geçerli değil) geliştirmeniz gerektiğini biliyorsunuz. .

Senin senaryoları çok karmaşık hale ne zaman ( "eğer xve yve zsen soyut daha fazla gerektiğinden, bilirsin ... sonra") kodunuzda yapmak iyileştirmeler var.

Çoğaltma ve fazlalık nedeniyle aynı testler iki farklı fikstürde yapıldığında, kodunuzda yapılacak iyileştirmelerin olduğunu biliyorsunuzdur.

İşte kodlama ile test edilebilirlik ve tasarım arasında çok yakın bir ilişki olduğunu gösteren Michael Feathers'ın mükemmel bir konuşması (yorumlarda ilk olarak displayName tarafından gönderildi). Konuşma ayrıca, genel olarak iyi tasarım ve test edilebilirlik hakkındaki bazı yaygın şikayetleri ve yanlış anlamaları da ele almaktadır.


@SSECommunity: Bugün itibariyle sadece 2 yükseltme ile bu cevabı gözden kaçırmak çok kolaydır. Bu cevaba bağlı Michael Feathers'ın Konuşmasını şiddetle tavsiye ederim
displayName

103

Birim testleriyle ilgili en iyi şey, kodunuzu diğer programcıların kodunuzu nasıl kullanacağını kullanmanıza izin vermesidir.

Kodunuz ünite testine zor geliyorsa, kullanımı muhtemelen zor olacaktır. Eğer bağımlılıktan atlamaksızın bağımlılıkları enjekte edemezseniz, kodunuz muhtemelen kullanılamayacak kadar esnek olacaktır. Verileri ayarlamak veya hangi işlemleri yapması gerektiğini bulmak için çok zaman harcamanız gerekiyorsa, test edilen kodunuz muhtemelen çok fazla eşleşmeye sahiptir ve çalışmak için acı verici olacaktır.


7
Mükemmel cevap. Testlerimi daima kodun ilk müşterisi olarak düşünmeyi seviyorum; Testleri yazmak acı verici olursa, API'yi tüketen ya da geliştirdiğim kodları yazmak acı verici olacaktır.
Stephen Byrne

41
Benim durumumda, en ünite testleri yok "Diğer programcılar kodunuzu nasıl kullanacaklarını kodunuzu kullanın.". Kodunuzu kullanırlar çünkü birim testleri kodu kullanır. Doğru, birçok ciddi kusuru ortaya çıkaracaklar. Ancak birim testi için tasarlanmış bir API, genel kullanım için en uygun API olmayabilir. Basitçe yazılmış ünite testleri, çoğu zaman çok fazla dahili içeriğe maruz bırakmak için alttaki kodu gerektirir. Yine, tecrübelerime dayanarak - bunu nasıl hallettiğinizi duymak isterim. (Aşağıdaki cevabımı görün)
user949300

7
@ user949300 - İlk önce teste inanan biri değilim. Cevabım önce kod (ve kesinlikle tasarım) fikrine dayanıyor. API'ler birim testi için tasarlanmamalı, müşteriniz için tasarlanmalıdır. Birim testleri müşterinize yaklaşmanıza yardımcı olur, ancak bir araçtır. Size hizmet etmek için oradalar, tam tersi değil. Ve kesinlikle seni berbat bir kod yapmaktan alıkoyamayacaklar.
Telastyn

3
Tecrübelerimdeki birim testleriyle ilgili en büyük sorun, iyi olanları yazmanın en başta iyi kod yazmak kadar zor olmasıdır. Kötü koddan iyi kod söyleyemezseniz, birim testleri yazmak kodunuzu daha iyi hale getirmez. Ünite testini yazarken, yumuşak, hoş kullanım ile "garip" veya zor olanı ayırt edebilmelisiniz. Kodunuzu biraz kullanmanıza neden olabilir, ancak yaptığınız işin kötü olduğunu kabul etmenizi zorlamazlar.
jpmc26

2
@ user949300 - Burada aklımdaki klasik örnek, bir connString gerektiren bir Havuz. Bunu bir kamuya açık yazılabilir özellik olarak ortaya koyduğunuzu ve sizden sonra () bir Havuz oluşturduklarını varsayalım. Buradaki fikir, 5. veya 6. saatten sonra, bu adımı atmayı unutan bir test yazmışsınızdır - ve bu nedenle çöküyor-, yapıcıdan geçen bir sınıf değişkeni olmaya zorlama yönündeki "doğal" bir eğilimde olacaksınız - API daha iyi ve bu tuzağı engelleyen üretim kodunun yazılması olasılığını arttırıyor. Bu bir garanti değil ama yardımcı oluyor, imo.
Stephen Byrne

31

Bunu gerçekleştirmek biraz zaman aldı, ancak teste dayalı geliştirme yapmanın ( birim testleri kullanarak ) gerçek yararı (düzenleme: benim için milajınız değişebilir) API tasarımını önceden yapmanız gerekiyor !

Geliştirmeye yönelik tipik bir yaklaşım, önce belirli bir sorunun nasıl çözüleceğini bulmaktır ve bu bilgi ve ilk uygulama tasarımı ile çözümünüzü çağrıştırmak için bir yol vardır. Bu oldukça ilginç sonuçlar verebilir.

TDD yaparken ilk önce çözümünüzü kullanacak kodu yazmanız gerekir. Giriş parametreleri ve beklenen çıktı böylece doğru olduğundan emin olabilirsiniz. Bu da gerçekte ne yapmanız gerektiğini anlamanızı gerektirir, böylece anlamlı testler oluşturabilirsiniz. O zaman ve ancak o zaman çözümü uygularsınız. Aynı zamanda benim deneyimim, kodunuzun tam olarak neyi başarması gerektiğini bildiğiniz zaman, daha netleşmesidir.

Ardından, uygulamadan sonra birim testler yeniden yapılanmanın işlevselliği bozmadığından emin olmanıza yardımcı olur ve kodunuzu nasıl kullanacağınızla ilgili belgeler sunar (ki bu testten geçtikçe doğru olduğunu bilirsiniz!). Ancak bunlar ikincildir - en büyük yarar, ilk başta kodu oluştururken zihniyettir.


Bu kesinlikle bir faydadır, ancak bunun “gerçek” fayda olduğunu sanmıyorum - asıl yarar, kodunuz için testler yazmanın doğal olarak "koşulları" bağımlılıklara iten ve bağımlılıkların aşırı enjekte edilmesini kuran gerçeğinden kaynaklanmaktadır (soyutlamayı daha da teşvik etmek) ) başlamadan önce.
Karınca P,

Sorun şu ki, bu API ile eşleşen bir dizi test yazıyorsunuz, daha sonra tam olarak gerektiği gibi çalışmıyor ve kodunuzu ve tüm testleri yeniden yazmak zorundasınız. Halka açık API'ler için, muhtemelen değişmeyeceklerdir ve bu yaklaşım iyidir. Ancak, yalnızca dahili olarak kullanılan kod için olan API'ler, birlikte çalışan birçok yarı özel API gerektiren bir özelliğin nasıl uygulanacağını düşündüğünüzde çok şey değiştirir
Juan Mendes,

@AntP Evet, bu API tasarımının bir parçasıdır.
Thorbjørn Ravn Andersen

@JuanMendes Bu nadir değildir ve gereksinimleri değiştirdiğinizde diğer testlerde olduğu gibi bu testlerin de değiştirilmesi gerekecektir. İyi bir IDE, yöntem imzalarını değiştirdiğinizde otomatik olarak yapılan çalışmanın bir parçası olarak sınıfları yeniden düzenlemenize yardımcı olur.
Thorbjørn Ravn Andersen

@JuanMendes eğer iyi testler ve küçük üniteler yazıyorsanız, tarif ettiğiniz etkinin etkisi pratikte küçüktür.
Ant P

6

Birim testlerinin "tasarımımızı ve yeniden düzenleyici şeylerimizi iyileştirmemize yardımcı olduğu" konusunda% 100 kabul edecektim.

İlk tasarımı yapmana yardım edip etmeyecekleri konusunda iki kafadayım . Evet, bariz kusurları ortaya çıkarıyorlar ve sizi "kodu nasıl test edilebilir hale getirebilirim?" Diye düşünmeye zorluyorlar mı? Bu, daha az yan etki, daha kolay yapılandırma ve kurulum, vb. İle sonuçlanmalıdır.

Ancak, benim tecrübeme göre, aşırı basit birim testleri, tasarımın gerçekte ne olduğunu anlamadan önce yazılmış (kuşkusuz ki bu zor çekirdekli TDD'nin abartılı bir hali, ancak çoğu zaman kodlayıcılar çok düşünmeden önce bir test yazıyor) Çok fazla içsel gösteren etki alanı modelleri.

TDD'deki deneyimim birkaç yıl önceydi, bu yüzden temel tasarımı çok fazla zorlamayan testler yazarken hangi yeni tekniklerin yardımcı olabileceğini duymakla ilgileniyorum. Teşekkürler.


Uzun sayıda yöntem parametresi bir kod kokusu ve bir tasarım hatasıdır.
Sufian

5

Birim testi, işlevler arasındaki arabirimlerin nasıl çalıştığını görmenizi sağlar ve genellikle hem yerel tasarımın hem de genel tasarımın nasıl geliştirileceği hakkında fikir verir. Ayrıca, kodunuzu geliştirirken birim testlerinizi geliştirirseniz, hazır bir regresyon test takımına sahipsiniz. Bir kullanıcı arayüzü veya arka uç kitaplığı geliştiriyor olmanız farketmez.

Program geliştirildikten sonra (birim testleriyle), hatalar giderildikçe, hataların giderildiğini doğrulamak için testler ekleyebilirsiniz.

Bazı projelerim için TDD kullanıyorum. Ders kitaplarından veya doğru olduğu düşünülen makalelerden çıkardığım örnekleri oluşturmak için çok çaba sarf ediyorum ve bu örneği kullanarak geliştirdiğim kodu test ediyorum. Metotlarla ilgili karşılaştığım yanlış anlamalar çok belirginleşiyor.

Kodun önce yazılması ya da testin ilk önce yapılması umurumda olmadığından bazı meslektaşlarımdan biraz daha gevşek olma eğilimindeyim.


Bu benim için harika bir cevap. Birkaç örnek vermeyi düşünür müsünüz, örneğin her bir vaka için bir tane (tasarım konusunda fikir edindiğinizde vb.).
Kullanıcı039402

5

Birim test etmek istediğinizde, ayrıştırıcınızın değer sınırlamasını doğru şekilde algıladığını tespit etmek için, bir CSV dosyasından bir satır geçirmek isteyebilirsiniz. Testinizi doğrudan ve kısa yapmak için bir satırı kabul eden bir yöntemle test etmek isteyebilirsiniz.

Bu otomatik olarak satırların okunmasını bireysel değerleri okumaktan ayırır.

Başka bir seviyede, test projenize her türlü fiziksel CSV dosyasını koymak istemeyebilirsiniz, ancak okunabilirliği ve testin amacını iyileştirmek için testinizin içinde büyük bir CSV dizisi olduğunu bildirerek daha okunaklı bir şey yapmak isteyebilirsiniz. Bu, ayrıştırıcınızı başka bir yerde yapabileceğiniz herhangi bir G / Ç'den ayırmanıza yol açacaktır.

Sadece basit bir örnek, sadece uygulamaya başlayın, sihirbazı bir noktada hissedeceksiniz (sahip olduğum).


4

Basitçe söylemek gerekirse, birim testleri yazmak, kodunuzdaki kusurları ortaya çıkarmanıza yardımcı olur.

Jonathan Wolter, Russ Ruffer ve Miško Hevery tarafından yazılan test edilebilir kodun yazılmasında kullanılan bu muhteşem rehber , kodlamayı test etmeyi engelleyen hataların aynı şekilde kolayca kullanılmasını ve esnekliğini engellediğine dair sayısız örnekler içermektedir. Bu nedenle, kodunuz test edilebilir ise kullanımı daha kolaydır. "Ahlak" ın çoğu, kod tasarımını büyük ölçüde geliştiren gülünç derecede basit ipuçlarıdır ( Bağımlılık Enjeksiyonu FTW).

Örneğin: önbellek bir şeyler çıkarmaya başladığında computeStuff yönteminin düzgün çalışıp çalışmadığını test etmek çok zordur. Bunun nedeni, "bigCache" neredeyse doluncaya kadar önbelleğe el ile eklemeniz gerekmesidir.

public OopsIHardcoded {

   Cache cacheOfExpensiveComputations;

   OopsIHardcoded() {
       this.cacheOfExpensiveComputation = buildBigCache();
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Bununla birlikte, bağımlılık enjeksiyonunu kullandığımızda, önbellek bir şeyler çıkarmaya başladığında computeStuff yönteminin düzgün çalışıp çalışmadığını test etmek çok kolaydır. Tek yaptığımız, new HereIUseDI(buildSmallCache()); Notice adını verdiğimiz yerde bir test oluşturmak , nesneyi daha hassas kontrolümüz var ve derhal temettü öder.

public HereIUseDI {

   Cache cacheOfExpensiveComputations;

   HereIUseDI(Cache cache) {
       this.cacheOfExpensiveComputation = cache;
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Kodumuz genellikle bir veritabanında tutulan verileri gerektirdiğinde benzer avantajlar elde edilebilir ... tam olarak ihtiyacınız olan verileri girin.


2
Dürüst olmak gerekirse, örneğin ne demek istediğinizi anlamadım. ComputeStuff metodunun önbellek ile ilgisi nedir?
John V

1
@ user970696 - Evet, "computeStuff ()" uygulamasının önbelleği kullandığını ima ediyorum. "ComputeStuff () işlevi her zaman doğru çalışıyor mu (önbelleğin durumuna bağlı olarak)" sorusudur. Sonuç olarak, doğrudan ayarlama yapamıyorsanız, computeStuff () uygulamasının, TÜM Olası KOLTUK DEVLETLERİ İÇİN istediğinizi yaptığını onaylamak zordur. "cacheOfExpensiveComputation = buildBigCache ();" satırını kodladığınız için önbelleği oluşturun (önbellekten doğrudan kurucu aracılığıyla geçmek yerine)
İvan

0

'Birim Testleri' ile neyin kastedildiğine bağlı olarak, gerçekten düşük seviyeli Birim testlerinin iyi tasarımları biraz daha yüksek seviyeli entegrasyon testleri kadar kolaylaştırdığını sanmıyorum - bir grup aktörün (sınıflar, fonksiyonlar, ne olursa olsun) olduğunu test eden testler. Kodunuz, geliştirme ekibi ile ürün sahibi arasında kararlaştırılan bir dizi arzu edilen davranış üretmek için doğru şekilde birleştirilir.

Bu seviyelerde testler yazabiliyorsanız, sizi çılgınca bağımlılıklar gerektirmeyen hoş, mantıksal, API benzeri bir kod oluşturmaya itiyor - basit bir test kurulumuna sahip olma arzusu sizi doğal olarak pek çok şeye sahip olmaya zorlayacaktır. çılgın bağımlılıklar veya sıkıca bağlı kod.

Yine de hata yapmayın - Birim testleri sizi kötü tasarıma ve iyi tasarıma yönlendirebilir. Geliştiricilerin güzel bir mantıksal tasarıma ve tek bir endişeye sahip olan bir parça kod aldıklarını ve bunları ayırdıklarını ve yalnızca test amacıyla daha fazla arabirim sunduğunu ve sonuç olarak kodu daha az okunabilir ve değiştirmeyi zorlaştırdıklarını gördüm. Ayrıca, geliştiricinin çok sayıda düşük seviye birim testine sahip olmanın daha yüksek seviye testlere sahip olmaları gerekmediğine karar vermesi durumunda, muhtemelen daha fazla hataya sahip olmanız bile mümkün. En sevdiğim özel bir örnek, panodan bilgi alma ve kapatma ile ilgili çok fazla parçalanmış, 'test edilebilir' kodun olduğu bir yerde düzeltdiğim bir hatadır. Hepsi parçalanmış ve çok küçük ayrıntı seviyelerine ayrılmıştır, birçok arayüz, testlerde birçok alay ve diğer eğlenceli şeyler. Tek bir sorun var; işletim sisteminin pano mekanizmasıyla etkileşime giren herhangi bir kod yoktu.

Ünite testleri kesinlikle tasarımınızı yönlendirebilir - ancak otomatik olarak sizi iyi bir tasarıma yönlendirmez. 'Bu kod test edildi, bu yüzden test edilebilir, bu yüzden iyi' sadece ötesine geçen iyi tasarımın ne olduğu hakkında fikir sahibi olmanız gerekir.

Tabii ki, 'birim testleri', 'UI üzerinden sürülmeyen otomatik testler' anlamına gelen insanlardan biriyseniz, o zaman bu uyarıların bazıları o kadar alakalı olmayabilir - dediğim gibi, daha yüksek olduğunu düşünüyorum. seviyeli entegrasyon testleri genellikle tasarımınızı yönlendirmede daha faydalıdır.


-2

Yeni testler eski testlerin tümünü geçtiğinde , birim testleri yeniden yapılanmaya yardımcı olabilir .

Bir bubblesortu uyguladığınızı, çünkü aceleci olduğunuz ve performans konusunda endişe duymadığınız, ancak şimdi verinin uzadığı için hızlı bir bağlantı kurmak istediğinizi varsayalım. Tüm testler geçerse, işler iyi görünür.

Tabii ki bu işi yapmak için testlerin kapsamlı olması gerekiyor. Benim örneğimde, testleriniz stabiliteyi kapsamıyor olabilir çünkü bu bubblesort ile ilgilenmiyordu.


1
Bu doğrudur, ancak kod tasarım kalitesine doğrudan etki etmekten daha fazla sürdürülebilirlik kazancıdır.
Ant P,

@AntP, OP refactoring ve birim testleri hakkında sorular sordu.
om

1
Yeniden yapılanmadan söz edilen soru, ancak asıl soru, ünite testlerinin kod tasarımını nasıl geliştirebileceğini / doğrulayabildiği ile ilgiliydi - yeniden yapılanma işlemini kendisi kolaylaştırmaz.
Karınca P

-3

Bir projenin uzun süreli bakımını kolaylaştırmak için birim testlerinin en değerli olduğunu gördüm. Aylar sonra bir projeye geri döndüğümde ve ayrıntıların çoğunu hatırlamadığımda, testler yapmak beni bir şeyleri engellemekten alıkoyuyor.


6
Bu kesinlikle testlerin önemli bir yönüdür, ancak soruyu gerçekten cevaplamıyor (testin iyi olmasının nedeni değil, tasarımın nasıl etkilendiği).
Hulk
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.