Bagaimana saya harus menulis tes untuk metode murni yang tidak mengembalikan apa pun?

13

Saya memiliki banyak kelas yang berhubungan dengan validasi nilai. Sebagai contoh, suatu RangeValidatorkelas memeriksa apakah suatu nilai berada dalam kisaran yang ditentukan.

Setiap kelas validator berisi dua metode is_valid(value):, yang mengembalikan Trueatau Falsebergantung pada nilai, dan ensure_valid(value)yang memeriksa nilai yang ditentukan dan tidak melakukan apa pun jika nilainya valid, atau melempar pengecualian tertentu jika nilainya tidak cocok dengan aturan yang telah ditentukan.

Saat ini ada dua unit tes yang terkait dengan metode ini:

  • Yang melewati nilai yang tidak valid, dan memastikan bahwa pengecualian dilemparkan.

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • Yang melewati nilai yang valid.

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

Meskipun tes kedua melakukan tugasnya — gagal jika pengecualian dilemparkan, dan berhasil jika ensure_validtidak melempar apa pun — fakta bahwa tidak ada assertbagian dalam terlihat aneh. Seseorang yang membaca kode seperti itu akan segera bertanya pada dirinya sendiri mengapa ada tes yang tampaknya tidak melakukan apa-apa.

Apakah ini praktik saat ini ketika menguji metode yang tidak mengembalikan nilai dan tidak memiliki efek samping? Atau haruskah saya menulis ulang tes dengan cara yang berbeda? Atau cukup beri komentar yang menjelaskan apa yang saya lakukan?

Arseni Mourzenko
sumber
11
Poin pedantic, tetapi jika Anda memiliki fungsi yang tidak memerlukan argumen (simpan untuk selfreferensi) dan tidak mengembalikan hasil, itu bukan fungsi murni.
David Arno
10
@ Davidvido: Ini bukan titik pedantic, itu langsung ke inti pertanyaan: metode ini sulit untuk diuji justru karena tidak murni.
Jörg W Mittag
@DavidArno Poin yang lebih bertele-tele, Anda dapat memiliki metode "tidak melakukan apa-apa" (dengan asumsi "mengembalikan tidak ada hasil" ditafsirkan sebagai "mengembalikan tipe unit" yang disebut voiddalam banyak bahasa dan memiliki aturan konyol.) Atau, Anda dapat memiliki tak terbatas loop (yang bekerja bahkan ketika "tidak mengembalikan hasil" benar-benar berarti tidak mengembalikan hasil.
Derek Elkins meninggalkan SE
Ada satu fungsi murni dari tipe unit -> unitdalam bahasa apa pun yang menganggap unit sebagai tipe data standar alih-alih sesuatu yang ajaib khusus. Sayangnya, ia hanya mengembalikan unit dan tidak melakukan hal lain.
Phoshi

Jawaban:

21

Sebagian besar kerangka uji memiliki pernyataan eksplisit untuk "Tidak melempar", misalnya Jasmine expect(() => {}).not.toThrow();dan nUnit dan teman juga memilikinya.

DeadMG
sumber
1
Dan jika kerangka kerja pengujian tidak memiliki pernyataan seperti itu, selalu memungkinkan untuk membuat metode lokal yang melakukan hal yang persis sama, membuat kode jelas. Ide bagus.
Arseni Mourzenko
7

Ini sangat tergantung pada bahasa dan kerangka kerja yang digunakan. Berbicara dalam hal NUnit, ada beberapa Assert.Throws(...)metode. Anda dapat memberikan mereka metode lambda:

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

yang dieksekusi di dalam Assert.Throws. Panggilan ke lambda kemungkinan besar akan dibungkus oleh try { } catch { }blok dan pernyataan gagal, jika pengecualian tertangkap.

Jika kerangka kerja Anda tidak menyediakan sarana ini, Anda bisa mengatasinya, dengan membungkus panggilan sendiri (Saya menulis dalam C #):

// 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)
{
}

Ini membuat niat lebih jelas, tetapi mengacaukan kode sampai batas tertentu. (Tentu saja Anda dapat membungkus semua ini dalam suatu metode.) Pada akhirnya itu akan terserah Anda.

Edit

Seperti yang ditunjukkan Doc Brown dalam komentar, masalahnya bukan untuk menunjukkan bahwa metode melempar, tetapi tidak melempar. Di NUnit ada juga pernyataan untuk itu

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))
Paul Kertscher
sumber
1

Cukup tambahkan komentar untuk memperjelas mengapa tidak ada penegasan yang dibutuhkan dan mengapa Anda tidak melupakannya.

Seperti yang Anda lihat di jawaban lain, hal lain membuat kode lebih rumit dan berantakan. Dengan komentar di sana, programmer lain akan mengetahui maksud tes.

Yang sedang berkata, tes semacam ini harus menjadi pengecualian (tidak ada kata pun dimaksudkan). Jika Anda menemukan diri Anda menulis sesuatu seperti itu secara teratur, maka mungkin tes tersebut memberi tahu Anda bahwa desainnya tidak optimal.

jhyot
sumber
1

Anda juga dapat menyatakan bahwa beberapa metode dipanggil (atau tidak dipanggil) dengan benar.

Contohnya:

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

Dalam tes Anda:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
Gabriel Robert
sumber