"Düzenle-İddia Et-Hareket Et" mi olmalı?


94

Arrange-Act-Assert'in klasik test modeliyle ilgili olarak, kendimi sıklıkla Act'ten önce gelen bir karşı iddia eklerken buluyorum. Bu şekilde, geçen iddianın gerçekten eylemin sonucu olarak geçtiğini biliyorum.

Bunu, kırmızı-yeşil-yeniden düzenleyicideki kırmızıya benzer olarak düşünüyorum, ancak testlerim sırasında kırmızı çubuğu görürsem, yeşil çubuğun fark yaratan kod yazdığım anlamına geldiğini bilirim. Başarılı bir test yazarsam, herhangi bir kod onu tatmin edecektir; Benzer şekilde Arrange-Assert-Act-Assert ile ilgili olarak, eğer ilk iddiam başarısız olursa, herhangi bir Yasanın son Assert'i geçeceğini biliyorum - bu yüzden aslında Yasa hakkında hiçbir şeyi doğrulamıyordu.

Testleriniz bu kalıbı takip ediyor mu? Neden ya da neden olmasın?

Güncelleme Açıklaması: İlk iddia, esasen son iddianın tam tersidir. Arrange'ın işe yaradığı bir iddia değil; Bu, Act'in henüz işe yaramadığı bir iddia.

Yanıtlar:


121

Bu, yapılacak en yaygın şey değil, ancak yine de kendi adına sahip olacak kadar yaygın. Bu tekniğe Guard Assertion denir . Bunun ayrıntılı bir açıklamasını 490. sayfada Gerard Meszaros tarafından yazılan mükemmel xUnit Test Patterns kitabında bulabilirsiniz (şiddetle tavsiye edilir).

Normalde, bu kalıbı kendim kullanmıyorum çünkü sağlama ihtiyacı hissettiğim ön koşulu doğrulayan belirli bir test yazmayı daha doğru buluyorum. Böyle bir test, ön koşul başarısız olursa her zaman başarısız olmalıdır ve bu, diğer tüm testlere dahil edilmesine ihtiyacım olmadığı anlamına gelir. Bu, endişelerin daha iyi izole edilmesini sağlar, çünkü bir test senaryosu yalnızca bir şeyi doğrular.

Belirli bir test senaryosu için yerine getirilmesi gereken birçok ön koşul olabilir, bu nedenle birden fazla Guard Assertion'a ihtiyacınız olabilir. Bunları tüm testlerde tekrarlamak yerine, her ön koşul için bir (ve yalnızca bir) teste sahip olmak, test kodunuzu daha kolay ulaşılabilir kılar çünkü bu şekilde daha az tekrar yapacaksınız.


+1, çok iyi cevap. Son kısım özellikle önemlidir, çünkü nesneleri ayrı bir birim testi olarak koruyabileceğinizi gösterir.
murrekatt

3
Genelde bu şekilde de yaptım, ancak ön koşulları sağlamak için ayrı bir test yaptırmakla ilgili bir sorun var (özellikle değişen gereksinimleri olan büyük bir kod tabanında) - ön koşul testi zamanla değiştirilecek ve 'ana' ile senkronize olmayacak bu önkoşulları öngören test. Öyleyse, ön koşulların tamamı iyi ve yeşil olabilir, ancak bu ön koşullar artık her zaman yeşil ve iyi görünen ana testte karşılanmaz. Ancak ön koşullar ana sınavda olsaydı başarısız olurlardı. Bu problemle karşılaştınız ve bunun için güzel bir çözüm buldunuz mu?
nchaud

2
Testlerinizi çok fazla değiştirirseniz, başka problemleriniz olabilir çünkü bu, testlerinizi daha az güvenilir hale getirecektir. Değişen gereksinimler karşısında bile, kodu yalnızca ek olarak tasarlamayı düşünün .
Mark Seemann

@MarkSeemann Haklısınız, tekrarı en aza indirmemiz gerekiyor, ancak öte yandan , Arrange testinin kendisi geçse de, belirli bir test için Arrange'i etkileyebilecek birçok şey olabilir. Örneğin, Düzenleme testi için temizleme veya başka bir Testten sonra temizleme hatalıydı ve Düzenleme, Düzenleme testindeki ile aynı olmayacaktır.
Rekshino


8

İşte bir örnek.

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
    range.encompass(7);
    assertTrue(range.includes(7));
}

Sadece Range.includes()gerçeğe dönmek için yazmış olabilirim . Yapmadım, ama sahip olabileceğimi hayal edebiliyorum. Ya da başka şekillerde yanlış yazabilirdim. TDD ile gerçekten doğru yaptığımı umuyor ve bekliyorum - bu includes()sadece çalışıyor - ama belki yapmadım. Dolayısıyla, ilk iddia, ikinci iddianın gerçekten anlamlı olmasını sağlamak için bir akıl sağlığı kontrolüdür.

Kendi başına okumak, assertTrue(range.includes(7));"değiştirilen aralığın 7'yi içerdiğini iddia et" diyor. İlk iddia bağlamında okuyun, diyor ki: " encompass () 'ı çağırmanın onu 7 içermesine neden olduğunu iddia edin . Ve test ettiğimiz birim encompass olduğu için, bunun bazı (küçük) bir değeri olduğunu düşünüyorum.

Kendi cevabımı kabul ediyorum; diğerlerinin çoğu, kurulumu test etmekle ilgili olarak sorumu yanlış yorumladı. Bunun biraz farklı olduğunu düşünüyorum.


Bir örnekle geri döndüğün için teşekkürler, Carl. Peki, TDD döngüsünün kırmızı kısmında, encompass () gerçekten bir şey yapana kadar; ilk iddia anlamsızdır, yalnızca ikincinin bir kopyasıdır. Yeşilde işe yaramaya başlar. Yeniden düzenleme sırasında anlam kazanıyor. Bunu otomatik olarak yapan bir UT çerçevesine sahip olmak güzel olabilir.
filant

Range sınıfının TDD'sini varsayalım, onu kırdığınızda Range ctor'u test eden başka bir başarısız test olmayacak mı?
filant

1
@philippe: Soruyu anladığımdan emin değilim. Range yapıcısı ve includes () kendi birim testlerine sahiptir. Biraz detaylandırır mısınız lütfen?
Carl Manaster 01

İlk assertFalse (range.includes (7)) iddiasının başarısız olması için Range Constructor'da bir kusurunuz olması gerekir. Bu nedenle, Range kurucusunun testlerinin bu iddia ile aynı anda bozulup bozulmayacağını sormak istedim. Ve Yasadan sonra başka bir değer üzerinde iddia etmeye ne dersiniz : örneğin, assertFalse (range.includes (6))?
philant

1
Bana göre aralık oluşturma, includes () gibi işlevlerden önce gelir. Bu yüzden, kabul etsem de, yalnızca hatalı bir kurucu (veya hatalı bir include ()) bu ilk iddianın başarısız olmasına neden olur, kurucunun testi include () çağrısı içermez. Evet, ilk iddiaya kadar tüm işlevler zaten test edilmiştir. Ancak bu ilk olumsuz iddia bir şeyi ve bana göre yararlı bir şeyi iletiyor. Bu tür her iddia, başlangıçta yazıldığı zaman geçse bile.
Carl Manaster

7

Bir Arrange-Assert-Act-Asserttest her zaman iki teste dönüştürülebilir:

1. Arrange-Assert

ve

2. Arrange-Act-Assert

İlk test yalnızca Düzenleme aşamasında kurulmuş olanı onaylayacak ve ikinci test yalnızca Harekete Geçme aşamasında meydana gelenler için geçerli olacaktır.

Bu, başarısız Arrange-Assert-Act-Assertolanın Düzenleme mi yoksa Yasa aşaması mı olduğu konusunda daha kesin geri bildirim verme avantajına sahiptir, ancak orijinalde bunlar birleştirilir ve daha derine inmeniz ve tam olarak hangi iddianın başarısız olduğunu ve neden başarısız olup olmadığını anlamak için tam olarak incelemeniz gerekir. Başarısız olan Düzen veya Yasaydı.

Ayrıca, testinizi daha küçük bağımsız birimlere ayırdığınız için birim test etme amacını daha iyi karşılar.

Son olarak, farklı testlerde benzer Düzenleme bölümleri gördüğünüzde, bunları paylaşılan yardımcı yöntemlere çekmeye çalışmanız gerektiğini unutmayın, böylece testleriniz gelecekte daha KURU ve daha sürdürülebilir hale gelir.


3

Ben şimdi bunu yapıyorum. AAAA farklı bir tür

Arrange - setup
Act - what is being tested
Assemble - what is optionally needed to perform the assert
Assert - the actual assertions

Güncelleme testi örneği:

Arrange: 
    New object as NewObject
    Set properties of NewObject
    Save the NewObject
    Read the object as ReadObject

Act: 
    Change the ReadObject
    Save the ReadObject

Assemble: 
    Read the object as ReadUpdated

Assert: 
    Compare ReadUpdated with ReadObject properties

Bunun nedeni, ACT'nin ReadUpdated okumasını içermemesi, eylemin bir parçası olmamasıdır. Hareket sadece değişiyor ve kurtarıyor. Yani gerçekten, ARRANGE ReadUpdated for assertion, ASSEMBLE'ı assertion için çağırıyorum. Bu, ARRANGE bölümünün karıştırılmasını önlemek içindir

ASSERT yalnızca iddialar içermelidir. Bu, assert'i oluşturan ACT ve ASSERT arasında ASSEMBLE bırakır.

Son olarak, Düzenlemede başarısız olursanız, testleriniz doğru değildir çünkü bu önemsiz hataları önlemek / bulmak için başka testlere sahip olmanız gerekir . Çünkü benim sunduğum senaryo için, READ ve CREATE'i test eden başka testler de olmalı. Bir "Koruma Onayı" oluşturursanız, DRY'yi bozuyor ve bakım yaratıyor olabilirsiniz.


1

Test ettiğiniz eylemi gerçekleştirmeden önce durumu doğrulamak için bir "akıl sağlığı kontrolü" iddiasında bulunmak eski bir tekniktir. Testin beklediğimi yaptığını kendime kanıtlamak için genellikle bunları test iskelesi olarak yazıyorum ve test iskelesi ile karmaşık testleri önlemek için daha sonra kaldırıyorum. Bazen iskeleyi içeride bırakmak testin anlatı görevi görmesine yardımcı olur.


1

Bu tekniği zaten okudum - muhtemelen sizden btw - ama kullanmıyorum; çoğunlukla birim testleri için üçlü A formuna alışkın olduğum için.

Şimdi merak ediyorum ve bazı sorularım var: testinizi nasıl yazarsınız, kırmızı-yeşil-kırmızı-yeşil-yeniden düzenleme döngüsünün ardından bu iddianın başarısız olmasına neden olur musunuz yoksa daha sonra mı eklersiniz?

Bazen, belki kodu yeniden düzenledikten sonra başarısız olur musunuz? Bu sana ne anlatıyor? Belki yardımcı olduğu bir örnek paylaşabilirsiniz. Teşekkürler.


Tipik olarak, ilk önermeyi başarısız olmaya zorlamıyorum - sonuçta, bir TDD iddiasının, yöntemi yazılmadan önce yapması gerektiği gibi, başarısız olmamalıdır. Ben do ben bunu yazarken, yazmak önce adil değil sonra, testi yazma normal seyrinde. Açıkçası, başarısız olduğunu hatırlayamıyorum - belki bu zaman kaybı olduğunu gösteriyor. Bir örnek bulmaya çalışacağım ama şu anda aklımda bir tane yok. Sorularınız için teşekkürler; yardımcı oluyorlar.
Carl Manaster

1

Bunu daha önce başarısız olan bir testi araştırırken yaptım.

Hatırı sayılır derecede kafa kaşınmasından sonra, nedeninin "Düzenleme" sırasında denen yöntemlerin düzgün çalışmaması olduğunu belirledim. Test başarısızlığı yanıltıcıydı. Düzenlemeden sonra bir Assert ekledim. Bu, gerçek sorunu vurgulayan bir yerde testin başarısız olmasına neden oldu.

Sanırım testin Düzenle kısmı çok uzun ve karmaşıksa burada bir kod kokusu da var.


Küçük bir nokta: Kod kokusundan çok tasarım kokusunun çok karmaşık olduğunu düşünürdüm - bazen tasarım öyle olur ki, yalnızca karmaşık bir Düzenleme birimi test etmenize izin verir. Bundan bahsediyorum çünkü bu durum basit bir kod kokusundan daha derin bir düzeltme istiyor.
Carl Manaster

1

Genel olarak "Düzenle, Harekete Geç, İddia Et" i çok severim ve kişisel standardım olarak kullanırım. Bununla birlikte, bana yapmamı hatırlatmakta başarısız olduğu tek şey, iddialar yapıldığında düzenlediklerimi bozmaktır. Çoğu durumda, çoğu şey otomatik olarak sihirli bir şekilde çöp toplama vb. Yoluyla ortadan kalktığından, bu çok fazla sıkıntıya neden olmaz. Ancak, dış kaynaklarla bağlantı kurduysanız, işiniz bittiğinde muhtemelen bu bağlantıları kapatmak isteyeceksiniz. iddialarınızla ya da birçoğunuz, başka birine verebilmesi gereken bağlantılara veya hayati kaynaklara sahip bir sunucuya veya pahalı bir kaynağa sahip. Bu, özellikle TearDown veya TestFixtureTearDown kullanmayan geliştiricilerden biriyseniz önemlidir.bir veya daha fazla testten sonra temizlemek için. Elbette, açtığım şeyi kapatmamamdan "Düzenle, Harekete Geç, Onaylama" sorumlu değildir; Ben sadece bu "aldım" dan bahsediyorum çünkü henüz tavsiye etmek için "imha etmek" ile eşanlamlı iyi bir "A-kelime" bulamadım! Herhangi bir öneri?


1
@carlmanaster, aslında benim için yeterince yakınsın! Bunu boyut için denemek için bir sonraki TestFixture'uma yapıştırıyorum. Tıpkı annenizin size öğretmesi gereken şeyi yapmanız gerektiğini hatırlatan o küçük hatırlatma gibi: "Açarsanız kapatın! Eğer mahvederseniz, temizleyin!" Belki başka biri geliştirebilir ama en azından "a!" İle başlar. Önerin için teşekkürler!
John Tobler

1
@carlmanaster, "Annul" u denedim. "Sökmek" ten daha iyi ve işe yarıyor, ama yine de kafamda "Düzenle, Harekete Geç, İddia Et" kadar mükemmel bir şekilde kalan başka bir "A" kelimesini arıyorum. Belki "Yok etmek mi ?!"
John Tobler

1
Şimdi, "Düzenle, Varsayım, Harekete Geç, İddia Et, Yok Et" var. Hmmm! İşleri çok karmaşık hale getiriyorum, ha? Belki de KISS'i bırakıp "Düzenle, Harekete Geç ve İddia Et!" E geri dönsem daha iyi olur.
John Tobler

1
Sıfırlamak için bir R kullanabilir miyim? Bunun bir A olmadığını biliyorum, ama bir korsanın "Aaargh! ve Assert ile tekerlemeleri sıfırla: o
Marcel Valdez Orozco

1

Wikipedia'nın Design by Contract girişine bir göz atın . Arrange-Act-Assert kutsal üçlüsü, aynı kavramlardan bazılarını kodlama girişimidir ve programın doğruluğunu kanıtlamakla ilgilidir. Makaleden:

The notion of a contract extends down to the method/procedure level; the
contract for each method will normally contain the following pieces of
information:

    Acceptable and unacceptable input values or types, and their meanings
    Return values or types, and their meanings
    Error and exception condition values or types that can occur, and their meanings
    Side effects
    Preconditions
    Postconditions
    Invariants
    (more rarely) Performance guarantees, e.g. for time or space used

Bunu kurmak için harcanan çaba ile kattığı değer arasında bir değiş tokuş var. AAA, gereken minimum adımlar için faydalı bir hatırlatmadır, ancak kimseyi ek adımlar oluşturmaktan caydırmamalıdır.


0

Test ortamınıza / dilinize bağlıdır, ancak genellikle Arrange kısmındaki bir şey başarısız olursa, bir istisna atılır ve test, Act kısmını başlatmak yerine göstermeyi başaramaz. Yani hayır, genellikle ikinci bir Assert bölümü kullanmıyorum.

Ayrıca, Arrange parçanızın oldukça karmaşık olması ve her zaman bir istisna oluşturmaması durumunda, onu bir yöntemin içine sarmayı ve bunun için kendi testini yazmayı düşünebilirsiniz, böylece başarısız olmayacağından emin olabilirsiniz (olmadan) bir istisna atmak).


0

Bu kalıbı kullanmıyorum çünkü şöyle bir şey yapmayı düşünüyorum:

Arrange
Assert-Not
Act
Assert

Anlamsız olabilir, çünkü sözde Arrange parçanızın doğru çalıştığını biliyorsunuz, bu da Arrange bölümündeki her şeyin aynı şekilde test edilmesi veya testlere ihtiyaç duymayacak kadar basit olması gerektiği anlamına gelir.

Cevabınızın örneğini kullanarak:

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7)); // <-- Pointless and against DRY if there 
                                    // are unit tests for Range(int, int)
    range.encompass(7);
    assertTrue(range.includes(7));
}

Korkarım sorumu gerçekten anlamıyorsun. İlk iddia, Arrange'ı test etmekle ilgili değildir; en sonunda devletin ileri sürülmesine neden olan şeyin Yasa olmasını sağlamaktır.
Carl Manaster

Ve benim açımdan, Assert-Not kısmına ne koyarsanız koyun, Düzenleme kısmında zaten ima edilmiştir, çünkü Düzenleme kısmındaki kod kapsamlı bir şekilde test edilmiştir ve siz zaten ne yaptığını biliyorsunuz.
Marcel Valdez Orozco

Ama Assert-Not bölümünde bir değer olduğuna inanıyorum, çünkü şunu söylüyorsunuz: Arrange bölümü 'dünyayı' 'bu durumda' bıraktığı için, o zaman benim 'Eylemim' bu 'yeni durumda' 'dünyayı' terk edecektir. ; ve Düzenleme kısmının bağlı olduğu kodun uygulanması değişirse, o zaman test de başarısız olur. Ancak yine, bu DRY'ye aykırı olabilir, çünkü Düzenleme bölümünde bağlı olduğunuz kod için testlere de sahip olmalısınız (gerekir).
Marcel Valdez Orozco

Belki aynı proje üzerinde çalışan birkaç ekibin (veya büyük bir ekibin) olduğu projelerde, böyle bir madde oldukça faydalı olabilir, aksi takdirde gereksiz ve gereksiz buluyorum.
Marcel Valdez Orozco

Muhtemelen böyle bir madde Entegrasyon testlerinde, Sistem Testlerinde veya Kabul Testlerinde daha iyi olacaktır; burada Düzenleme bölümü genellikle birden fazla bileşene bağlıdır ve 'dünyanın' başlangıç ​​durumunun beklenmedik şekilde değişmesine neden olabilecek daha fazla faktör vardır. Ancak Birim testlerinde buna yer göremiyorum.
Marcel Valdez Orozco

0

Örnekteki her şeyi gerçekten test etmek istiyorsanız, daha fazla test deneyin ... örneğin:

public void testIncludes7() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
}

public void testIncludes5() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(5));
}

public void testIncludes0() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(0));
}

public void testEncompassInc7() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(7));
}

public void testEncompassInc5() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(5));
}

public void testEncompassInc0() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(0));
}

Aksi takdirde, hata için çok fazla olasılığı kaçırırsınız ... ör. Kapsamdan sonra, aralık yalnızca 7'yi içerir, vb ... Ayrıca aralık uzunluğu için testler de vardır (rastgele bir değeri kapsamadığından emin olmak için) ve Tamamen aralıkta 5'i kapsamaya çalışmak için başka bir test dizisi ... ne bekleriz - kapsamda bir istisna mı yoksa aralık değiştirilmeyecek mi?

Her neyse, mesele şu ki, test etmek istediğiniz eylemde herhangi bir varsayım varsa, bunları kendi testlerine koyun, evet?


0

Kullanırım:

1. Setup
2. Act
3. Assert 
4. Teardown

Çünkü temiz bir kurulum çok önemli.

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.