Yenileme yaparken birim testlerinizi nasıl sürdürüyorsunuz?


29

Başka bir soruda, TDD'ye bağlı ağrılardan birinin, test odasının yeniden yapılanma sırasında ve sonrasında kod tabanı ile senkronize kaldığı ortaya çıktı.

Şimdi, yeniden yapılanmanın büyük bir hayranıyım. TDD yapmaktan vazgeçmeyeceğim. Ancak, küçük yeniden düzenleme işlemlerinin birçok test hatasına neden olacağı şekilde yazılmış testlerin problemlerini de yaşadım.

Yeniden aktive olurken testleri çiğnemekten nasıl kaçınırsınız?

  • Testleri 'daha iyi' yazıyor musunuz? Eğer öyleyse, ne aramalısınız?
  • Bazı yeniden yapılandırma türlerinden kaçınıyor musunuz?
  • Test yeniden düzenleme araçları var mı?

Düzenleme: Ne sormak istediğimi soran yeni bir soru yazdım (ancak bunu ilginç bir değişken olarak tuttu).


7
TDD ile yeniden yapılanmada ilk adımınızın başarısız olan bir test yazmak ve daha sonra çalışmasını sağlamak için kodu yeniden yazmak olduğunu düşünürdüm.
Matt Ellen

IDE'niz de testleri nasıl yeniden düzenleyeceğinizi çözemiyor mu?

@ Thorbjørn Ravn Andersen, evet ve sormak istediğim soruyu soran yeni bir soru yazdım (ama bunu ilginç bir değişken olarak tuttu; azheglov'un cevabını görün, aslında ne söylediğini)
Alex Feinman

Bu soruya thar Bilgisi eklediniz mi?

Yanıtlar:


35

Yapmaya çalıştığın şey gerçekten yeniden yönlendirme değil. Yeniden yapılanma ile, tanım gereği, yazılımınızın ne yaptığını değiştirmez, nasıl çalıştığını değiştirirsiniz .

Tüm yeşil testlerle başlayın (tüm geçişler), sonra "kaputun altında" değişiklikler yapın (ör. Türetilmiş bir sınıftan tabana bir yöntemi taşıma, bir yöntemi çıkarma veya bir Kompozit'i Oluşturucu ile kapsülleme vb.). Testlerin hala geçmeli.

Tanımladığınız şey yeniden yapılanma değil, aynı zamanda test edilen yazılımın işlevselliğini de artıran bir yeniden tasarım gibi görünüyor. TDD ve yeniden düzenleme (burada tanımlamaya çalıştığım gibi) çatışma içinde değil. Yine de “yeşil-yeşil” refactor ve "delta" işlevselliğini geliştirmek için TDD (kırmızı-yeşil) uygulayabilirsiniz.


7
Aynı kod X 15 yere kopyaladı. Her yerde özelleştirilmiş. Ortak bir kütüphane haline getirir ve X'i parametreleştirir veya bu farklılıklara izin vermek için strateji desenini kullanırsınız. X için birim testlerinin başarısız olacağını garanti ediyorum. X'in istemcileri, genel arayüz biraz değiştiği için başarısız olacaktır. Yeniden dizayn mı yoksa refactor mu? Ben buna refactor diyorum ama her iki şekilde de her şeyi kırar. Sonuç olarak, hepsinin nasıl bir araya geldiğini tam olarak bilmiyorsanız, yeniden toplanamazsınız. O zaman testleri düzeltmek sıkıcı ama sonuçta önemsiz.
Kevin,

3
Testlerin sürekli ayarlanması gerekiyorsa, muhtemelen çok detaylı testlere sahip olmaktan bir ipucu. Örneğin, bir kod parçasının belirli koşullar altında, belirli bir düzende olmadan A, B ve C olaylarını tetiklemesi gerektiğini varsayalım. Eski kod ABC'ye göre yapar ve testler olayları bu sıraya göre bekler. Eğer refactored kodu ACB için olayları dağıtırsa, yine de spesifikasyona göre çalışır ancak test başarısız olur.
otto

3
@Kevin: Tanımladığınız şeyin yeniden tasarlandığına inanıyorum, çünkü ortak arayüz değişiyor. Fowler'in yeniden yapılanma tanımlaması ("dış davranışını değiştirmeden kodun iç yapısını değiştirme") bu konuda oldukça açık.
azheglov

3
@azheglov: belki ama benim tecrübeme göre uygulama kötü ise arayüz de öyle
Kevin

2
Tamamen geçerli ve net bir soru “kelimenin anlamı” tartışmasında son bulur. Buna nasıl isim verdiğin kimin umrunda, hadi bu tartışmayı başka bir yerde yapalım. Bu arada, bu cevap, herhangi bir gerçek cevabı tamamen göz ardı ediyor, fakat bugüne kadar hala en fazla oyu alıyor. İnsanların TDD'yi neden din olarak gördüklerini anlıyorum.
Dirk Boer

21

Ünite testlerine sahip olmanın avantajlarından biri, güvenle refactor yapabilmenizdir.

Yeniden düzenleme, genel arayüzü değiştirmezse, birim testlerini olduğu gibi bırakır ve yeniden düzenleme işleminden sonra hepsinin geçmesini sağlarsınız.

Yeniden düzenleme, genel arayüzü değiştirirse, önce testler yeniden yazılmalıdır. Yeni testler geçene kadar refactor.

Yeniden yapılanmalardan asla kaçınmam, çünkü testleri bozuyor. Birim testlerinin yazılması popodaki bir ağrı olabilir ama uzun vadede bu acıya değecektir.


7

Diğer cevaplar aksine, test bazı yolları o notta önemlidir edebilirsiniz testi (SUT) kapsamında sistem refactored zaman, kırılgan hale eğer deney whitebox olduğunu.

Alaycılara çağrılan yöntemlerin sırasını doğrulayan alaycı bir çerçeve kullanıyorum (çağrılar yan etki olmadığından sipariş sırasız olduğunda); o zaman kodum bu yöntemle daha temizse farklı bir sıraya göre çağırır ve yeniden yönlendiririm, o zaman testim sona erer. Genel olarak, alaylar testlere kırılganlık getirebilir.

SUT'umun iç durumunu özel veya korumalı üyelerini açığa çıkararak kontrol ediyorsam ("temel" işlevini görsel temelde kullanabiliriz veya "iç" erişim düzeyini yükseltebilir ve c # 'da "internalsvisibleto" kullanabiliriz; c # a " teste özgü bir alt sınıf " kullanılabilir) daha sonra aniden sınıfın iç durumu önemli olacaktır - sınıfı kara kutu olarak yeniden değerlendiriyor olabilirsiniz, ancak beyaz kutu testleri başarısız olacak. Tek bir alanın, SUT durumu değiştiğinde farklı şeyler ifade etmek için tekrar kullanıldığını varsayalım (iyi uygulama değil!) - kırılmış testleri yeniden yazmamız gerekebilir.

Teste özgü alt sınıflar, korunan yöntemleri test etmek için de kullanılabilir - bu, üretim kodu bakış açısına göre bir refraktörün test kodu bakış açısından bir değişiklik olduğu anlamına gelebilir. Birkaç çizgiyi korumalı bir yöntemin içine veya dışına taşımak, üretim yan etkileri göstermeyebilir, ancak bir testi bozabilir.

" Test kancaları " veya başka bir teste özgü veya koşullu derleme kodu kullanırsam, iç mantığa kırılgan bağımlılıklar nedeniyle testlerin bozulmamasını sağlamak zor olabilir.

Bu nedenle, testlerin SUT'un iç detaylarına birleştirilmesini önlemek için aşağıdakilere yardımcı olabilir:

Yukarıdaki hususların tümü testlerde kullanılan beyaz kutu kuplajına örnektir. Bu nedenle, kırılma testlerini yeniden düzenlemekten tamamen kaçınmak için SUT'un kara kutu testini kullanın.

Feragatname: Burada yeniden yapılanmayı tartışmak amacıyla, iç uygulamalarda görünür dış etki olmadan değişiklik yapılmasını dahil etmek için bu sözcüğü biraz daha geniş bir şekilde kullanıyorum. Bazı safhalar, yalnızca, Martin Fowler ve Kent Beck'in Atomik Yeniden İşleme operasyonlarını tanımlayan Refactoring adlı kitabına atıfta bulunabilir ve bunlara atıfta bulunabilir.

Uygulamada, burada açıklanan atomik işlemlerden biraz daha büyük olmayan adımlar atma eğilimindeyiz ve özellikle üretim kodunu dışarıdan aynı şekilde bırakan değişiklikler testlerden geçmeyebilir. Ancak bir refaktör olarak "başka davranış algoritmasıyla aynı algoritmaya sahip başka bir algoritma yerine algoritmayı dahil etmenin" adil olduğunu düşünüyorum. Martin Fowler’in kendisi, yeniden yapılanmanın testleri kırabileceğini söylüyor:

Bir alaycı testi yazdığınızda, tedarikçileri ile doğru şekilde konuşmasını sağlamak için SUT'un giden çağrılarını test ediyorsunuz. Klasik bir test yalnızca son durumu önemser - bu durumun nasıl türetildiği değil. Mockist testler bu nedenle bir yöntemin uygulanmasına daha fazla bağlanmıştır. Çağrılarınızı ortak çalışanlara göre değiştirmek genellikle alaycı testlerin kırılmasına neden olur.

[...]

Uygulamaya bağlanma, yeniden yapılanmaya da müdahale eder, çünkü uygulama değişikliklerinin testleri klasik testlerden daha fazla kırması daha muhtemeldir.

Fowler - Mocks taslak değil


Fowler kelimenin tam anlamıyla Refactoring kitabını yazdı; ve Birim testiyle ilgili en yetkili kitap (Gerard Meszaros'un xUnit Test Patterns) Fowler'in "imzası" serisindedir, bu yüzden yeniden düzenlemenin bir testi kırabileceğini söylediğinde, muhtemelen haklıdır.
mükemmeliyetçi

5

Yeniden yapılanma sırasında testleriniz bozulursa, tanım olarak, “programınızın davranışını değiştirmeden programınızın yapısını değiştiren” yeniden yapılanma yapmazsınız.

Bazen testlerinizin davranışını değiştirmeniz gerekebilir. Belki iki yöntemi bir araya getirmeniz gerekir (dinleyen bir TCP soket sınıfında bind () ve listen () diyorsunuz), kodunuzun şimdi değiştirilen API'yi kullanmaya çalışmakta ve başarısız olmasına neden oluyor. Ama bu yeniden canlandırıcı değil!


Ya sadece testler tarafından test edilen bir yöntemin adını değiştirirse? Siz de onları testlerde yeniden adlandırmazsanız, testler başarısız olur. Burada programın davranışını değiştirmiyor.
Oscar Mederos

2
Bu durumda yaptığı sınavlar da yeniden inceleniyor. Ancak dikkatli olmanız gerekir: önce yöntemi yeniden adlandırın, sonra sınamayı çalıştırın. Doğru nedenlerden dolayı başarısız olmalı (derleyemez (C #), bir MessageNotUnderstood istisnası (Smalltalk) alırsınız, hiçbir şey olmuyor gibi görünüyor (Objective-C'nin boş yeme şekli). Ardından, yanlışlıkla herhangi bir hataya neden olmadığınızı bilerek testinizi değiştirirsiniz. "Testleriniz bozulursa", "yeniden yapılanmayı tamamladıktan sonra testleriniz kesilirse" anlamına gelir. Değişiklik parçalarını küçük tutmaya çalışın!
Frank Shearar

1
Birim Testleri doğal olarak kodun yapısına bağlanır. Örneğin, Fowler, refactoring.com/catalog'da birim testlerini etkileyebilecek pek çok şeye sahiptir (örneğin, gizleme yöntemi, satır içi yöntemi, hata kodunu istisna ile değiştir vb.).
Kristian H,

yanlış. İki yöntemi bir araya getirmek açık bir şekilde resmi adlara sahip bir yeniden düzenlemedir (örneğin, satır içi yöntemi tanımlamaya uyan) ve satır içi bir yöntemin testlerini kıracak - bazı test durumlarının şimdi başka yollarla yeniden yazılması / test edilmesi gerekir. Ünite testlerini kırmak için bir programın davranışını değiştirmek zorunda değilim, tek yapmam gereken, ünite testlerini kendileriyle birleştiren iç kısımları yeniden yapılandırmak. Bir programın davranışı değişmediği sürece, bu yine de yeniden yapılanma tanımına uyar.
KolA

Yukarıdakileri iyi yazılmış testler varsayarak yazdım: eğer uygulamanızı test ediyorsanız - eğer testin yapısı test edilen kodun içini yansıtıyorsa, elbette. Bu durumda, birimin sözleşmesini test edin, uygulamayı değil.
Frank Shearar

4

Bence bu sorunun sorunu, farklı insanların farklı şekilde 'yeniden düzenleme' kelimesini kullanmasıdır. Bence muhtemelen kastettiğin birkaç şeyi dikkatlice tanımlamak en iyisidir:

>  Keep the API the same, but change how the API is implemented internally
>  Change the API

Önceden belirtilmiş bir kişinin daha önce belirttiği gibi, API'yi aynı tutuyorsanız ve tüm regresyon testleriniz genel API'de çalışırsa, hiçbir problem yaşamayacaksınız. Yeniden kaplama, hiç sorun yaratmamalı. Herhangi bir başarısız test EITHER, eski kodunuzun bir hata olduğu ve testinizin kötü olduğu veya yeni kodunuzun bir hata olduğu anlamına gelir.

Ama bu oldukça açık. Yani, PROBABLY, yeniden düzenleme yaparak, API'yi değiştirdiğinizi kastediyorsunuz.

Öyleyse buna nasıl yaklaşacağımı cevaplayayım!

  • İlk olarak, YENİ API ​​davranışınızın olmasını istediğiniz şeyi yapan YENİ bir API oluşturun. Eğer bu yeni API, OLDER API ile aynı isme sahipse, yeni API adına _NEW adını eklerim.

    int DoSomethingInterestingAPI ();

dönüşür:

int DoSomethingInterestingAPI_NEW( int takes_more_arguments );
int DoSomethingInterestingAPI_OLD();
int DoSomethingInterestingAPI() { DoSomethingInterestingAPI_NEW (whatever_default_mimics_the_old_API);

Tamam - bu aşamada - tüm regresyon testleriniz eşik geçişi - DoSomethingInterestingAPI () adını kullanarak.

SONRAKİ, kodunuzu gözden geçirin ve tüm çağrıları DoSomethingInterestingAPI () 'ye uygun DoSomethingInterestingAPI_NEW () varyantına değiştirin. Bu, yeni API'yi kullanmak için regresyon testlerinizin hangi bölümlerinin değiştirilmesi gerektiğine dair güncelleme / yeniden yazma içerir.

NEXT, DoSomethingInterestingAPI_OLD () [[kullanımdan kaldırıldı ()]] olarak işaretleyin. Kullanımdan kaldırılan API'yi istediğiniz sürece saklayın (güvenebileceğiniz tüm kodları güvenle güncelleyene kadar).

Bu yaklaşımla, regresyon testlerinizdeki herhangi bir başarısızlık basitçe o regresyon testindeki hatalardır veya kodunuzdaki hataları tam olarak istediğiniz gibi tanımlayabilirsiniz. Açıkça API'nın _NEW ve _OLD sürümlerini oluşturarak bir API revize etme aşaması, bir süredir bir arada bulunan yeni ve eski kod parçalarını almanıza izin verir.


Bu cevabı seviyorum, çünkü SUT'a yapılan Birim Testlerinin, yayınlanan bir Api'ye verilen harici istemcilerle aynı olduğu açıkça ortaya çıkıyor. Reçete yazdığınız şey, bağımlılık cehenneminden kaçınmak için yayınlanmış kütüphane / bileşeni yönetmek için SemVer protokolüne çok benzer. Ancak bu, zaman ve esneklikle gelir, bu yaklaşımı her mikro birimin ortak arayüzüne ekstrapolasyon yapmak, ekstrapolasyon maliyetleri anlamına gelir. Daha esnek bir yaklaşım, testleri uygulamadan mümkün olduğu kadar ayırmaktır; örneğin, test girişlerini ve çıkışlarını tanımlamak için entegrasyon testi veya ayrı bir DSL
KolA

1

Birim testlerinizin "aptal" diyeceğim bir ayrıntıya sahip olduğunu farz ediyorum :) yani, her sınıfın ve işlevin mutlak önem derecesini test ediyorlar. Kod üretme araçlarından uzak durun ve daha büyük bir yüzeye uygulanan testleri yazın; daha sonra, uygulamalarınızın arabirimlerinin değişmediğini ve testlerinizin hala çalıştığını bilerek, iç kısımları istediğiniz kadar yeniden değerlendirebilirsiniz.

Her bir yöntemi test eden ünite testleri yaptırmak istiyorsanız, aynı anda yeniden taramayı beklemelisiniz.


1
Aslında soruyu ele alan en faydalı cevap - test kapsamınızı titiz bir iç trivia temeli üzerine kurmayın ya da sürekli dağılmasını beklemeyin - ancak çoğu kişi TDD'nin tam tersini yapmayı öngördüğü için, aşağı oy aldı. Aşırı sinirli bir yaklaşımla ilgili uygunsuz gerçeği belirtmek için aldığınız şey budur.
KolA

1

refactoring işlemi sırasında ve sonrasında test takımının kod temeli ile senkronize tutulması

Bunu zorlaştıran şey eşleşmektir . Herhangi bir test, uygulama ayrıntılarına bir dereceye kadar bağlanma ile gelir, ancak birim testleri (TDD'nin olup olmadığına bakılmaksızın), özellikle iç kısımları engellediği için kötüdür: daha fazla birim testi, birimlere bağlanan daha fazla koda eşittir, yani yöntem imzaları / herhangi bir diğer kamu arayüzü Birimlerin - en azından.

Tanım olarak "Birimler", düşük seviye uygulama ayrıntılarıdır, birimlerin arayüzü, sistem ilerledikçe değişebilir / ayrılabilir / birleşebilir ve değişebilir. Birim testlerin çokluğu bu evrimi, yardım ettiğinden daha fazla engelleyebilir.

Refactoring yaparken testleri kesmekten nasıl kaçınılır? Kuplajdan kaçının. Uygulamada, mümkün olduğu kadar çok birim testten kaçınmak ve uygulama ayrıntılarının daha agnostik olarak daha yüksek seviye / entegrasyon testlerini tercih etmek anlamına gelir. Unutmayın, gümüş mermi olmamasına rağmen, testler hala belli bir seviyedeki bir şeye birleştirmek zorundadır, ancak ideal olarak Semantik Versiyon kullanarak açık bir şekilde versiyonlanmış bir arayüz olmalıdır, yani genellikle yayınlanmış api / uygulama seviyesinde Çözümünüzdeki her ünite için).


0

Testleriniz, gereklilik yerine uygulama için çok sıkı bir şekilde birleştirilmiştir.

Testlerinizi aşağıdaki gibi yorumlarla yazmayı düşünün:

//given something
...test code...
//and something else
...test code...
//when something happens
...test code...
//then the state should be...
...test code...

bu şekilde testlerin anlamını değiştiremezsiniz.

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.