Hiçbir şey döndürmeyen saf bir yöntem için nasıl test yazmalıyım?


13

Değerlerin doğrulanmasıyla ilgilenen bir sürü sınıfım var. Örneğin, bir RangeValidatorsınıf bir değerin belirtilen aralıkta olup olmadığını kontrol eder.

Her validator sınıfı iki yöntem içerir: bu değer is_valid(value)döndürür Trueveya Falsedeğere bağlı olarak, ensure_valid(value)belirtilen değeri kontrol eder ve değer geçerliyse hiçbir şey yapmaz veya değer önceden tanımlanmış kurallarla eşleşmezse belirli bir istisna atar.

Şu anda bu yöntemle ilişkili iki birim testi vardır:

  • Geçersiz bir değer ileten ve istisnanın atılmasını sağlayan değer.

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • Geçerli bir değeri geçen değer.

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

Her ne kadar ikinci test işini yapsa da - istisna atılırsa başarısız olur ve ensure_validhiçbir şey atmazsa başarılı olur - assertiçeride hiç bir şey olmadığı gerçeği garip görünüyor. Böyle bir kodu okuyan biri derhal kendisine hiçbir şey yapmıyor gibi görünen bir test olduğunu soracaktır .

Değer döndürmeyen ve yan etkileri olmayan yöntemleri test ederken bu geçerli bir uygulama mıdır? Yoksa testi farklı bir şekilde yeniden mi yazmalıyım? Yoksa basitçe ne yaptığımı açıklayan bir yorum mu koyuyorsunuz?



11
Bilgiçlik noktası, ancak argüman almayan ( selfreferans için kaydetme ) ve sonuç döndürmeyen bir fonksiyonunuz varsa , bu saf bir fonksiyon değildir.
David Arno

10
@DavidArno: Bu bilgiçliksel bir nokta değil, sorunun tam ortasına gidiyor: yöntem kesin olmadığı için test etmek zor .
Jörg W Mittag

@DavidArno Daha bilgiçimsel nokta, "hiçbir şey yapma" yöntemine sahip olabilirsiniz ("sonuç vermez" ifadesinin voidbirçok dilde çağrılan ve aptalca kuralları olan "birim türünü döndürür" olarak yorumlanır .) Alternatif olarak, sonsuz bir loop ("hiçbir sonuç döndürmediğinde bile çalışır" gerçekten hiçbir sonuç döndürmediği anlamına gelir.
Derek Elkins SE

Herhangi bir dilde, sihirli bir şekilde özel kasalı bir şey yerine üniteyi standart bir veri türü olarak kabul eden tek bir tür işlev vardır unit -> unit. Ne yazık ki, sadece birimi döndürür ve başka bir şey yapmaz.
Phoshi

Yanıtlar:


21

Çoğu test çerçevesinin " expect(() => {}).not.toThrow();Atmaz " için açık bir iddiası vardır, örneğin Yasemin ve nUnit ve arkadaşlarının da bir tane vardır.


1
Test çerçevesi böyle bir iddiaya sahip değilse, aynı şeyi yapan ve kodu kendi kendini açıklayan bir yerel yöntem oluşturmak her zaman mümkündür. İyi bir fikir.
Arseni Mourzenko

7

Bu büyük ölçüde kullanılan dile ve çerçeveye bağlıdır. Açısından konuşan NUnitvardır Assert.Throws(...)yöntemleri. Onlara lambda yöntemini iletebilirsiniz:

Assert.Throws(() => rangeValidator.EnsureValid(-5))

hangi içinde yürütülür Assert.Throws. Lambda'ya yapılan çağrı büyük olasılıkla bir try { } catch { }blok tarafından sarılır ve bir istisna yakalanırsa iddia başarısız olur.

Çerçeveniz bu araçları sağlamazsa, aramayı kendiniz sararak (C # ile yazıyorum) etrafta çalışabilirsiniz:

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

Bu, niyeti daha açık hale getirir, ancak kodu bir dereceye kadar keser. (Elbette tüm bunları bir yöntemle sarabilirsiniz.) Sonunda size kalmış.

Düzenle

Doc Brown'un yorumlarda belirttiği gibi, konu yöntemin atıldığını değil, atmadığını göstermek olmuştur. NUnit'te bunun için bir iddia var

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))

1

Neden iddia gerekmediğini ve neden unutmadığınızı açıklığa kavuşturmak için bir yorum ekleyin.

Diğer cevaplarda da görebileceğiniz gibi, başka her şey kodu daha karmaşık ve karmaşık hale getirir. Buradaki yorumla, diğer programcılar testin amacını bilecekler.

Bununla birlikte, bu tür bir test bir istisna olmalıdır (cinas amaçlanmamıştır). Kendinizi düzenli olarak böyle bir şey yazarken bulursanız, muhtemelen testler tasarımın en uygun olmadığını söylüyor.


1

Ayrıca bazı yöntemlerin düzgün çağrıldığını (veya çağrılmadığını) da iddia edebilirsiniz.

Örneğin:

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

Testinizde:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
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.