Birim test uygulaması mantığı ve güvensiz dil yapıları arasındaki çizgi nerededir?


87

Bunun gibi bir fonksiyon düşünün:

function savePeople(dataStore, people) {
    people.forEach(person => dataStore.savePerson(person));
}

Bu şekilde kullanılabilir:

myDataStore = new Store('some connection string', 'password');
myPeople = ['Joe', 'Maggie', 'John'];
savePeople(myDataStore, myPeople);

Bunun kendi birim testlerine sahip olduğunu veya satıcı tarafından verildiğini varsayalımStore . Her durumda, biz güveniyoruz Store. Ayrıca, hata işlemenin - örneğin veri tabanı kopma hatalarının - sorumluluğunun olmadığını varsayalım savePeople. Aslında, mağazanın kendisinin muhtemelen hiçbir şekilde hata yapamayacağı sihirli bir veritabanı olduğunu varsayalım . Bu varsayımlar göz önüne alındığında , soru şudur:

Birimce savePeople()test edilmeli mi yoksa bu tür testler yerleşik forEachdil yapısını test etmeyi mi gerektiriyor?

Elbette, her bir kişi için bir defa denen bir alay dataStoreve iddiaya girebiliriz dataStore.savePerson(). Bu tür bir testin uygulama değişikliklerine karşı güvenlik sağladığını kesinlikle iddia edebilirsiniz: örneğin, forEachgeleneksel bir fordöngü ile değiştirmeye karar verirsek , ya da başka bir yineleme yöntemi. Yani test tamamen önemsiz değil . Ve yine de çok yakın görünüyor ...


İşte daha verimli olabilecek başka bir örnek. Başka hiçbir şeyi veya işlevi koordine etmekten başka bir şey yapmayan bir işlev düşünün. Örneğin:

function bakeCookies(dough, pan, oven) {
    panWithRawCookies = pan.add(dough);
    oven.addPan(panWithRawCookies);
    oven.bakeCookies();
    oven.removePan();
}

Bunun gibi bir fonksiyon, olması gerektiğini düşündüğünüz varsayılarak, nasıl test edilmeli? Beni sadece alay etmez birim testinin her türlü hayal etmek çok zor dough, panve oven, ardından yöntemleri üzerlerinde denir iddia. Ancak böyle bir test, işlevin tam olarak uygulanmasını çoğaltmaktan başka bir şey yapmamaktadır.

Bu fonksiyonun anlamlı bir kara kutu şeklinde test edilememesi, fonksiyonun kendisiyle ilgili bir tasarım hatasını gösteriyor mu? Eğer öyleyse, nasıl geliştirilebilir?


Örneği motive eden soruya daha fazla netlik kazandırmak için bakeCookies, testler eklemeye çalışırken ve eski kodlara yeniden bakarken karşılaştığım daha gerçekçi bir senaryo ekleyeceğim.

Bir kullanıcı yeni bir hesap oluşturduğunda, bazı şeylerin olması gerekir: 1) veritabanında yeni bir kullanıcı kaydı oluşturulması gerekir 2) karşılama e-postası gönderilir 3) kullanıcının IP adresinin dolandırıcılık için kaydedilmesi gerekir amaçlar.

Bu nedenle, tüm "yeni kullanıcı" adımlarını birbirine bağlayan bir yöntem oluşturmak istiyoruz:

function createNewUser(validatedUserData, emailService, dataStore) {
  userId = dataStore.insertUserRecord(validateduserData);
  emailService.sendWelcomeEmail(validatedUserData);
  dataStore.recordIpAddress(userId, validatedUserData.ip);
}

Bu yöntemlerden herhangi biri bir hata atarsa, hatanın uygun gördüğü şekliyle işleyebilmesi için çağrının koduna kabarmasını istiyoruz. API kodu tarafından çağrılıyorsa, hatayı uygun bir http yanıt koduna çevirebilir. Bir web arayüzü tarafından aranıyorsa, hatayı kullanıcıya gösterilecek şekilde uygun bir mesaja çevirebilir. Mesele şu ki, bu fonksiyon atılabilecek hataları nasıl idare edeceğini bilmiyor.

Kafamın özü, böyle bir işlevi test etmek için testin kendisinde tam olarak uygulamanın tekrarlanmasının gerekli göründüğüdür (yöntemlerin belli bir sıradaki alaylara çağrılmasıyla) ve bu yanlış görünüyor.


44
Çalıştıktan sonra. Kurabiyeleriniz var mı?
Ewan

6
güncellemenizle ilgili: neden bir tavayla alay etmek istediniz? veya hamur? bu sesler, basit hafıza içi nesneler yaratma konusunda önemsiz olması gereken sesler ve bu nedenle onları tek bir birim olarak test etmemeniz için hiçbir neden yok. Unutmayın, "birim testindeki" "birim", "tek bir sınıf" anlamına gelmez. "Bir şeyi yapmak için kullanılan en küçük kod birimi" anlamına geliyor. Bir tava muhtemelen hamur nesneler için bir konteynırdan başka bir şey değildir, bu yüzden bakeCookies yöntemini dışarıdan sadece test sürüşü yapmak yerine izolasyonda test etmek istenir.
sara

11
Günün sonunda, buradaki işyerinde temel prensip, kodun çalıştığını garanti altına almak için yeterli testler yazmanız ve birinin bir şeyleri değiştirdiğinde bunun yeterli bir "kömür madeninde kanarya" olduğudur. Bu kadar. Büyülü teşvikler, formül varsayımları veya dogmatik iddialar yoktur; bu nedenle% 85 ila% 90 kod kapsamı (% 100 değil) yaygın olarak mükemmel
Robert Harvey,

5
@RobertHarvey ne yazık ki formülik platitudes ve TDD ses ısırıkları, anlaşmanın coşkulu hallerini kazandığınızdan emin olarak, gerçek dünyayla ilgili sorunları çözmenize yardımcı olmaz. Bunun için ellerinizi kirletmeniz ve gerçek bir soruyu yanıtlama riskiniz olması gerekiyor
Jonah

4
Azalan siklomatik karmaşıklık sırasına göre birim testi. İnan bana, bu işleve gelmeden önce zamanın tükenecek
Neil McGuigan

Yanıtlar:


118

Meli savePeople()birim test edilecek? Evet. dataStore.savePersonİşe ya da db bağlantısının çalıştığını, hatta çalıştığını test etmiyorsunuz foreach. savePeopleKontratı ile verdiği sözü yerine getiren testleri siz yapıyorsunuz .

Bu senaryoyu hayal edin: Birisi kod tabanının büyük bir refactorünü yapar forEachve uygulamanın bir bölümünü yanlışlıkla kaldırır, böylece her zaman yalnızca ilk öğeyi kaydeder. Bunu yakalamak için bir birim testi yapmak istemez miydin?


20
@RobertHarvey: Çok fazla gri alan var ve bu ayrımı IMO yapmak önemli değil. Yine de haklısın - “doğru işlevleri çağırıyor” değil, “doğru şeyi yapıyor” nasıl yapıldığına bakmadan test etmek gerçekten önemli değil. Önemli olan, belirli bir çıkış kümesi elde ettiğiniz işleve belirli bir girdi kümesi verildiğinde, bunun test edilmesidir. Bununla birlikte, bu son cümlenin nasıl kafa karıştırıcı olduğunu görebiliyorum, bu yüzden kaldırdım.
Bryan Oakley

64
“SavePeople'ın sözleşmesiyle verdiği sözü yerine getirdiğini test ediyorsunuz.” Bu. Çok fazla bu.
Lovis

2
Bunu kapsayan "uçtan uca" sistem testine sahip değilseniz.
Ian

6
@ Uçtan uca testler ünite testlerinin yerini almaz, ücretsizdir. Sırf insanların listesini kaydetmenizi sağlayan bir uçtan uca testiniz olabileceği için, aynı zamanda bunu kapsayacak bir birim testine sahip olmamanız gerektiği anlamına gelmez.
Vincent Savard,

4
@VincentSavard, ancak eğer risk anter şekilde kontrol ediliyorsa, birim testin maliyeti / faydası azalır.
Ian

36

Genellikle bu tür bir soru, insanlar "testten sonra" gelişme yaptığında ortaya çıkar. Bu soruna, uygulamadan önce testlerin geldiği TDD bakış açısıyla yaklaşın ve kendinize bu soruyu tekrar bir alıştırma olarak sorun.

En azından genellikle dışarıda olan TDD başvurumda, uygulandıktan savePeoplesonra olduğu gibi bir işlevi yerine getirmiyordum savePerson. savePeopleVe savePersonfonksiyonları olarak başlayacak ve aynı birim testlerinden test odaklı edilir; ikisi arasındaki ayrım yeniden yapılanma aşamasında birkaç testten sonra ortaya çıkacaktır. Bu çalışma şekli aynı zamanda işlevin nerede olması savePeoplegerektiği - serbest bir işlev mi yoksa bir parçası mı olduğu sorusunu da ortaya çıkarır dataStore.

Sonunda, testler doğru bir kaydedebilirsiniz eğer sadece kontrol etmem Personde Store, aynı zamanda pek çok kişi. Bu aynı zamanda, diğer kontrollerin gerekli olup olmadığını sorgulamama da yol açar, örneğin, " savePeopleİşlevin atomik olduğundan emin olmalı mıyım , tümünü mi yoksa hiçbirini mi kaydetmeliyim?" saklanmıyor? Bu hatalar nasıl görünür? ", vb. Bütün bunlar, sadece bir forEachveya başka bir yineleme biçiminin kullanımını kontrol etmekten çok daha fazla miktardadır .

Aynı anda birden fazla kişiyi kurtarmanın şartı sonra geldiyse de, savePersonzaten teslim edildi, sonra ben mevcut testleri güncellemek istiyorum savePersonyeni işlevi aracılığıyla çalıştırmak için savePeople, emin hala basitçe ilk başta temsilciliği vererek bir kişiyi kurtarabilir yapım Daha sonra davranışı atomik yapmanın gerekip gerekmediğini düşünerek yeni testler yoluyla davranışı bir den fazla test edin.


4
Temel olarak arayüzü değil, uygulamayı test edin.
Snoop

8
Adil ve anlayışlı noktalar. Yine de bir şekilde gerçek sorumun mahrum kaldığını hissediyorum :) Cevabınız, “Gerçek dünyada, iyi tasarlanmış bir sistemde, sorununuzun bu basitleştirilmiş versiyonunun var olacağını sanmıyorum” diyor. Yine adil, ama özellikle daha genel bir sorunun özünü vurgulamak için bu basitleştirilmiş sürümü yarattım. Örneğin yapay doğasını geçemiyorsanız, belki de sadece yineleme ve delegasyon yapan benzer bir işlev için iyi bir nedene sahip olduğunuz başka bir örneği hayal edebilirsiniz. Ya da belki de sadece imkansız olduğunu düşünüyorsunuz?
Jonah

@Jonah güncellendi. Umarım sorunuzu biraz daha iyi cevaplar. Bunların hepsi fikir odaklı ve bu sitenin amacına karşı olabilir, ancak kesinlikle çok ilginç bir tartışma. Bu arada, uygulamanın ne kadar önemsiz olduğuna bakılmaksızın tüm uygulama davranışları için birim testlerini bırakmak için çaba sarf etmemiz gereken profesyonel çalışma bakış açısıyla cevap vermeye çalıştım; biz ayrılırsak yeni koruyucular için belgelenmiş sistem. Kişisel veya yani kritik olmayan (para da kritik) projeler için çok farklı bir fikrim var.
MichelHenrich

Güncelleme için teşekkürler. Tam olarak nasıl test edersiniz savePeople? OP'nin son paragrafında veya başka bir şekilde tarif ettiğim gibi?
Jonah

1
Üzgünüm, "alay konusu yok" kısmıyla kendimi netleştirmedim. Demek istediğim savePerson, senin önerdiğin gibi işlev için sahte kullanmayacağım , bunun yerine daha genel olarak test edeceğim savePeople. Birim testleri doğrudan çağrı Storeyapmak savePeopleyerine çalıştırılmak üzere değiştirilecektir savePerson, bu nedenle bunun için hiçbir alay kullanılmaz. Ancak, kodlama problemlerini gerçek veritabanları ile oluşan çeşitli entegrasyon problemlerinden izole etmek istediğimiz için elbette veritabanı mevcut olmamalıdır, bu yüzden burada hala bir alay var.
MichelHenrich,

21

SavePeople () birim test edilmeli

Evet, olmalı. Ancak test koşullarınızı uygulamadan bağımsız bir şekilde yazmaya çalışın. Örneğin, kullanım örneğinizi birim testine dönüştürmek:

function testSavePeople() {
    myDataStore = new Store('some connection string', 'password');
    myPeople = ['Joe', 'Maggie', 'John'];
    savePeople(myDataStore, myPeople);
    assert(myDataStore.containsPerson('Joe'));
    assert(myDataStore.containsPerson('Maggie'));
    assert(myDataStore.containsPerson('John'));
}

Bu test çok şey yapar:

  • işlevin sözleşmesini doğrular savePeople()
  • uygulanması umurunda değil savePeople()
  • örnek kullanımını belgeliyor savePeople()

Veri deposunu hala alay / taslak / taklit edebileceğinizi unutmayın. Bu durumda, açık fonksiyon çağrıları için kontrol yapmazdım ama işlemin sonucunu kontrol ederim. Bu şekilde testim gelecekteki değişikliklere / refaktörlere hazırlanır.

Örneğin, veri deposu uygulamanız saveBulkPerson()gelecekte bir yöntem sağlayabilir - şimdi savePeople()kullanımının uygulanmasında yapılan bir değişiklik , beklendiği saveBulkPerson()kadar uzun sürdüğü sürece ünite testini saveBulkPerson()bozmaz. Ve eğer bir saveBulkPerson()şekilde beklendiği gibi çalışmazsa, birim testiniz bunu yakalayacaktır.

ya da bu tür testler, forEach dil yapısının yerleşik olarak test edilmesini sağlar mı?

Belirtildiği gibi, uygulama için değil (entegrasyon testleri yapmıyorsanız - o zaman belirli işlev çağrılarını kullanmak kullanımda olabilir) beklenmeyen sonuçları ve işlev arayüzünü test etmeye çalışın. Bir işlevi uygulamak için birden fazla yol varsa, bunların tümü birim testinizde çalışmalıdır.

Sorunun güncellenmesiyle ilgili olarak:

Durum değişikliklerini test edin! Örneğin bazı hamurlar kullanılacaktır. Senin uygulanmasına göre, kullanılan miktarı iddia doughsığar panveya iddia doughtüketilir. panİşlev çağrısından sonra çerezleri içerdiğini kabul edin . ovenBoş olduğunu ve eskisi gibi aynı durumda olduğunu kabul edin.

Ek testler için son durumları doğrulayın: ovenAramadan önce boş değilse ne olur ? Yeterli yoksa ne olur dough? Eğer panzaten dolu?

Bu testler için gerekli tüm verileri hamur, tava ve fırın nesnelerinden çıkarabilmelisiniz. İşlev çağrılarını yakalamaya gerek yok. Fonksiyonu, uygulaması sizin için uygun olmayacak gibi düşünün!

Aslında, çoğu TDD kullanıcısı, fonksiyonlarını yazmadan önce testlerini yapar, böylece gerçek uygulamaya bağlı olmazlar.


En son eklemeniz için:

Bir kullanıcı yeni bir hesap oluşturduğunda, bazı şeylerin olması gerekir: 1) veritabanında yeni bir kullanıcı kaydı oluşturulması gerekir 2) karşılama e-postası gönderilir 3) kullanıcının IP adresinin dolandırıcılık için kaydedilmesi gerekir amaçlar.

Bu nedenle, tüm "yeni kullanıcı" adımlarını birbirine bağlayan bir yöntem oluşturmak istiyoruz:

function createNewUser(validatedUserData, emailService, dataStore) {
    userId = dataStore.insertUserRecord(validateduserData);
    emailService.sendWelcomeEmail(validatedUserData);
    dataStore.recordIpAddress(userId, validatedUserData.ip);
}

Bunun gibi bir fonksiyon için, ack / stub / fake (ne daha genel görünüyorsa) dataStoreve emailServiceparametrelerini alayım. Bu işlev kendi başına herhangi bir parametre üzerinde herhangi bir durum geçişi yapmaz, onları bazılarının yöntemlerine devreder. Fonksiyon çağrısının 4 şey yaptığını doğrulamaya çalışacağım:

  • bir kullanıcıyı veri deposuna ekledi
  • bir hoşgeldin e-postası (veya en azından karşılık gelen yöntem olarak adlandırılır) gönderdi
  • kullanıcıların IP'sini veri deposuna kaydetti
  • karşılaştığı herhangi bir istisnayı / hatayı temsil etti (varsa)

İlk 3 denetler mocks, taslakları veya sahte ile yapılabilir dataStoreve emailService(gerçekten test ederken e-postalar göndermek istemiyorum). Bazı yorumlar için bunu aramam gerektiğinden, bunlar arasındaki farklar:

  • Sahte, orijinalle aynı şekilde davranan ve belli bir ölçüde ayırt edilemeyen bir nesnedir. Kodu normalde testlerde tekrar kullanılabilir. Bu, örneğin, bir veritabanı sarıcısı için basit bir bellek içi veritabanı olabilir.
  • Bir saplama sadece bu testin gerekli işlemlerini yerine getirmek için gereken kadar uygular. Çoğu durumda, bir saplama bir teste veya yalnızca orijinal yöntemlerin küçük bir setini gerektiren bir test grubuna özgüdür. Bu örnekte, dataStoresadece insertUserRecord()ve 'nin uygun bir versiyonunu uygulayan olabilir recordIpAddress().
  • Sahte, nasıl kullanıldığını doğrulamanıza izin veren bir nesnedir (çoğunlukla çağrıları yöntemlerine göre değerlendirmenize izin vererek). Bunları, ünite testlerinde çok az kullanmaya çalışırdım, çünkü onları kullanarak, aslında işlev uygulamasını test etmeye çalışırsınız, arayüzüne uyumu değil, ama yine de kullanımları vardır. İhtiyaç duyduğunuz sahtekarlığı yaratmanıza yardımcı olmak için pek çok sahte çerçeve vardır.

Bu yöntemlerden herhangi biri bir hata atarsa, hatanın uygun gördüğü şekliyle işleyebilmesi için çağrının koduna kabarmasını istiyoruz. API kodu tarafından çağrılıyorsa, hatayı uygun bir HTTP yanıt koduna çevirebilir. Bir web arayüzü tarafından aranıyorsa, hatayı kullanıcıya gösterilecek şekilde uygun bir mesaja çevirebilir. Mesele şu ki, bu fonksiyon atılabilecek hataları nasıl idare edeceğini bilmiyor.

Beklenen istisnalar / hatalar geçerli test durumlarıdır: Böyle bir olay olması durumunda, işlevin beklediğiniz gibi davrandığını onaylarsınız. Bu, ilgili sahte / sahte / saplama nesnesinin istendiğinde atmasını sağlayarak başarılabilir.

Kafamın özü, böyle bir işlevi test etmek için testin kendisinde tam olarak uygulamanın tekrarlanmasının gerekli göründüğüdür (yöntemlerin belli bir sıradaki alaylara çağrılmasıyla) ve bu yanlış görünüyor.

Bazen bu yapılmalı (entegrasyon testlerinde çoğunlukla bunu umursuyorsanız da). Daha sık, beklenen yan etkileri / durum değişikliklerini doğrulamanın başka yolları da vardır.

Tam işlev çağrılarının doğrulanması, oldukça kırılgan birim testlerinin yapılmasını sağlar: Orijinal işlevde yalnızca küçük değişiklikler yapılmamasına neden olur. Bu arzu edilebilir veya istenmeyebilir, ancak bir işlevi değiştirdiğinizde ilgili birim test (ler) inde bir değişiklik yapılması gerekir (yeniden düzenleme, optimizasyon, hata düzeltme, ...).

Ne yazık ki, bu durumda ünite testi bazı güvenilirliğini kaybeder: değiştiğinden, değişiklik eskisi gibi davrandıktan sonra işlevi onaylamaz.

Örneğin, oven.preheat()çerez pişirme örneğinizde (optimizasyon!) Bir arama ekleyen birini düşünün :

  • Fırın nesnesini alay ettiyseniz, yöntemin gözlemlenebilir davranışı değişmediyse de denemenin başarısız olduğunu beklemez ve testin başarısız olmasını beklemez (umarım hala bir kurabiye tabağınız vardır).
  • Bir saplama, yalnızca test edilecek yöntemleri veya tüm arayüzü bazı kukla yöntemlerle eklediğinize bağlı olarak başarısız olabilir veya olmayabilir.
  • Sahte başarısız olmalı, çünkü yöntemi uygulamalıdır (arayüze göre)

Birim testlerimde mümkün olduğunca genel olmaya çalışıyorum: Uygulama değişirse, ancak görünen davranış (arayanın perspektifinden) hala aynıysa, testlerim geçmelidir. İdeal olarak, mevcut bir ünite testini değiştirmem gereken tek vaka bir hata düzeltmesi olmalıdır (testin değil, test edilen fonksiyonun).


1
Sorun şu ki, yazdığınız myDataStore.containsPerson('Joe')anda işlevsel bir test veritabanının varlığını varsayıyorsunuzdur. Bunu yaptıktan sonra, bir birim testi değil, bir entegrasyon testi yazıyorsunuz.
Jonah

Bir test veri deposuna sahip olduğuma güvenebileceğimi (bunun gerçek veya sahte olduğu umurumda değil) ve her şeyin ayarlandığı gibi çalıştığını (zaten bu durumlar için birim testleri yapmam gerektiğine inanıyorum) var. Testin test etmek istediği tek şey, savePeople()aslında bu kişileri, veri deposu beklenen arayüzü uyguladığı sürece verdiğiniz veri deposuna eklemektir. Bir entegrasyon testi, örneğin, veritabanı sarıcımın gerçekten doğru veritabanının bir yöntem çağrısı çağırdığını kontrol etmesi olabilir.
hoffmale

Netleştirmek için, bir sahte kullanıyorsanız, yapabileceğiniz tek şey , belki de belirli bir parametreyle, sahte üzerine bir yöntemin çağrıldığını iddia etmek . Daha sonra alay durumunu belirtemezsiniz. Bu nedenle, test edilen yöntemi çağırdıktan sonra veritabanının durumu hakkında iddialarda bulunmak istiyorsanız, olduğu gibi myDataStore.containsPerson('Joe'), bir tür işlevsel db kullanmanız gerekir. Bu adımı attığınızda, artık birim testi değildir.
Jonah

1
gerçek bir veritabanı olması gerekmez - sadece gerçek veri deposuyla aynı arayüzü uygulayan bir nesne (okuma: veri deposu arayüzü için ilgili birim testlerinden geçer). Bunu hala bir taklit olarak görürüm. Sahte dizinin bunu yapmak için herhangi bir yöntemle eklenen her şeyi saklamasına izin verin ve test verilerinin dizinin içinde olup olmadığını kontrol edin myPeople. IMHO Bir sahte hala gerçek bir nesneden beklenen aynı gözlemlenebilir davranışa sahip olmalıdır, aksi takdirde, gerçek arabirimle değil, sahte arabirimle uyumu test ediyorsunuz.
Hoffmale

"Sahte dizinin bunu yapmak için herhangi bir yöntemle eklenen her şeyi saklamasına izin verin ve test verilerinin (myPeople öğelerinin) dizide olup olmadığını kontrol edin" - bu hala bir "gerçek" veritabanı, sadece geçici, hafızadaki bir. "IMHO alaycı hala gerçek bir nesneden beklenen gözlemlenebilir davranışa sahip olmalı" - Sanırım bunun için savunuculuğunu yapabilirsin, ama "sahte" nin test literatüründe veya popüler kütüphanelerden hiçbirinde anlamı yok. gördüm. Bir sahte basitçe beklenen yöntemlerin beklenen parametrelerle çağrıldığını doğrular.
Jonah

13

Böyle bir testin sağladığı birincil değer, uygulamanızı yenilenebilir kılar.

Kariyerimde birçok performans optimizasyonu yapardım ve gösterdiğiniz modelle ilgili sık sık problemler yaşardım: N varlıklarını veritabanına kaydetmek, N ekleri yapmak. Tek bir deyim kullanarak toplu ekleme yapmak genellikle daha etkilidir.

Öte yandan, zamanından önce optimize etmek de istemiyoruz. Genellikle bir seferde yalnızca 1 - 3 kişiyi kurtarırsanız, optimize edilmiş bir toplu iş yazmak aşırı yüklenebilir.

Uygun bir birim testiyle, onu yukarıda uyguladığınız şekilde yazabilirsiniz ve en iyi duruma getirmeniz gerektiğini tespit ederseniz, herhangi bir hatayı yakalamak için otomatik bir testin güvenlik ağını kullanmakta özgürsünüz. Doğal olarak, bu testlerin kalitesine göre değişir, bu nedenle liberal olarak test edin ve iyi test edin.

Bu davranışı test eden birimin ikincil avantajı, amacının ne olduğuna dair dokümantasyon sağlamaktır. Bu önemsiz örnek açık olabilir, ancak aşağıdaki bir sonraki noktaya bakıldığında, çok önemli olabilir.

Diğerlerinin de belirttiği üçüncü avantaj, entegrasyon veya kabul testleriyle test etmek çok zor olan detayları kapsayan testleri test edebilmenizdir. Örneğin, tüm kullanıcıların atomik olarak kaydedilmesi gerekliliği varsa, bunun için bir test durumu yazabilirsiniz, bu size beklendiği gibi davrandığını bilmenin bir yolunu sunar ve ayrıca açıkça görülmeyen bir gereksinim için dokümantasyon görevi görür. yeni geliştiricilere.

TDD eğitmeninden aldığım bir düşünceyi ekleyeceğim. Yöntemi test etme. Davranışı test et. Başka bir deyişle, bunun savePeopleişe yaradığını test etmiyorsunuz, birden fazla kullanıcının tek bir çağrıya kaydedilebileceğini test ediyorsunuz.

Bir programın çalıştığını doğrulamak gibi ünite testlerini düşünmeyi bıraktığımda kalite birimi testini ve TDD'yi iyileştirme yeteneğimi buldum, ancak bunun yerine bir kod biriminin beklediğim şeyi yaptığını doğruladılar . Bunlar farklı. Çalıştığını doğrulamıyorlar, ama düşündüğüm şeyi yaptığını doğruladılar. Bu şekilde düşünmeye başladığımda bakış açım değişti.


Toplu ekleme refactoring örneği iyi bir örnektir. OP'de önerdiğim olası birim testi - savePersonlistedeki her kişi için bir dataStore sahnesinin çağırdığı şey - yine de refactoring için toplu bir ekleme ile kırılacaktı. Hangisi bana bunun kötü bir birim testi olduğunu gösteriyor. Ancak, gerçek bir test veritabanı kullanmadan ve bununla ilgili iddialarda bulunmadan, hem toplu hem de kişi başına tasarruf uygulamalarını geçebilecek alternatif bir uygulama görmüyorum, bu yanlış görünüyor. Her iki uygulama için de işe yarar bir test verebilir misiniz?
Jonah

1
@ jpmc26 İnsanların kurtulduğunu test eden bir test ne olacak?
immibis

1
@ immibis Bunun anlamını anlamıyorum. Muhtemelen, gerçek mağaza bir veritabanı tarafından desteklenmektedir, bu yüzden bir birim testi için alay etmeli ya da saplamalısınız. Bu noktada, alay veya saplamanızın nesneleri depolayabildiğini test ediyor olacaksınız. Bu tamamen işe yaramaz. Yapabileceğiniz en iyi savePersonyöntem, her giriş için yöntemin çağrıldığını ve döngüyü bir toplu ekleme ile değiştirirseniz, artık bu yöntemi çağırmazsınız. Yani sınavın bozuldu. Aklında başka bir şey varsa, ben açığım, ama henüz göremiyorum. (Ve onu görmemek benim açımdan değildi.)
jpmc26

1
@ immibis Bunu yararlı bir test olarak düşünmüyorum. Sahte veri deposunu kullanmak, gerçek şeyle çalışacağına dair bana hiçbir güven vermedi. Sahte olduğumun gerçek gibi çalıştığını nasıl bilebilirim? Bir entegrasyon testi grubunun bunu yapmasına izin vermeyi tercih ederim. (Muhtemelen buradaki ilk
yorumumda

1
@ immibis Aslında pozisyonumu tekrar gözden geçiriyorum. Bunun gibi problemlerden dolayı birim testlerinin değeri konusunda şüpheliydim, ama belki de bir giriş yapsanız bile değeri küçümsüyorum. Ben do giriş / çıkış testi olma eğiliminde olduğunu biliyoruz çok sahte ağır testlerden daha yararlı, ancak sahte burada aslında sorunun bir parçası ile belki ret bir giriş yerine.
jpmc26

6

Meli bakeCookies()test edilecek? Evet.

Bunun gibi bir fonksiyon, olması gerektiğini düşündüğünüz varsayılarak, nasıl test edilmeli? Hamur, tava ve fırınla ​​alay etmeyen herhangi bir birim testini hayal etmem ve daha sonra kendilerine metotların çağrıldığını iddia etmek zor.

Pek sayılmaz. Fonksiyonun NE yapması gerektiğine yakından bakın - ovennesneyi belirli bir duruma getirmesi gerekiyordu . Koda bakıldığında, nesnelerin panve doughnesnelerin durumlarının gerçekten önemli olmadığı anlaşılıyor . Öyleyse bir ovennesneyi geçmeli (veya alay etmeli ) ve işlev çağrısının sonunda belirli bir durumda olduğunu iddia etmelisiniz .

Başka bir deyişle, kurabiyeleri pişirdiğini iddia etmelisinizbakeCookies() .

Çok kısa fonksiyonlar için birim testleri totolojiden biraz daha fazla görünebilir. Ama unutma, programın, yazarken çalıştığın zamandan çok daha uzun sürecek. Bu işlev gelecekte değişebilir veya değişmeyebilir.

Birim testleri iki işleve sahiptir:

  1. Her şeyin çalıştığını test ediyor. Bu, en az kullanışlı fonksiyon biriminin test ettiği ve soruyu sorarken yalnızca bu işlevi düşündüğünüz görülüyor.

  2. Programın gelecekteki değişikliklerinin daha önce uygulanmış olan işlevselliği bozmadığını kontrol eder. Bu, birim testlerinin en faydalı işlevidir ve hataların büyük programlara girmesini önler. Programa özellikler eklerken normal kodlamada kullanışlıdır, ancak programı uygulayan temel algoritmaların programın gözlemlenebilir herhangi bir davranışını değiştirmeden büyük ölçüde değiştirildiği yeniden düzenleme ve optimizasyonlarda daha kullanışlıdır.

İşlev içindeki kodu test etmeyin. Bunun yerine, fonksiyonun söylediğini yaptığını test edin. Ünite sınamalarına bu şekilde baktığınızda (sınama işlevleri, kod değil), dil yapılarını veya uygulama mantığını hiçbir zaman sınamadığınızı fark edersiniz. Bir API'yi test ediyorsunuz.


Merhaba, cevap için teşekkürler. 2. güncellememe bakıp bu örnekteki işlevi nasıl test edeceğinize dair düşüncelerinizi verebilir misiniz?
Jonah

Bunun gerçek bir fırın veya sahte bir fırın kullandığınızda etkili olabileceğini ancak sahte bir fırında daha az etkili olduğunu düşünüyorum. Alaylar (Meszaros tanımlarına göre) kendilerine çağrılan metotların kaydı dışında hiçbir devlete sahip değildir. Benim deneyimlerim gibi fonksiyonlar bakeCookiesbu şekilde test edildiğinde, uygulamanın gözlemlenebilir davranışını etkilemeyecek olan yeniden yansıtıcılar sırasında kırılma eğiliminde olmalarıdır.
James_pic

Tam olarak @James_pic. Ve evet, kullandığım sahte tanım budur. Bu yüzden, yorumunuzu yaparken, böyle bir durumda ne yaparsınız? Testi bıraktın mı? Yine de kırılgan, uygulama tekrar eden testi yazın? Başka bir şey?
Jonah

@Jonah Genellikle o bileşene entegrasyon testleriyle denemeye bakacağım (muhtemelen modern takımların kalitesi nedeniyle şişmiş olması hata ayıklanmasının daha zor olduğu konusunda uyarıları buldum), ya da yarı ikna edici sahte.
James_pic

3

SavePeople () birim testine tabi tutulmalı mı yoksa bu tür testler forEach dil yapısını test etmeyi mi gerektiriyor?

Evet. Ama olabilir sadece yapısını yeniden test edecek bir şekilde yapıyoruz.

Burada dikkat edilmesi gereken, savePersonyarı yolda başarısız olduğunda bu fonksiyonun nasıl davrandığıdır ? Nasıl çalışması gerekiyor?

Bu , fonksiyonun birim sınamalarında uygulamanız gerektiğini sağladığı ince davranış türüdür.


Evet, ince hata koşullarının aynı fikirdeyim denenmesi gerektiğine , ancak bu ilginç bir soru değil imo - cevap açık. Bu nedenle, özellikle, sorumun amacına göre, savePeoplehataların idaresinden sorumlu olmamasını özellikle belirtmiştim . Tekrar netleştirmek için varsayarak bu savePeoplesorumludur sadece Listede yineleme ve başka yönteme her öğenin tasarrufu için yetki verme, hala test edilmelidir?
Jonah

@Jonah: Ünite testinizi yalnızca foreachyapı ile sınırlamakta ısrar edecekseniz , bunun dışındaki herhangi bir koşul, yan etki veya davranış değil, haklısınız; Yeni ünite testi gerçekten o kadar da ilginç değil.
Robert Harvey,

1
@ jonah - mümkün olduğunca yinelemeli ve kaydetmeli mi, yoksa hata mı durdurmalı? Tek tasarruf buna karar veremez, çünkü nasıl kullanıldığını bilmez.
Telastyn

1
@jonah - siteye hoşgeldiniz. Soru-Cevap formatımızın kilit bileşenlerinden biri, size yardım etmek için burada olmadığımızdır . Sorunuz elbette size yardımcı olur, ancak aynı zamanda siteye gelen ve sorularına cevap arayan birçok kişiye yardımcı olur. İstediğiniz soruyu cevapladım. Cevabı beğenmezseniz ya da kaleci hedefleri değiştirmeyi tercih etmek benim suçum değil. Ve açıkçası, diğer cevaplar daha iyi de olsa, aynı temel şeyi söylüyor gibi görünüyor.
Telastyn

1
@Telastyn, birim testi hakkında fikir edinmeye çalışıyorum. Benim ilk soru yeterince açık değildi, bu yüzden doğru konuşmayı yönlendirmek için açıklamalar ekliyorum gerçek soruma . Beni bir şekilde "haklı olma" oyununda aldattığım gibi yorumlamayı seçiyorsun. Kod inceleme ve SO hakkındaki soruları yanıtlamak için yüzlerce saat harcadım. Amacım her zaman cevap verdiğim insanlara yardım etmektir. Senin değilse, bu senin seçimin. Sorularıma cevap vermek zorunda değilsin.
Jonah

3

Burada anahtar önemsiz olarak belirli bir fonksiyona bakış açınız. Programlamanın çoğu önemsizdir: bir değer atayın, biraz matematik yapın, bir karar verin: eğer öyleyse, o zamana kadar bir döngü devam edin ... İzolasyonda, hepsi önemsiz. Bir programlama dilini öğreten herhangi bir kitabın ilk 5 bölümünü yeni aldınız.

Bir test yazmanın çok kolay olması, tasarımınızın o kadar da kötü olmadığının bir işareti olmalıdır. Test edilmesi kolay olmayan bir tasarımı tercih eder misiniz?

“Bu asla değişmeyecek.” çoğu başarısız projenin nasıl başladığı. Bir birim testi, yalnızca ünitenin belirli koşullar altında beklendiği gibi çalışıp çalışmadığını belirler. Geçmesini sağlayın ve ardından uygulama ayrıntılarını unutabilir ve sadece kullanabilirsiniz. Bir sonraki görev için o beyin alanını kullanın.

İşlerin beklendiği gibi çalıştığını bilmek çok büyük ve büyük projelerde ve özellikle de büyük ekiplerde önemsiz değildir. Programcıların ortak noktalarından biri varsa, hepimizin başkasının korkunç koduyla uğraşmak zorunda olduğumuz gerçeğidir. En azından yapabileceğimiz bazı testler var. Şüphe duyduğunuzda, bir test yazın ve devam edin.


Geri bildiriminiz için teşekkür ederiz. Güzel nokta. Gerçekten cevaplanmasını istediğim soru (henüz açıklığa kavuşturmak için başka bir güncelleme ekledim) delegasyon aracılığıyla başka bir hizmet sırasını çağırmaktan başka hiçbir şey yapmayan işlevleri sınamak için uygun yoldur. Bu gibi durumlarda, “sözleşmeyi belgelemek” için uygun birim testlerinin, işlevlerin uygulanmasının bir düzeltmesi olduğu, yöntemlerin çeşitli alaylarla çağrıldığını iddia ettiği görülmektedir. Yine de, bu durumlarda uygulama ile aynı olması test yanlış hissettiriyor ....
Jonah

1

SavePeople () birim testine tabi tutulmalı mı yoksa bu tür testler forEach dil yapısını test etmeyi mi gerektiriyor?

Bu, @BryanOakley tarafından zaten cevaplandı, ancak bazı ekstra argümanlarım var (Sanırım):

İlk olarak, bir birim testi, bir API'nın uygulanmasının değil, bir sözleşmenin yerine getirilmesinin test edilmesi içindir; Test ön koşulları belirlemeli, sonra aramalı, sonra efektleri, yan etkileri, değişmeyenleri ve postalama koşullarını kontrol etmelidir. Neyin test edileceğine karar verdiğinizde , API'nin uygulanması önemli değildir (ve olmamalıdır) .

İkinci olarak, testiniz işlev değiştiğinde değişmezleri kontrol etmek için orada olacaktır . Şimdi değişmemesi gerçeği , sınava girmemeniz gerektiği anlamına gelmiyor.

Üçüncüsü, hem TDD yaklaşımında (onu zorunlu kılan) hem de bunun dışında önemsiz bir test uygulamanın değeri var.

C ++ yazarken, sınıflarım için, bir nesneyi başlatan ve değişmezleri kontrol eden önemsiz bir test yazma eğilimindeyim (atanabilir, normal, vb.). Geliştirme sırasında bu testin kaç kez kırılmasının şaşırtıcı olduğunu buldum (örneğin - bir sınıfa hareketli olmayan bir üye ekleyerek).


1

Bence sorunuz aşağı düşüyor:

Bir boşluk fonksiyonunu bir entegrasyon testi olmadan nasıl test edebilirim?

Çerez pişirme işlevinizi değiştirirsek, örneğin çerezleri geri gönderirsek, örneğin testin ne olması gerektiği hemen belli olur.

Fonksiyonu çağırdıktan sonra pan.GetCookies'i çağırmak zorunda kalırsak, 'gerçekten bir entegrasyon testi' mi yoksa 'sadece pan nesnesini mi test ettiğimizi' sorabilir miyiz?

Bence her şeyle alay etmiş bir ünite testi yaptırmakta haklısın ve sadece kontrol fonksiyonlarına xy ve z'nin değer eksikliği deniyorsun.

Fakat! Bu durumda test edilebilir bir sonuç döndürmek için boş fonksiyonlarınızı yeniden kırmanız gerektiğini VEYA gerçek nesneleri kullanıp bir entegrasyon testi yaptırmanız gerektiğini savunuyorum.

--- createNewUser örneği için güncelleme

  • veritabanında yeni bir kullanıcı kaydı oluşturulması gerekiyor
  • hoş bir e-posta gönderilmesi gerekiyor
  • kullanıcının IP adresinin dolandırıcılık amacıyla kaydedilmesi gerekir.

Tamam, bu kez fonksiyonun sonucu kolayca döndürülemez. Parametrelerin durumunu değiştirmek istiyoruz.

Burası biraz tartışmalı olduğum yer. Durumsal parametreler için somut sahte uygulamalar yaratıyorum

Lütfen sevgili okuyucular, öfkenizi kontrol etmeye çalışın!

yani...

var validatedUserData = new UserData(); //we can use the real object for this
var emailService = new MockEmailService(); //a simple mock which saves sentEmails to a List<string>
var dataStore = new MockDataStore(); //a simple mock which saves ips to a List<string>

//run the test
target.createNewUser(validatedUserData, emailService, dataStore);

//check the results
Assert.AreEqual(1, emailService.EmailsSent.Count());
Assert.AreEqual(1, dataStore.IpsRecorded.Count());
Assert.AreEqual(1, dataStore.UsersSaved.Count());

Bu, test edilen yöntemin uygulama detayını istenen davranıştan ayırır. Alternatif bir uygulama:

function createNewUser(validatedUserData, emailService, dataStore) {
  userId = dataStore.bulkInsedrtUserRecords(new [] {validateduserData});
  emailService.addEmailToQueue(validatedUserData);
  emailService.ProcessQueue();
  dataStore.recordIpAddress(userId, validatedUserData.ip);
}

Hala ünite testinden geçecek. Ayrıca, sahte nesneleri testler arasında yeniden kullanabilme ve UI veya Entegrasyon Testleri için uygulamanıza enjekte etme avantajına sahipsiniz.


Bu bir entegrasyon testi değildir, çünkü 2 somut sınıfın adından bahsediyorsunuzdur ... entegrasyon testleri, disk IO, DB, harici web servisleri vb. gibi harici sistemlerle entegrasyonları test etmek içindir. pan.getCookies () -Haber, hızlı, ilgilendiğimiz şeyi kontrol eder vs. Yöntemin çerezleri doğrudan geri getirmesinin daha iyi bir tasarım gibi hissettiğini kabul ediyorum.
sara

3
Bekle. Hepimiz biliyoruz ki pan.getcookies, bir aşçıya, şanslarını yakaladıklarında kurabiyeleri fırından çıkarmaları için bir e-posta gönderir
Ewan

Sanırım teorik olarak mümkün, ama oldukça yanıltıcı bir isim olurdu. kim hiç e-posta gönderen fırın ekipmanlarını duydu? ama seni görüyorum, buna bağlı. Bu işbirlikçi sınıfların yaprak nesneler veya sadece düz hafıza içi şeyler olduğunu farz ediyorum, ancak sinsi şeyler yaparlarsa, o zaman dikkatli olunması gerekir. Bence kesinlikle eposta gönderimi bundan daha yüksek bir seviyede yapılmalı. Bu işletmelerde aşağı ve kirli iş mantığı gibi görünüyor.
sara

2
Bu retorik bir soruydu, ancak: "hiç e-posta gönderen fırın ekipmanını kim duydu?" venturebeat.com/2016/03/08/…
clacke

Merhaba Ewan, bu cevabın gerçekten sorduğum şeye çok yaklaştığını düşünüyorum. bakeCookiesPişmiş kurabiyeleri geri getirme konusundaki amacınız çok açık ve sanırım gönderdikten sonra biraz düşündüm. Bence yine güzel bir örnek değil. Umarım sorumu neyin motive ettiğini daha gerçekçi bir örnek olarak sunan bir güncelleme daha ekledim. Girişinizi takdir ediyorum.
Jonah

0

Ayrıca test etmelisiniz bakeCookies- ne vermeli / vermeli bakeCookies(egg, pan, oven)? Kızarmış yumurta mı, istisna mı? Kendi başlarına, asıl malzemeleri ne panumursarlar ne de ovenumursarlar, çünkü hiçbiri bakeCookiesyapmamalı , fakat genellikle çerezler vermelidir. Daha genel olarak nasıl bağlı olabilir doughelde edilir ve orada olmadığını ise o sadece olma şansı eggveya örneğin wateryerine.

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.