Ne zaman alay etmeliyim?


138

Sahte ve sahte nesneler hakkında temel bir anlayışa sahibim, ancak alaycı ne zaman / nerede kullanacağım hakkında bir his bulduğumdan emin değilim - özellikle bu senaryoya uygulanacağı gibi. burada .


Yalnızca işlem dışı bağımlılıkları ve bunların etkileşimlerini harici olarak gözlemlenebilenleri (SMTP sunucusu, ileti yolu, vb.) Taklit etmenizi öneririm. Veritabanıyla alay etme, bu bir uygulama detayı. Burada daha fazlası: enterprisecraftsmanship.com/posts/when-to-mock
Vladimir

Yanıtlar:


122

Birim testi, tek bir kod yolunu tek bir yöntemle test etmelidir. Bir yöntemin yürütülmesi o yöntemin dışına, başka bir nesneye geçtiğinde ve tekrar döndüğünde, bir bağımlılığınız olur.

Bu kod yolunu gerçek bağımlılıkla test ettiğinizde birim testi yapmazsınız; entegrasyon testi yapıyorsunuz. Bu iyi ve gerekli olsa da, birim test değildir.

Bağımlılığınız hatalıysa, testiniz yanlış bir pozitif geri dönecek şekilde etkilenebilir. Örneğin, bağımlılığı beklenmeyen bir null iletebilir ve bağımlılık, belgelendiği gibi null değerini alamayabilir. Testiniz olması gerektiği gibi bir boş argüman istisnası oluşturmaz ve test geçer.

Ayrıca, bağımlı nesnenin bir test sırasında tam olarak ne istediğinizi geri döndürmesini sağlamak imkansız değilse de zor olabilir. Bu aynı zamanda testlere beklenen istisnaları atmayı da içerir.

Bir alay, bu bağımlılığın yerini alır. Bağımlı nesneye yapılan aramalarda beklentileri, istisna işleme kodunuzu test edebilmeniz için, istediğiniz testi gerçekleştirmeniz için gereken dönüş değerlerini ve / veya hangi istisnaları atacağınızı belirleyebilirsiniz. Bu şekilde söz konusu üniteyi kolayca test edebilirsiniz.

TL; DR: Birim testinizin dokunduğu her bağımlılığı alay edin.


164
Bu cevap çok radikal. Birim testleri, hepsi aynı birleşik birime ait olduğu sürece, tek bir yöntemden daha fazlasını yapabilir ve uygulamalıdır. Aksi takdirde, çok fazla alay / taklit gerekir, bu da karmaşık ve kırılgan testlere yol açar. Sadece gerçekten test edilen birime ait olmayan bağımlılıklar alay yoluyla değiştirilmelidir.
Rogério

10
Bu cevap da çok iyimser. @ Jan'ın sahte nesnelerin eksikliklerini içermesi daha iyi olurdu.
Jeff Axelrod

1
Bu, özellikle alaylardan ziyade testler için bağımlılıklar enjekte etmek için bir argüman değil mi? Yanıtınızda "sahte" ifadeyi "saplama" ile değiştirebilirsiniz. Önemli bağımlılıkları alay etmeli veya saplamalısınız. Ben temelde alaycı nesnelerin bölümlerini yeniden uygulamak sona eren bir sürü sahte kod gördüm; alaylar kesinlikle gümüş bir kurşun değildir.
Draemon

2
Birim testinizin dokunduğu her bağımlılık ile alay edin. Bu her şeyi açıklıyor.
Teoman shipahi

2
TL; DR: Birim testinizin dokunduğu her bağımlılığı alay edin. - Bu gerçekten harika bir yaklaşım değil, mockito'nun kendisi - her şeyi alay etmeyin. (downvoted)
p_champ

167

Sahte nesneler, test altındaki bir sınıf ile belirli bir arabirim arasındaki etkileşimleri test etmek istediğinizde yararlıdır .

Örneğin, bu yöntemi tam olarak bir kez sendInvitations(MailServer mailServer)çağırmak ve MailServer.createMessage()tam olarak bir kez çağırmak istiyoruz MailServer.sendMessage(m)ve MailServerarabirimde başka hiçbir yöntem çağrılmıyor . Bu, sahte nesneleri kullanabileceğimiz zamandır.

Sahte nesnelerle, gerçek MailServerImplveya bir testi TestMailServergeçmek yerine, MailServerarayüzün sahte bir uygulamasını geçebiliriz . Bir taklidi geçmeden önceMailServer , onu "eğitiriz", böylece hangi yöntemin beklenmesi gerektiğini ve hangi dönüş değerlerinin döndürüleceğini bilir. Sonunda, sahte nesne, beklenen tüm yöntemlerin beklendiği gibi çağrıldığını iddia eder.

Bu teoride kulağa hoş geliyor, ancak bazı dezavantajları da var.

Sahte eksiklikler

Bir sahte çerçeveniz varsa , sınama altındaki sınıfa her arabirim iletmeniz gerektiğinde sahte nesneyi kullanmanız caziptir . Bu şekilde , gerekli olmadığında bile etkileşimleri test edersiniz . Ne yazık ki, etkileşimlerin istenmeyen (yanlışlıkla) test edilmesi kötüdür, çünkü o zaman uygulamanın gerekli sonucu üretmesi yerine belirli bir gereksinimin belirli bir şekilde uygulandığını test ediyorsunuz.

İşte sözde kodda bir örnek. Bir MySortersınıf oluşturduğumuzu ve bunu test etmek istediğimizi varsayalım :

// the correct way of testing
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert testList equals [1, 2, 3, 7, 8]
}


// incorrect, testing implementation
testSort() {
    testList = [1, 7, 3, 8, 2] 
    MySorter.sort(testList)

    assert that compare(1, 2) was called once 
    assert that compare(1, 3) was not called 
    assert that compare(2, 3) was called once 
    ....
}

(Bu örnekte, test etmek istediğimiz hızlı sıralama gibi belirli bir sıralama algoritması olmadığını varsayıyoruz; bu durumda, ikinci test aslında geçerli olacaktır.)

Böyle aşırı bir örnekte, ikinci örneğin neden yanlış olduğu açıktır. Uygulamasını değiştirdiğimizde, MySorterilk test, doğru şekilde sıraladığımızdan emin olmak için harika bir iş çıkarır, bu da testlerin tamamıdır - kodu güvenli bir şekilde değiştirmemize izin verir. Öte yandan, ikinci test her zaman kırılır ve aktif olarak zararlıdır; yeniden düzenlemeyi engeller.

Saplamalar gibi alay ediyor

Sahte çerçeveler genellikle daha az katı kullanıma izin verir, burada yöntemlerin kaç kez çağrılması gerektiğini ve hangi parametrelerin beklendiğini tam olarak belirtmemiz gerekmez; taslak olarak kullanılan sahte nesnelerin oluşturulmasına izin verir .

Diyelim sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)ki test etmek istediğimiz bir yöntem var. PdfFormatterNesne davetiye oluşturmak için kullanılabilir. İşte test:

testInvitations() {
   // train as stub
   pdfFormatter = create mock of PdfFormatter
   let pdfFormatter.getCanvasWidth() returns 100
   let pdfFormatter.getCanvasHeight() returns 300
   let pdfFormatter.addText(x, y, text) returns true 
   let pdfFormatter.drawLine(line) does nothing

   // train as mock
   mailServer = create mock of MailServer
   expect mailServer.sendMail() called exactly once

   // do the test
   sendInvitations(pdfFormatter, mailServer)

   assert that all pdfFormatter expectations are met
   assert that all mailServer expectations are met
}

Bu örnekte, PdfFormatternesneyi gerçekten önemsemiyoruz, bu yüzden herhangi bir çağrıyı sessizce kabul etmesi ve sendInvitation()bu noktada çağrılan tüm yöntemler için bazı makul hazır dönüş değerleri döndürmesi için eğitiyoruz . Tam olarak bu eğitim yöntemleri listesini nasıl bulduk? Testi basitçe yaptık ve test geçene kadar yöntemleri eklemeye devam ettik. Dikkat etmeliyiz ki, saplamayı neden çağırması gerektiğine dair bir ipucu olmadan bir yönteme yanıt vermek için eğittik, testin şikayet ettiği her şeyi ekledik. Mutluyuz, test geçiyor.

Ama daha sonra, daha süslü pdf'ler oluşturmak için sendInvitations()ya da sendInvitations()kullanan başka bir sınıfı değiştirdiğimizde ne olur ? Testimiz aniden başarısız oluyor çünkü artık daha fazla yöntem PdfFormatterçağrıldı ve saplamamızı onları beklemek için eğitmedik. Ve genellikle böyle durumlarda başarısız olan sadece bir test değil, sendInvitations()yöntemi doğrudan veya dolaylı olarak kullanan herhangi bir testtir . Daha fazla eğitim ekleyerek tüm bu testleri düzeltmeliyiz. Ayrıca, artık gerekli olmayan yöntemleri kaldıramadığımızı da unutmayın, çünkü hangilerinin gerekli olmadığını bilmiyoruz. Yine, yeniden düzenlemeyi engelliyor.

Ayrıca, testin okunabilirliği korkunç bir şekilde acı çekti, orada istediğimiz için yazmadığımız birçok kod var, ama yapmak zorunda olduğumuz için; bu kodu orada isteyen biz değiliz. Sahte nesneler kullanan testler çok karmaşık görünür ve genellikle okunması zordur. Testler, okuyucunun test altındaki sınıfın nasıl kullanılması gerektiğini anlamasına yardımcı olmalı, bu yüzden basit ve anlaşılır olmalıdır. Eğer okunabilir değillerse, kimse onları koruyamaz; aslında onları silmek, onları korumaktan daha kolaydır.

Bunu nasıl düzeltirim? Kolayca:

  • Mümkün olduğunda alay yerine gerçek sınıfları kullanmayı deneyin. Gerçek kullanın PdfFormatterImpl. Mümkün değilse, mümkün kılmak için gerçek sınıfları değiştirin. Bir sınıfın testlerde kullanılamaması genellikle sınıfla ilgili bazı sorunlara işaret eder. Sorunları çözmek bir kazan-kazan durumudur - sınıfı düzelttiniz ve daha basit bir testiniz var. Öte yandan, düzeltmemek ve alaycılığı kullanmak kazançsız bir durumdur - gerçek sınıfı düzeltmediniz ve daha fazla yeniden düzenleme yapılmasını engelleyen daha karmaşık, daha az okunabilir testleriniz var.
  • Arabirimin her testte alay etmek yerine basit bir test uygulaması oluşturmayı deneyin ve bu test sınıfını tüm testlerinizde kullanın. TestPdfFormatterHiçbir şey yapmayan yaratın . Bu şekilde, tüm testler için bir kez değiştirebilirsiniz ve testleriniz, saplamalarınızı eğittiğiniz uzun kurulumlarla dolmaz.

Sonuçta, sahte nesnelerin kullanımı vardır, ancak dikkatli kullanılmadıklarında, genellikle kötü uygulamaları teşvik eder, uygulama ayrıntılarını test eder, yeniden düzenlemeyi engeller ve okunması zor ve bakımı zor testler üretir .

Alayların eksiklikleri hakkında daha fazla bilgi için ayrıca bkz. Mock Objects: Eksiklikler ve Kullanım Durumları .


1
İyi düşünülmüş bir cevap ve çoğunlukla katılıyorum. Birim testleri beyaz kutu testi olduğundan, meraklı PDF'ler göndermek için uygulamayı değiştirdiğinizde testleri değiştirmek zorunda kalmanın mantıksız bir yük olmayabileceğini söyleyebilirim. Bazen alaylar, çok fazla kazan plakasına sahip olmak yerine koçanları hızlı bir şekilde uygulamak için yararlı bir yol olabilir. Ancak uygulamada, kullanımlarının bu basit vakalara geri döndürülmediği görülmektedir.
Draemon

1
alay etmenin asıl mesele, testlerinizin tutarlı olması, testinizi her çalıştırdığınızda uygulamaları muhtemelen diğer programcılar tarafından sürekli olarak değişen nesneler üzerinde alay etme konusunda endişelenmenize gerek olmaması değil mi?
PositiveGuy

1
Çok iyi ve ilgili noktalar (özellikle test kırılganlığı hakkında). Ben daha gençken alayları çok kullanıyordum, ama şimdi alaylara potansiyel olarak tek kullanımlık olarak bağlı olan ve entegrasyon testine (gerçek bileşenlerle) daha fazla odaklanan birim testini düşünüyorum
Kemoda

6
"Bir sınıfı testlerde kullanamamak genellikle sınıfla ilgili bazı sorunlara işaret eder." Sınıf bir
hizmetse

1
Ancak sendInvitations () 'i değiştirdiğimizde ne olur? Test edilen kod değiştirilirse, önceki sözleşmeyi artık garanti etmez, bu nedenle başarısız olması gerekir. Ve genellikle böyle durumlarda başarısız olan tek bir test değildir . Bu durumda, kod temiz uygulanmamıştır. Bağımlılığın yöntem çağrılarının doğrulanması sadece bir kez test edilmelidir (uygun birim testinde). Diğer tüm sınıflar yalnızca sahte örneği kullanır. Bu yüzden birim testlerle entegrasyonu karıştıran herhangi bir fayda görmüyorum.
Christopher

55

Temel kural:

Test ettiğiniz işlevin parametre olarak karmaşık bir nesneye ihtiyacı varsa ve bu nesneyi örneklemek (örneğin, bir TCP bağlantısı kurmaya çalışırsa) bir acı olur.


4

Test etmeye çalıştığınız kod birimine "tam" olması gereken bir bağımlılık olduğunda bir nesneyi alay etmelisiniz.

Örneğin, kod biriminizde bazı mantığı test etmeye çalıştığınızda, ancak başka bir nesneden bir şey almanız gerektiğinde ve bu bağımlılıktan döndürülen şey, test etmeye çalıştığınız şeyi etkileyebilir - bu nesneyi alay etmeyi.

Konuyla ilgili harika bir podcast burada bulunabilir


Bağlantı şimdi hedeflenen bölüme değil, geçerli bölüme yönlendiriliyor. Amaçlanan podcast bu hanselminutes.com/32/mock-objects mi?
C Perkins
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.