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 .
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 .
Yanıtlar:
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.
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 MailServer
arabirimde başka hiçbir yöntem çağrılmıyor . Bu, sahte nesneleri kullanabileceğimiz zamandır.
Sahte nesnelerle, gerçek MailServerImpl
veya bir testi TestMailServer
geçmek yerine, MailServer
arayü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.
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 MySorter
sı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, MySorter
ilk 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.
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. PdfFormatter
Nesne 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, PdfFormatter
nesneyi 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:
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.TestPdfFormatter
Hiç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ı .
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.
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