it-swarm-id.com

Apakah boleh memiliki beberapa pernyataan dalam pengujian unit tunggal?

Dalam komentar untuk postingan hebat ini , Roy Osherove menyebutkan proyek [~ # ~] oapt [~ # ~]) yang dirancang untuk menjalankan setiap pernyataan dalam satu pengujian.

Berikut ini ditulis di halaman muka proyek:

Tes unit yang tepat harus gagal karena satu alasan, itulah sebabnya Anda harus menggunakan satu penegasan per pengujian unit.

Dan, juga, Roy menulis dalam komentar:

Pedoman saya biasanya bahwa Anda menguji satu KONSEP logis per tes. Anda dapat memiliki beberapa pernyataan pada objek yang sama. mereka biasanya akan konsep yang sama sedang diuji.

Saya berpikir bahwa, ada beberapa kasus di mana beberapa pernyataan diperlukan (mis. Pernyataan Sikap ), tetapi secara umum saya mencoba menghindari ini. Apa pendapat Anda? Berikan contoh dunia nyata di mana banyak pernyataan benar-benar dibutuhkan.

430
Restuta

Saya tidak berpikir itu selalu merupakan hal buruk , tapi saya pikir kita harus berusaha keras hanya dengan satu pernyataan dalam pengujian kami. Ini berarti Anda menulis lebih banyak tes dan pengujian kami hanya akan menguji satu per satu.

Karena itu, saya akan mengatakan mungkin setengah dari tes saya sebenarnya hanya memiliki satu pernyataan. Saya pikir itu hanya menjadi kode (test?) Bau ketika Anda memiliki sekitar lima atau lebih yang menegaskan dalam tes Anda.

Bagaimana Anda menyelesaikan beberapa menegaskan?

246
Jaco Pretorius

Tes harus gagal hanya karena satu alasan, tetapi itu tidak selalu berarti bahwa hanya ada satu pernyataan Assert. IMHO lebih penting untuk memegang pola " Atur, Bertindak, Tegas ".

Kuncinya adalah bahwa Anda hanya memiliki satu tindakan, dan kemudian Anda memeriksa hasil dari tindakan itu menggunakan menegaskan. Tetapi itu adalah "Atur, Bertindak, Tegas, Akhir tes". Jika Anda tergoda untuk melanjutkan pengujian dengan melakukan tindakan lain dan lebih banyak konfirmasi sesudahnya, lakukan itu sebagai tes terpisah.

Saya senang melihat beberapa pernyataan tegas yang merupakan bagian dari pengujian tindakan yang sama. misalnya.

[Test]
public void ValueIsInRange()
{
  int value = GetValueToTest();

  Assert.That(value, Is.GreaterThan(10), "value is too small");
  Assert.That(value, Is.LessThan(100), "value is too large");
} 

atau

[Test]
public void ListContainsOneValue()
{
  var list = GetListOf(1);

  Assert.That(list, Is.Not.Null, "List is null");
  Assert.That(list.Count, Is.EqualTo(1), "Should have one item in list");
  Assert.That(list[0], Is.Not.Null, "Item is null");
} 

Anda bisa menggabungkan ini menjadi satu pernyataan, tetapi itu hal yang berbeda dari bersikeras bahwa Anda harus atau harus. Tidak ada peningkatan dari menggabungkan mereka.

misalnya Yang pertama bisa menjadi

Assert.IsTrue((10 < value) && (value < 100), "Value out of range"); 

Tapi ini tidak lebih baik - pesan kesalahan dari itu kurang spesifik, dan tidak memiliki kelebihan lain. Saya yakin Anda dapat memikirkan contoh-contoh lain di mana menggabungkan dua atau tiga (atau lebih) menegaskan menjadi satu kondisi boolean besar membuatnya lebih sulit untuk dibaca, lebih sulit untuk mengubah dan lebih sulit untuk mengetahui mengapa itu gagal. Mengapa ini hanya demi aturan?

[~ # ~] nb [~ # ~] : Kode yang saya tulis di sini adalah C # dengan NUnit, tetapi prinsip-prinsipnya akan berlaku dengan bahasa lain dan kerangka kerja. Sintaksnya mungkin sangat mirip juga.

314
Anthony

Saya tidak pernah berpikir bahwa lebih dari satu pernyataan adalah hal yang buruk.

Saya selalu melakukannya:

public void ToPredicateTest()
{
    ResultField rf = new ResultField(ResultFieldType.Measurement, "name", 100);
    Predicate<ResultField> p = (new ConditionBuilder()).LessThanConst(400)
                                                       .Or()
                                                       .OpenParenthesis()
                                                       .GreaterThanConst(500)
                                                       .And()
                                                       .LessThanConst(1000)
                                                       .And().Not()
                                                       .EqualsConst(666)
                                                       .CloseParenthesis()
                                                       .ToPredicate();
    Assert.IsTrue(p(ResultField.FillResult(rf, 399)));
    Assert.IsTrue(p(ResultField.FillResult(rf, 567)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 400)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 666)));
    Assert.IsFalse(p(ResultField.FillResult(rf, 1001)));

    Predicate<ResultField> p2 = (new ConditionBuilder()).EqualsConst(true).ToPredicate();

    Assert.IsTrue(p2(new ResultField(ResultFieldType.Confirmation, "Is True", true)));
    Assert.IsFalse(p2(new ResultField(ResultFieldType.Confirmation, "Is False", false)));
}

Di sini saya menggunakan beberapa pernyataan untuk memastikan kondisi kompleks dapat diubah menjadi predikat yang diharapkan.

Saya hanya menguji satu unit (metode ToPredicate), tetapi saya membahas semua yang dapat saya pikirkan dalam pengujian.

85
Matt Ellen

Ketika saya menggunakan pengujian unit untuk memvalidasi perilaku tingkat tinggi, saya benar-benar memasukkan beberapa pernyataan dalam satu tes. Inilah tes yang sebenarnya saya gunakan untuk beberapa kode pemberitahuan darurat. Kode yang berjalan sebelum pengujian menempatkan sistem ke dalam kondisi di mana jika prosesor utama dijalankan, alarm akan dikirim.

@Test
public void testAlarmSent() {
    assertAllUnitsAvailable();
    assertNewAlarmMessages(0);

    pulseMainProcessor();

    assertAllUnitsAlerting();
    assertAllNotificationsSent();
    assertAllNotificationsUnclosed();
    assertNewAlarmMessages(1);
}

Ini mewakili kondisi yang perlu ada pada setiap langkah dalam proses agar saya yakin bahwa kode berperilaku seperti yang saya harapkan. Jika satu pernyataan gagal, saya tidak peduli bahwa yang tersisa bahkan tidak akan dijalankan; karena keadaan sistem tidak lagi valid, pernyataan berikutnya tidak akan memberi tahu saya sesuatu yang berharga. * Jika assertAllUnitsAlerting() gagal, maka saya tidak akan tahu apa yang harus dibuat dari assertAllNotificationSent()'s success OR kegagalan sampai saya menentukan apa yang menyebabkan kesalahan sebelumnya dan memperbaikinya.

(* - Oke, mereka mungkin berguna dalam men-debug masalah. Tetapi informasi yang paling penting, bahwa tes gagal, telah diterima.)

21
BlairHippo

Alasan lain mengapa saya berpikir, bahwa banyak menegaskan dalam satu metode bukanlah hal yang buruk dijelaskan dalam kode berikut:

class Service {
    Result process();
}

class Result {
    Inner inner;
}

class Inner {
    int number;
}

Dalam pengujian saya, saya hanya ingin menguji bahwa service.process() mengembalikan angka yang benar dalam Inner instance kelas.

Alih-alih menguji ...

@Test
public void test() {
    Result res = service.process();
    if ( res != null && res.getInner() != null ) Assert.assertEquals( ..., res.getInner() );
}

Aku melakukan

@Test
public void test() {
    Result res = service.process();
    Assert.notNull(res);
    Assert.notNull(res.getInner());
    Assert.assertEquals( ..., res.getInner() );
}
8
Betlista

Saya pikir ada banyak kasus di mana menulis beberapa pernyataan valid dalam aturan bahwa tes hanya gagal karena satu alasan.

Misalnya, bayangkan fungsi yang mem-parsing string tanggal:

function testParseValidDateYMD() {
    var date = Date.parse("2016-01-02");

    Assert.That(date.Year).Equals(2016);
    Assert.That(date.Month).Equals(1);
    Assert.That(date.Day).Equals(0);
}

Jika tes gagal itu karena satu alasan, penguraian salah. Jika Anda berpendapat bahwa tes ini dapat gagal karena tiga alasan berbeda, Anda akan IMHO terlalu halus dalam definisi Anda tentang "satu alasan".

6
Pete

Saya tidak tahu situasi apa pun di mana itu adalah ide yang baik untuk memiliki beberapa pernyataan di dalam metode [Test] itu sendiri. Alasan utama orang-orang suka memiliki beberapa Pernyataan adalah mereka mencoba untuk memiliki satu kelas [TestFixture] untuk setiap kelas yang diuji. Sebagai gantinya, Anda dapat memecah tes Anda menjadi lebih banyak kelas [TestFixture]. Ini memungkinkan Anda melihat banyak cara di mana kode tersebut mungkin tidak bereaksi seperti yang Anda harapkan, alih-alih hanya di mana pernyataan pertama gagal. Cara Anda mencapainya adalah Anda memiliki setidaknya satu direktori per kelas yang sedang diuji dengan banyak kelas [TestFixture] di dalamnya. Setiap kelas [TestFixture] akan dinamai sesuai dengan keadaan spesifik objek yang akan Anda uji. Metode [SetUp] akan membuat objek ke status yang dijelaskan oleh nama kelas. Kemudian Anda memiliki beberapa metode [Tes] yang masing-masing menyatakan hal-hal berbeda yang Anda harapkan benar, mengingat keadaan objek saat ini. Setiap metode [Tes] dinamai sesuai dengan apa yang ditegaskannya, kecuali mungkin itu dinamai berdasarkan konsep alih-alih hanya pembacaan bahasa Inggris dari kode. Maka setiap implementasi metode [Test] hanya membutuhkan satu baris kode di mana ia menyatakan sesuatu. Keuntungan lain dari pendekatan ini adalah membuat tes sangat mudah dibaca karena menjadi sangat jelas apa yang Anda uji, dan apa yang Anda harapkan hanya dengan melihat kelas dan nama metode. Ini juga akan skala yang lebih baik ketika Anda mulai menyadari semua kasus Edge kecil yang ingin Anda uji dan ketika Anda menemukan bug.

Biasanya ini berarti bahwa baris terakhir dari kode di dalam metode [SetUp] harus menyimpan nilai properti atau mengembalikan nilai dalam variabel instance pribadi dari [TestFixture]. Maka Anda dapat menyatakan beberapa hal berbeda tentang variabel instance ini dari metode [Tes] yang berbeda. Anda juga dapat membuat pernyataan tentang apa properti yang berbeda dari objek yang sedang diuji diatur ke sekarang bahwa itu dalam keadaan yang diinginkan.

Kadang-kadang Anda perlu membuat pernyataan di sepanjang jalan saat Anda mendapatkan objek yang diuji dalam keadaan yang diinginkan untuk memastikan Anda tidak mengacaukan sebelum memasukkan objek ke keadaan yang diinginkan. Dalam hal itu, pernyataan ekstra tersebut akan muncul di dalam metode [SetUp]. Jika ada yang salah di dalam metode [SetUp], akan menjadi jelas bahwa ada sesuatu yang salah dengan pengujian sebelum objek masuk ke kondisi yang diinginkan yang ingin Anda uji.

Satu masalah lain yang mungkin Anda temui adalah Anda mungkin sedang menguji Pengecualian yang Anda harapkan akan dibuang. Ini bisa menggoda Anda untuk tidak mengikuti model di atas. Namun, itu masih dapat dicapai dengan menangkap pengecualian di dalam metode [SetUp] dan menyimpannya ke dalam variabel instan. Ini akan memungkinkan Anda untuk menegaskan hal-hal yang berbeda tentang pengecualian, masing-masing dengan metode [Tes] sendiri. Anda kemudian dapat juga menegaskan hal-hal lain tentang objek yang sedang diuji untuk memastikan tidak ada efek samping yang tidak diinginkan dari pengecualian yang dilemparkan.

Contoh (ini akan dipecah menjadi beberapa file):

namespace Tests.AcctTests
{
    [TestFixture]
    public class no_events
    {
        private Acct _acct;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
        }

        [Test]
        public void balance_0() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_0
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(0m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }

    [TestFixture]
    public class try_withdraw_negative
    {
        private Acct _acct;
        private List<string> _problems;

        [SetUp]
        public void SetUp() {
            _acct = new Acct();
            Assert.That(_acct.Balance, Is.EqualTo(0));
            _problems = _acct.Withdraw(-0.01m);
        }

        [Test]
        public void has_problem() {
            Assert.That(_problems, Is.EquivalentTo(new string[] { "Withdraw amount must be greater than zero." }));
        }

        [Test]
        public void balance_not_changed() {
            Assert.That(_acct.Balance, Is.EqualTo(0m));
        }
    }
}
3

Jika Anda memiliki beberapa pernyataan dalam satu fungsi tes, saya berharap mereka secara langsung relevan dengan tes yang Anda lakukan. Sebagai contoh,

@Test
test_Is_Date_segments_correct {

   // It is okay if you have multiple asserts checking dd, mm, yyyy, hh, mm, ss, etc. 
   // But you would not have any assert statement checking if it is string or number,
   // that is a different test and may be with multiple or single assert statement.
}

Memiliki banyak tes (bahkan ketika Anda merasa itu mungkin berlebihan) bukanlah hal yang buruk. Anda dapat berargumen bahwa memiliki tes vital dan yang paling esensial lebih penting. JADI, ketika Anda menegaskan, pastikan pernyataan tegas Anda ditempatkan dengan benar daripada terlalu khawatir tentang banyak pernyataan. Jika Anda membutuhkan lebih dari satu, gunakan lebih dari satu.

2
hagubear

Memiliki beberapa pernyataan dalam tes yang sama hanya merupakan masalah ketika tes gagal. Maka Anda mungkin harus men-debug tes atau menganalisis pengecualian untuk mengetahui pernyataan mana yang gagal. Dengan satu pernyataan dalam setiap tes, biasanya lebih mudah untuk menentukan apa yang salah.

Saya tidak bisa memikirkan skenario di mana banyak pernyataan benar-benar diperlukan, karena Anda selalu dapat menulis ulang mereka sebagai beberapa kondisi dalam pernyataan yang sama. Namun mungkin lebih disukai jika Anda misalnya memiliki beberapa langkah untuk memverifikasi data perantara antara langkah-langkah daripada mengambil risiko bahwa langkah-langkah selanjutnya macet karena input yang buruk.

2
Guffa

Jika tes Anda gagal, Anda tidak akan tahu apakah pernyataan berikut juga akan rusak. Seringkali, itu berarti Anda akan kehilangan informasi berharga untuk mencari tahu sumber masalahnya. Solusi saya adalah menggunakan satu pernyataan tetapi dengan beberapa nilai:

String actual = "val1="+val1+"\nval2="+val2;
assertEquals(
    "val1=5\n" +
    "val2=hello"
    , actual
);

Itu memungkinkan saya untuk melihat semua pernyataan yang gagal sekaligus. Saya menggunakan beberapa baris karena sebagian besar IDE akan menampilkan perbedaan string dalam dialog perbandingan berdampingan.

2
Aaron Digulla

Tujuan dari unit test adalah untuk memberi Anda sebanyak mungkin informasi tentang apa yang gagal tetapi juga untuk membantu secara akurat menentukan masalah yang paling mendasar terlebih dahulu. Ketika Anda tahu secara logis bahwa satu pernyataan akan gagal mengingat bahwa pernyataan lain gagal atau dengan kata lain ada hubungan ketergantungan antara tes maka masuk akal untuk menggulirkan ini sebagai beberapa pernyataan dalam satu tes. Ini bermanfaat untuk tidak membuang sampah sembarangan hasil tes dengan kegagalan yang jelas yang bisa dihilangkan jika kita menebus pernyataan pertama dalam satu pengujian. Dalam kasus di mana hubungan ini tidak ada preferensi akan secara alami kemudian untuk memisahkan pernyataan ini menjadi tes individu karena jika menemukan kegagalan ini akan memerlukan beberapa iterasi uji coba untuk menyelesaikan semua masalah.

Jika Anda kemudian juga mendesain unit/kelas sedemikian rupa sehingga tes yang terlalu rumit perlu ditulis, itu membuat beban lebih sedikit selama pengujian dan mungkin mempromosikan desain yang lebih baik.

1
jpierson

Ya, boleh saja memiliki beberapa pernyataan selama tes gagal memberi Anda cukup informasi untuk dapat mendiagnosis kegagalan. Ini akan tergantung pada apa yang Anda uji dan apa mode kegagalannya.

Tes unit yang tepat harus gagal karena satu alasan, itulah mengapa Anda harus menggunakan satu pengujian per unit.

Saya tidak pernah menemukan formulasi seperti itu untuk membantu (bahwa kelas harus memiliki satu alasan untuk berubah adalah contoh dari pepatah yang tidak membantu seperti itu). Pertimbangkan pernyataan bahwa dua string sama, ini secara semantik setara dengan menyatakan bahwa panjang dua string adalah sama dan setiap karakter pada indeks yang sesuai sama.

Kita dapat menggeneralisasi dan mengatakan bahwa sistem pernyataan ganda mana saja dapat ditulis ulang sebagai pernyataan tunggal, dan pernyataan tunggal mana pun dapat diuraikan menjadi seperangkat pernyataan yang lebih kecil.

Jadi, fokus saja pada kejelasan kode dan kejelasan hasil tes, dan biarkan itu memandu jumlah pernyataan yang Anda gunakan daripada sebaliknya.

1
CurtainDog

Pertanyaan ini terkait dengan masalah klasik keseimbangan antara masalah kode spaghetti dan lasagna.

Memiliki beberapa pernyataan dapat dengan mudah masuk ke masalah spageti di mana Anda tidak memiliki gagasan tentang tes apa, tetapi memiliki satu pernyataan per tes dapat membuat pengujian Anda sama-sama tidak terbaca memiliki beberapa tes dalam lasagna besar membuat temuan tes mana yang melakukan apa yang tidak mungkin .

Ada beberapa pengecualian, tetapi dalam hal ini menjaga pendulum di tengah adalah jawabannya.

0
gsf

Jawabannya sangat sederhana - jika Anda menguji fungsi yang mengubah lebih dari satu atribut, dari objek yang sama, atau bahkan dua objek yang berbeda, dan kebenaran fungsi tergantung pada hasil dari semua perubahan itu, maka Anda ingin menegaskan bahwa setiap perubahan itu telah dilakukan dengan benar!

Saya mendapatkan ide konsep yang logis, tetapi kesimpulan sebaliknya akan mengatakan bahwa tidak ada fungsi yang harus berubah lebih dari satu objek. Tapi itu tidak mungkin untuk diterapkan dalam semua kasus, dalam pengalaman saya.

Ambil konsep logis dari transaksi bank - menarik jumlah dari satu rekening bank dalam banyak kasus HARUS menyertakan penambahan jumlah itu ke akun lain. Anda TIDAK PERNAH ingin memisahkan kedua hal itu, mereka membentuk unit atom. Anda mungkin ingin membuat dua fungsi (withdraw/addMoney) dan karenanya menulis dua tes unit yang berbeda - sebagai tambahan. Tetapi kedua tindakan tersebut harus dilakukan dalam satu transaksi dan Anda juga ingin memastikan bahwa transaksi tersebut berhasil. Dalam hal ini tidak cukup untuk memastikan langkah-langkah individual berhasil. Anda harus memeriksa kedua rekening bank, dalam pengujian Anda.

Mungkin ada contoh yang lebih rumit yang Anda tidak akan uji dalam tes unit, di tempat pertama, tetapi dalam tes integrasi atau penerimaan. Tapi batas-batas itu lancar, IMHO! Tidak mudah untuk memutuskan, ini masalah keadaan dan mungkin preferensi pribadi. Menarik uang dari satu dan menambahkannya ke akun lain masih merupakan fungsi yang sangat sederhana dan jelas merupakan kandidat untuk pengujian unit.

0
cslotty