Neden yeniden düzenleyeceğim kod için testler yazmalıyım?


15

Ben büyük bir eski kod sınıfı yeniden düzenleme. Yeniden düzenleme (sanırım) bunu savunuyor:

  1. eski sınıf için test yazma
  2. halkı sınıftan ayırmak

Sorun: Sınıfı yeniden düzenlediğimde, 1. adımdaki testlerimin değiştirilmesi gerekecek. Örneğin, bir zamanlar eski bir yöntemde olan, şimdi ayrı bir sınıf olabilir. Bir yöntem neydi şimdi birkaç yöntem olabilir. Eski sınıfın tüm manzarası yeni bir şeye silinebilir ve bu nedenle 1. adımda yazdığım testler neredeyse boş ve geçersiz olacaktır. Özünde Adım 3'ü ekleyeceğim . Testlerimi bolca yeniden yaz

Refraktörden önce testler yazmanın amacı nedir? Kendime daha fazla iş yaratmanın akademik bir alıştırması gibi geliyor. Şimdi yöntem için testler yazıyorum ve bir şeyleri nasıl test edeceğimizi ve eski yöntemin nasıl çalıştığını daha fazla öğreniyorum. Bunu eski kodun kendisini okuyarak öğrenebilirsiniz, ancak testler yazmak neredeyse burnumu ovalamak ve aynı geçici bilgiyi ayrı testlerde belgelemek gibidir. Bu şekilde kodun ne yaptığını öğrenmek dışında neredeyse hiç seçeneğim yok. Burada geçici dedim, çünkü heck koddan yeniden düzenleyeceğim ve tüm belgelerim ve testlerim önemli bir kısım için boş ve geçersiz olacak, ancak bilgim kalacak ve yeniden düzenleme konusunda daha taze olmamı sağlayacak.

Kodu daha iyi anlamama yardımcı olmak için refactor'dan önce test yazmanın gerçek nedeni bu mu? Başka bir neden olmalı!

Lütfen açıkla!

Not:

Bu yazı var: Tam bir yeniden düzenleme için zaman olmadığında eski kod için testler yazmak mantıklı mı? ancak "refactor'dan önce test yazma" der, ancak "neden" veya "yazma testleri" "yakında yok edilecek yoğun çalışma" gibi görünüyorsa ne yapılacağını söylemez


1
Öncülünüz yanlış. Testlerinizi değiştirmeyeceksiniz. Yeni testler yazacaksınız. 3. Adım "geçersiz olan testleri silin" olacaktır.
pdr

1
3. adım daha sonra "Yeni testler yazın. Geçersiz testleri silin" bölümünü okuyabilir. Sanırım hala orijinal eseri yok etmek anlamına geliyor
Dennis

3
Hayır, adım 2 sırasında yeni testleri yazmak istersiniz. Evet, adım 1 imha edilir. Ama zaman kaybı mıydı? Hayır, çünkü 2. adımda hiçbir şeyi kırmamanız için size bir ton güvence verir.
pdr

3
@Dennis - Durumlarla ilgili aynı endişelerinizi paylaşırken, en yeniden düzenleme çabalarını "orijinal işi yok etmek" olarak düşünebiliriz, ancak asla yok etmediysek, hiçbir zaman 10k satırlı spagetti kodundan uzaklaşmayacağız dosya. Aynı şey muhtemelen birim testler için de geçerli, test ettikleri kodla el ele gidiyorlar. Kod geliştikçe ve işler taşındıkça ve / veya silindikçe, birim testleri de onunla birlikte gelişmelidir.
DXM

"Kodu anlamak" küçük bir avantaj değildir. Anlamadığınız bir programı yeniden düzenlemeyi nasıl beklersiniz? Bu kaçınılmaz bir programdır ve bir programın gerçek bir şekilde anlaşıldığını tam bir test yazmaktan daha iyi gösterebilir. Ayrıca, test ne kadar soyut olursa, daha sonra çizmeniz daha az olası olacaktır, bu yüzden bir şey varsa, ilk önce yüksek seviye testlere sadık kalın.
Neil

Yanıtlar:


46

Yeniden düzenleme, (harici olarak görünür) davranışı değiştirmeden bir kod parçasını temizler (örneğin stili, tasarımı veya algoritmaları geliştirmek). Emin öncesi ve üstlenmeden sonra kod emin olmak için değil testleri yazmak ise bunun yerine önce ve üstlenmeden sonra uygulama bu bir göstergesi olarak testleri yazmak, aynı şekilde davranır yeni kod uyumludur ve hiçbir yeni hata tanıtıldı: Aynı.

Birincil endişeniz, yazılımınızın genel arayüzü için birim testleri yazmak olmalıdır. Bu arayüz değiştirilmemelidir, bu nedenle testler (bu arayüz için otomatik bir kontrol olan) da değişmemelidir.

Bununla birlikte, testler hataları bulmak için de yararlıdır, bu nedenle yazılımınızın özel bölümleri için de testler yazmak mantıklı olabilir. Bu testlerin yeniden düzenleme boyunca değişmesi beklenmektedir. Bir uygulama detayını değiştirmek istiyorsanız (özel bir fonksiyonun adlandırılması gibi), önce testleri değişen beklentilerinizi yansıtacak şekilde güncelleyin, ardından testin başarısız olduğundan (beklentilerinizin karşılanmadığından) emin olun, ardından gerçek kodu değiştirin ve tüm testlerin tekrar geçtiğini kontrol edin. Hiçbir zaman genel arayüz testleri başarısız olmaya başlamamalıdır.

Daha büyük ölçekte değişiklikler yaparken, örneğin birden fazla koda bağlı parçanın yeniden tasarlanması bu daha zordur. Ancak bir tür sınır olacak ve bu sınırda testler yazabileceksiniz.


6
+1. Aklımı oku, cevabımı yazdı. Önemli nokta: Yeniden düzenleme sonrasında aynı hataların hala orada olduğunu göstermek için birim testleri yazmanız gerekebilir!
david.pfx

Soru: Neden işlev adı değiştirme örneğinizde, başarısız olduğundan emin olmak için önce testi değiştiriyorsunuz? Tabii ki bunu değiştirdiğinizde başarısız olacağını söylemek istiyorum - linkler birlikte kodu bağlamak için kullandığınız bağlantıyı kesti! Belki de yeni seçtiğiniz adla başka bir özel özel işlev olabileceğini mi düşünüyorsunuz ve bunu kaçırmanız durumunda durumun doğru olmadığını doğrulamanız gerekir mi? Görüyorum ki bu size OKB'yi sınırlayan belirli bir güvence verecek, ancak bu durumda buna benzer bir aşırıya kaçma hissi veriyor. Örneğinizdeki testin başarısız olmasının olası bir nedeni var mı?
Dennis

^ devamı: genel bir teknik olarak, yanlış giden şeyleri olabildiğince erken yakalamak için kodunuzun adım adım sağlık kontrolünü yapmanın iyi olduğunu görüyorum. Her seferinde ellerinizi yıkamazsanız hastalanmayabilirsiniz, ancak sadece bir alışkanlık olarak ellerinizi yıkamak, kontamine şeylerle temas edip etmediğinizi genel olarak daha sağlıklı tutacaktır. Burada zaman zaman ellerinizi gereksiz yere yıkayabilir veya zaman zaman gereksiz yere test edebilirsiniz, ancak sizi ve kodunuzu sağlıklı tutmaya yardımcı olur. Demek istediğin bu muydu?
Dennis

@Dennis aslında, bilinçsizce bilimsel olarak doğru bir deneyi tanımlıyordum: Bir parametreyi değiştirirken hangi parametrenin sonucu gerçekten etkilediğini söyleyemeyiz. Testlerin kod olduğunu ve her kodda hata olduğunu unutmayın. Koda dokunmadan önce testleri çalıştırmadığı için programcı cehenneme gidecek misiniz? Elbette hayır: testleri yürütmek ideal olur, bunun gerekli olup olmadığı profesyonel yargınızdır. Ayrıca, bir test derlenmediğinde başarısız olduğunu ve cevabımın sadece bağlayıcı içeren statik diller için değil, dinamik diller için de geçerli olduğunu unutmayın.
amon

2
Refactoring sırasında çeşitli hataları düzeltmekten ben test yapmadan kod hareketleri gibi kolay hareket olmazdı farkında değilim. Testler, kodumu değiştirerek tanıttığım davranışsal / fonksiyonel "diffs" uyarısı veriyor.
Dennis

7

Ah, eski sistemleri korumak.

İdeal olarak testleriniz sınıfa yalnızca kod tabanının geri kalanıyla, diğer sistemlerle ve / veya kullanıcı arabirimiyle davranır. Arabirimler. Akış yukarı veya akış aşağı bileşenleri etkilemeden arayüzü yeniden düzenleyemezsiniz. Eğer hepsi sıkıca birleşmiş bir karmaşa ise, yeniden düzenleme yerine yeniden yazma çabasını da düşünebilirsiniz, ancak büyük ölçüde anlambilimdir.

Düzenleme: Diyelim ki kodunuzun bir kısmı bir şeyi ölçer ve sadece bir değer döndüren bir işlevi vardır. Tek arabirim işlevi / yöntemi / whatnot'u çağırıyor ve döndürülen değeri alıyor. Bu gevşek bağlantıdır ve birim testi kolaydır. Ana programınızda bir arabelleği yöneten bir alt bileşen varsa ve buna yapılan tüm çağrılar arabelleğin kendisine, bazı kontrol değişkenlerine bağlıysa ve hata mesajlarını başka bir kod bölümünden geri alırsa, bunun sıkı bir şekilde bağlandığını ve birim testi zor. Yine de yeterli miktarda sahte nesne ile yapabilirsiniz ve ne olursa olsun, dağınık hale gelir. Özellikle c. Tamponun nasıl çalıştığı herhangi bir yeniden düzenleme alt bileşeni kıracaktır.
Düzenlemeyi Sonlandır

Sınıfınızı kararlı kalan arabirimler üzerinden test ediyorsanız, testleriniz yeniden düzenleme işleminden önce ve sonra geçerli olmalıdır. Bu, kırmadığınızdan güvenle değişiklik yapmanızı sağlar. En azından daha fazla güven.

Ayrıca artımlı değişiklikler yapmanıza izin verir. Bu büyük bir proje ise, hepsini yıkmak, yepyeni bir sistem kurmak ve daha sonra testler geliştirmek isteyeceğinizi düşünmüyorum. Bir bölümünü değiştirebilir, test edebilir ve değişikliğin sistemin geri kalanını azaltmadığından emin olabilirsiniz. Ya da öyleyse, bıraktığınız zaman şaşkınlık duymak yerine en azından dev karışık karışıklığın geliştiğini görebilirsiniz.

Bir yöntemi üçe bölebilirsiniz, ancak yine de bir önceki yöntemle aynı şeyi yapacaklardır, böylece eski yöntem için sınava girebilir ve üçe bölebilirsiniz. İlk testi yazma çabası boşa gitmez.

Ayrıca, eski sistem bilgisini "geçici bilgi" olarak ele almak iyi gitmeyecektir. Eski sistemler söz konusu olduğunda, bunun daha önce nasıl yapıldığını bilmek çok önemlidir. "Neden bunu yapıyor?"


Sanırım anlıyorum, ama beni arayüzlerde kaybettin. yani testler yazıyorum Şimdi test altında yöntem çağrıldıktan sonra belirli değişkenlerin düzgün yerleştirilmiş olup olmadığını kontrol edin. Bu değişkenler değiştirilir veya yeniden düzenlenirse, testlerim de öyle olacaktır. Üzerinde çalıştığım mevcut eski sınıf, değişken değişiklikler veya daha az iş yoğunluğu sağlayacak arabirimler / alıcılar / ayarlayıcılar içermiyor. Ama yine de eski kod söz konusu olduğunda arayüzlerle ne demek istediğinizden emin değilim. Belki biraz yaratabilirim? Ama bu yeniden düzenleme olacak.
Dennis

1
Evet, eğer her şeyi yapan bir tanrı sınıfınız varsa, o zaman gerçekten herhangi bir arayüz yoktur. Ancak, başka bir sınıfı çağırırsa, üst sınıf bunun belirli bir şekilde davranmasını bekler ve birim testleri bunu doğrulayabilir. Yine de, yeniden düzenleme sırasında birim testinizi güncellemek zorunda kalmayacağınızı iddia etmem.
Philip

4

Kendi cevabım / aydınlanmam:

Refactoring sırasında çeşitli hataları düzeltmekten ben test yapmadan kod hareketleri gibi kolay hareket olmazdı farkında değilim. Testler, kodumu değiştirerek tanıttığım davranışsal / fonksiyonel "diffs" uyarısı veriyor.

İyi testler uygulandığında hiper farkında olmanız gerekmez. Kodunuzu daha rahat bir şekilde düzenleyebilirsiniz. Testler sizin için doğrulama ve akıl sağlığı kontrollerini yapar.

Ayrıca, testlerim yeniden düzenlenmiş ve yok edilmediğim gibi kaldı. Aslında kodumun derinliklerine inerken testlerime iddialar eklemek için bazı ekstra fırsatlar fark ettim.

GÜNCELLEME

Şimdi, testlerimi çok değiştiriyorum: / Çünkü orijinal işlevi yeniden düzenledim (işlevi kaldırdım ve bunun yerine yeni bir temizleyici sınıf yarattım, eskiden fonksiyonun içinde olan tüyleri yeni sınıfın dışında hareket ettirdim), şimdi daha önce çalıştırdığım test altındaki kod, farklı bir sınıf adı altında farklı parametreler alır ve farklı sonuçlar üretir (kabartmalı orijinal kodun test etmek için daha fazla sonucu vardı). Bu yüzden testlerimin bu değişiklikleri yansıtması gerekiyor ve temel olarak testlerimi yeni bir şeye yeniden yazıyorum.

Yeniden yazma testlerinden kaçınmak için yapabileceğim başka çözümler olduğunu düşünüyorum. yani eski işlev adını yeni kod ve içindeki tüy ile tutun ... ama en iyi fikir olup olmadığını bilmiyorum ve ne yapacağımı karar vermek için henüz bu kadar deneyimim yok.


Yeniden düzenleme ile birlikte uygulamayı yeniden tasarladığınız gibi görünüyor.
JeffO

Ne zaman yeniden düzenleniyor ve ne zaman yeniden tasarlanıyor? yani yeniden düzenleme yaparken daha büyük hantal sınıfları daha küçük sınıflara ayırmamak ve onları hareket ettirmek zordur. Yani evet, ayrımdan tam olarak emin değilim, ama belki de ikisini de yapıyorum.
Dennis

3

Kodunuzu yaptığınız gibi sürmek için testlerinizi kullanın. Eski kodda bu, değiştireceğiniz kod için test yazma anlamına gelir. Bu şekilde ayrı bir eser değildirler. Testler, kodun nasıl yapması gerektiğinin içsel cesaretleri hakkında değil, neyin başarılması gerektiğiyle ilgili olmalıdır.

Genellikle kod olmayan testleri eklemek istersiniz) kod için kodların davranışının beklendiği gibi çalışmaya devam ettiğinden emin olmak için yeniden düzenleyeceksiniz. Bu nedenle, yeniden düzenleme sırasında sürekli olarak test paketini çalıştırmak harika bir güvenlik ağıdır. Değişikliklerin beklenmedik bir şeyi etkilemediğini doğrulamak için bir test paketi olmadan kod değiştirme düşüncesi korkutucu.

Eski testlerin güncellenmesi, yeni testlerin yazılması, eski testlerin silinmesi vb.Gibi cesurca gelince, bunu modern profesyonel yazılım geliştirme maliyetinin bir parçası olarak görüyorum.


İlk paragrafınız, 1. adımı görmezden gelmeyi ve devam ederken testler yazmayı savunuyor gibi görünüyor; ikinci paragrafınız bununla çelişiyor gibi görünüyor.
pdr

Cevabım güncellendi.
Michael Durrant

2

Özel durumunuzda yeniden düzenleme yapmanın amacı nedir?

TDD'ye (Test Odaklı Gelişim) hepimizin (bir dereceye kadar) inandığım cevabımı vermek amacıyla varsayın.

Yeniden düzenleme işleminizin amacı mevcut davranışı değiştirmeden mevcut kodu temizlemekse, yeniden düzenleme işleminden önce test yazmak, kodun davranışını değiştirmediğinizden emin olmanızdır, başarılı olursa, testler hem önce hem de sonra başarılı olacaktır. refactor.

  • Testler, yeni çalışmanızın gerçekten işe yaradığından emin olmanıza yardımcı olacaktır.

  • Testler muhtemelen de orijinal nasıl çalışır durumlarda ortaya çıkarmaya değil işi.

Ama nasıl eğer gerçekten davranışlarını etkilemeden önemli üstlenmeden yaparsınız bazı dereceye?

Yeniden düzenleme sırasında olabilecek birkaç şeyin kısa bir listesi:

  • değişkeni yeniden adlandır
  • yeniden adlandırma işlevi
  • işlev ekle
  • silme işlevi
  • işlevi iki veya daha fazla işleve bölme
  • iki veya daha fazla işlevi tek bir işlevde birleştirin
  • bölünmüş sınıf
  • sınıfları birleştir
  • sınıfı yeniden adlandır

Listelenen etkinliklerin her birinin bir şekilde davranışı değiştirdiğini iddia edeceğim .

Ve yeniden düzenleme sizin davranışınızı değiştirirse, testlerinizin hala hiçbir şeyi kırmamanızı nasıl sağlayacağınızı tartışacağım .

Belki davranış makro düzeyde değişmez, ancak birim testin amacı makro davranışını sağlamak değildir. Bu entegrasyon testi. Birim testinin amacı, ürününüzü oluşturduğunuz parçaların ve parçaların kırılmamasını sağlamaktır. Zincir, en zayıf halka vb.

Bu senaryoya ne dersiniz:

  • Varsayalım function bar()

  • function foo() aramak bar()

  • function flee() ayrıca işlev çağrısı yapar bar()

  • Sadece çeşitli, flam()bir çağrı yaparfoo()

  • Her şey muhteşem çalışıyor (en azından görünüşe göre).

  • Refactor ...

  • bar() olarak yeniden adlandırıldı barista()

  • flee() çağrı olarak değiştirildi barista()

  • foo()olduğu değil çağrıya değiştirildibarista()

Açıkçası, her ikisi için de testler foo()ve flam()şimdi başarısız.

Belki ilk etapta foo()aradığınızı fark etmediniz bar(). Kesinlikle fark etmedi flam()bağımlıydı bar()yoluyla foo().

Her neyse. Nokta sizin testler her iki yeni kırık davranışı ortaya çıkarmak olacaktır foo()ve flam(), senin üstlenmeden çalışması sırasında artımlı moda.

Testler iyi bir şekilde yeniden düzenlemenize yardımcı olur.

Testiniz olmadığı sürece.

Bu biraz tartışmalı bir örnek. Molalar bar()değişirse foo(), o foo()zaman başlamak için çok karmaşık olduğunu ve parçalanması gerektiğini iddia edenler var . Ancak prosedürler bir sebepten ötürü diğer prosedürleri çağırabilir ve tüm karmaşıklığı ortadan kaldırmak imkansızdır , değil mi? Bizim işimiz karmaşıklığı makul bir şekilde yönetmek .

Başka bir senaryo düşünün.

Bir bina inşa ediyorsunuz.

Binanın doğru bir şekilde inşa edilmesini sağlamak için bir iskele inşa ediyorsunuz.

İskele, diğer şeylerin yanı sıra bir asansör şaftı oluşturmanıza yardımcı olur. Daha sonra, iskele yırtılır, ancak asansör şaftı kalır. İskele tahrip ederek "orijinal işi" imha ettiniz.

Benzetme kıvrımlıdır, ancak asıl nokta, ürün oluşturmanıza yardımcı olacak araçlar oluşturmanın duyulmamış olmasıdır. Araçlar kalıcı olmasa bile faydalıdır (hatta gerekli). Marangozlar her zaman jig yaparlar, bazen sadece bir iş için. Sonra jigleri parçalara ayırırlar, bazen parçaları diğer işler için diğer jigleri oluşturmak için kullanırlar, bazen değil. Ama bu jigleri işe yaramaz veya boşa harcamaz.

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.