Apakah boleh memiliki beberapa pernyataan dalam satu unit test?

397

Dalam komentar untuk posting hebat ini , Roy Osherove menyebutkan proyek OAPT yang dirancang untuk menjalankan setiap pernyataan dalam satu tes.

Berikut ini ditulis di halaman muka proyek:

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

Dan, juga, Roy menulis dalam komentar:

Pedoman saya biasanya bahwa Anda menguji satu KONSEP logis per tes. Anda dapat memiliki banyak penegasan tentang objek yang sama . mereka biasanya akan menjadi konsep yang sama sedang diuji.

Saya pikir, ada beberapa kasus di mana diperlukan beberapa pernyataan (mis. Penjagaan Pernyataan ), tetapi secara umum saya mencoba menghindari ini. Apa pendapat Anda? Tolong berikan contoh dunia nyata di mana beberapa pernyataan sangat dibutuhkan .

Restuta
sumber
2
Bagaimana Anda mengejek tanpa memiliki banyak pernyataan? Setiap harapan pada mock adalah pernyataan dalam dirinya sendiri, termasuk urutan panggilan yang Anda buat.
Christopher Creutzig
15
Saya telah melihat filosofi satu-metode-per-metode disalahgunakan di masa lalu. Seorang rekan kerja lama menggunakan mekanisme pewarisan funky untuk memungkinkan hal ini. Itu menyebabkan banyak sub-kelas (satu per cabang) dan banyak tes yang melakukan proses set-up / tear-down yang sama hanya untuk memeriksa hasil yang berbeda. Itu lambat, sulit dibaca dan masalah pemeliharaan yang parah. Saya tidak pernah meyakinkan dia untuk kembali ke pendekatan yang lebih klasik. Buku Gerard Meszaros berbicara tentang topik ini secara rinci.
Travis Parks
3
Saya pikir sebagai aturan umum Anda harus mencoba meminimalkan jumlah penegasan per tes. Namun, selama pengujian cukup mempersempit masalah ke tempat tertentu dalam kode, maka itu adalah tes yang bermanfaat.
ConditionRacer
2
Saya telah melihat kasus di mana banyak pernyataan digunakan sebagai ganti RowTest(MbUnit) / TestCase(NUnit) untuk menguji berbagai perilaku kasus tepi. Gunakan alat yang tepat untuk pekerjaan itu! (Sayangnya, MSTest tampaknya belum memiliki kemampuan uji baris.)
GalacticCowboy
@ GalacticCowboy Anda bisa mendapatkan fungsionalitas serupa RowTestdan TestCasemenggunakan sumber data uji . Saya menggunakan file CSV sederhana dengan kesuksesan besar.
julealgon

Jawaban:

236

Saya tidak berpikir itu hal yang buruk , tetapi saya pikir kita harus berusaha keras untuk hanya memiliki satu menegaskan dalam tes 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 bau kode (tes?) Ketika Anda memiliki sekitar lima atau lebih menegaskan dalam tes Anda.

Bagaimana Anda memecahkan banyak pernyataan?

Jaco Pretorius
sumber
9
Seperti jawaban ini - yaitu OK, tetapi tidak, dalam contoh umum, baik (-:
Murph
5
Ehh? Kenapa kamu ingin melakukan itu? eksekusi metode persis sama?
jgauffin
198
Satu pengujian per unit adalah cara yang bagus untuk menguji kemampuan pembaca untuk menggulir ke atas dan ke bawah.
Tom
3
Adakah alasan di balik ini? Seperti, jawaban saat ini hanya menyatakan apa yang seharusnya, tetapi tidak mengapa.
Steven Jeuris
37
Sangat tidak setuju. Jawabannya tidak mencantumkan keuntungan memiliki pernyataan tunggal, dan kerugian yang jelas adalah bahwa Anda perlu menyalin tes tempel hanya karena jawaban di internet mengatakan demikian. Jika Anda perlu menguji beberapa bidang hasil atau beberapa hasil operasi tunggal, Anda benar-benar harus menyatakan semuanya dalam pernyataan independen, karena itu memberikan informasi yang jauh lebih berguna daripada mengujinya dalam pernyataan gumpalan besar. Dalam pengujian yang baik, Anda menguji satu operasi, bukan hasil tunggal dari operasi.
Peter
296

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

Kuncinya adalah bahwa Anda hanya memiliki satu tindakan, dan kemudian Anda memeriksa hasil dari tindakan itu menggunakan pernyataan. Tapi itu adalah "Atur, Bertindak, Tegas, Akhir dari ujian ". 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 dapat 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 dan kerangka kerja lain. Sintaksnya mungkin sangat mirip juga.

Anthony
sumber
32
Kuncinya adalah bahwa Anda hanya memiliki satu tindakan, dan kemudian Anda memeriksa hasil dari tindakan itu menggunakan pernyataan.
Amitābha
1
Saya pikir ini juga menarik, untuk memiliki lebih dari satu pernyataan, jika Arrange adalah waktu yang mahal.
Rekshino
1
@Rekshino, jika pengaturan itu mahal waktu, kita dapat membagikan kode susunan, misalnya, dengan memasukkan kode susunan ke dalam rutin inisialisasi pengujian.
Shaun Luttin
2
Jadi jika saya bandingkan dengan jawaban Jaco, "hanya satu yang menegaskan" menjadi "hanya satu kelompok yang menegaskan" yang lebih masuk akal bagi saya.
Walfrat
2
Ini adalah jawaban yang bagus, tetapi saya tidak setuju bahwa satu pernyataan saja tidak lebih baik. Contoh penegasan yang buruk tidak lebih baik, tetapi itu tidak berarti penegasan tunggal tidak akan lebih baik jika dilakukan dengan benar. Banyak perpustakaan mengijinkan pencocokan kustom / pencocokan sehingga sesuatu dapat dibuat jika belum ada. Misalnya Assert.IsBetween(10, 100, value)yang mencetak Expected 8 to be between 10 and 100 adalah lebih baik dari dua terpisah menegaskan dalam pendapat saya. Anda tentu bisa berpendapat bahwa itu tidak perlu, tetapi biasanya patut dipertimbangkan apakah mudah untuk mengurangi menjadi satu pernyataan sebelum membuat seluruh rangkaian dari mereka.
Thor84no
85

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 rumit dapat diubah menjadi predikat yang diharapkan.

Saya hanya menguji satu unit ( ToPredicatemetode), tetapi saya mencakup semua yang dapat saya pikirkan dalam pengujian.

Matt Ellen
sumber
46
Berbagai pernyataan buruk karena deteksi kesalahan. Jika Anda telah gagal pada Assert.IsTrue pertama Anda, konfirmasi lain tidak akan dieksekusi, dan Anda tidak akan mendapatkan informasi apa pun dari mereka. Di sisi lain jika Anda memiliki 5 tes, bukan 1 dengan 5 menegaskan Anda bisa mendapatkan sesuatu yang berguna
Sly
6
Apakah Anda menganggapnya masih buruk jika semua pernyataan menguji fungsionalitas yang sama? Seperti di atas, contoh menguji kondisi dan jika ada yang gagal, Anda harus memperbaikinya. Apakah penting bagi Anda bahwa Anda mungkin melewatkan 2 menegaskan terakhir jika yang sebelumnya gagal?
ngeri
106
Saya memperbaiki masalah saya satu per satu. Jadi fakta bahwa tes bisa gagal lebih dari satu kali tidak mengganggu saya. Jika saya membaginya saya akan memiliki kesalahan yang sama muncul, tetapi sekaligus. Saya merasa lebih mudah untuk memperbaiki hal-hal secara bertahap. Saya akui, bahwa dalam contoh ini, dua penegasan terakhir mungkin dapat di-refactored ke dalam pengujian mereka sendiri.
Matt Ellen
19
Kasus Anda sangat representatif, itulah mengapa NUnit memiliki atribut tambahan TestCase - nunit.org/?p=testCase&r=2.5
Restuta
10
Kerangka pengujian google C ++ memiliki ASSERT () dan EXPECT (). ASSERT () berhenti pada kegagalan sementara EXPECT () melanjutkan. Itu sangat berguna ketika Anda ingin memvalidasi lebih dari satu hal dalam ujian.
ratkok
21

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 membuat assertAllNotificationSent()kesuksesan ATAU 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.)

BlairHippo
sumber
Ketika Anda melakukan itu, Anda sebaiknya menggunakan kerangka kerja pengujian dengan tes dependen, lebih baik (mis. Testng mendukung fitur ini)
Kemoda
8
Saya menulis tes seperti ini juga sehingga Anda dapat yakin dengan apa yang dilakukan kode dan menyatakan perubahan, saya tidak berpikir ini adalah unit test, tetapi tes integrasi.
mdma
Apa pendapat Anda tentang refactoring menjadi asertAlarmStatus (int numberOfAlarmMessages) ;?
Borjab
1
Pernyataan Anda akan menghasilkan nama uji yang sangat bagus.
Bjorn
Lebih baik membiarkan tes berjalan, bahkan pada input yang tidak valid. Ini memberi Anda lebih banyak informasi seperti itu (dan terutama jika masih lewat ketika Anda tidak mengharapkannya).
CurtainDog
8

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 yang service.process()mengembalikan nomor yang benar dalam Innerinstance 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() );
}
Betlista
sumber
2
Dan itu hal yang baik, Anda seharusnya tidak memiliki logika kondisional dalam ujian Anda. Itu membuat tes lebih kompleks dan kurang mudah dibaca. Dan saya kira Roy menguraikannya dengan benar dalam posting blognya bahwa banyak pernyataan pada satu objek tidak masalah hampir sepanjang waktu. Jadi yang Anda miliki hanyalah penjagaan dan tidak apa-apa untuk memilikinya.
Restuta
2
Assert.notNullS Anda berlebihan, pengujian Anda akan gagal dengan NPE jika batal.
sara
1
Juga, contoh pertama Anda (dengan if) akan lulus jika resadalahnull
sara
1
@ Kai notNull's redundan, saya setuju, tapi saya merasa lebih bersih untuk menegaskan (dan jika saya tidak malas juga dengan pesan yang tepat) alih-alih pengecualian ...
Betlista
1
Pernyataan yang gagal juga melempar pengecualian, dan dalam kedua kasus Anda mendapatkan tautan langsung ke baris yang tepat yang melemparkannya dengan jejak tumpukan yang menyertainya sehingga secara pribadi saya lebih suka tidak mengacaukan tes dengan prasyarat yang akan diperiksa pula. Saya lebih suka oneliner à la Assert.assertEquals(..., service.process().getInner());, mungkin dengan variabel yang diekstraksi jika garisnya menjadi "terlalu panjang"
sara
6

Saya pikir ada banyak kasus di mana menulis beberapa pernyataan valid dalam aturan bahwa tes hanya boleh 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 terlalu baik dalam definisi "satu alasan".

Pete
sumber
3

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 keadaan yang dijelaskan oleh nama kelas. Kemudian Anda memiliki beberapa metode [Uji] yang masing-masing menyatakan hal-hal yang 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 lebih baik ketika Anda mulai menyadari semua kasus tepi kecil yang ingin Anda uji dan ketika Anda menemukan bug. kecuali mungkin itu dinamai setelah konsep bukan hanya pembacaan kode bahasa Inggris. 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 lebih baik ketika Anda mulai menyadari semua kasus tepi kecil yang ingin Anda uji dan ketika Anda menemukan bug. kecuali mungkin itu dinamai setelah konsep bukan hanya pembacaan kode bahasa Inggris. 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 lebih baik ketika Anda mulai menyadari semua kasus tepi kecil yang ingin Anda uji dan ketika Anda menemukan bug. dan apa yang Anda harapkan hanya dengan melihat kelas dan nama metode. Ini juga akan skala lebih baik ketika Anda mulai menyadari semua kasus tepi kecil yang ingin Anda uji dan ketika Anda menemukan bug. dan apa yang Anda harapkan hanya dengan melihat kelas dan nama metode. Ini juga akan skala lebih baik ketika Anda mulai menyadari semua kasus tepi kecil yang ingin Anda uji dan ketika Anda menemukan bug.

Biasanya ini berarti bahwa baris terakhir kode di dalam metode [SetUp] harus menyimpan nilai properti atau nilai pengembalian dalam variabel instance pribadi dari [TestFixture]. Maka Anda mungkin menegaskan 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 diuji diatur sekarang 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 dalam 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 duga akan dibuang. Ini bisa menggoda Anda untuk tidak mengikuti model di atas. Namun, itu masih bisa 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));
        }
    }
}
INTPnerd
sumber
Bagaimana Anda menangani input TestCase dalam kasus ini?
Simon Gillbee
Jadi, di organisasi saya saat ini, kami memiliki 20.000 unit test yang sangat mirip dengan yang Anda perlihatkan. Ini mimpi buruk. Sebagian besar kode pengaturan tes disalin / ditempelkan, menghasilkan pengaturan tes yang salah dan tes tidak valid yang lulus. Untuk setiap [Test]metode, kelas itu instantiated dan [SetUp]metode dieksekusi lagi. Ini membunuh .NET Garbage Collector dan menyebabkan pengujian berjalan sangat lambat: 5+ menit secara lokal, 20+ menit di server build. Tes 20K akan berjalan dalam 2 - 3 menit. Saya tidak akan merekomendasikan gaya pengujian ini sama sekali, terutama untuk test suite besar.
fourpastmidnight
@fourpastmidnight sebagian besar dari apa yang Anda katakan tampak seperti kritik yang valid, tetapi intinya tentang menyalin dan menempel kode pengaturan dan karena itu salah, itu bukan masalah struktur tetapi dari programmer yang tidak bertanggung jawab (yang bisa merupakan hasil dari manajer yang tidak bertanggung jawab atau lingkungan negatif) lebih dari programmer buruk). Jika orang hanya menyalin dan menempel kode dan berharap itu benar dan tidak repot-repot memahami kode, dalam konteks apa pun dengan alasan apa pun, mereka perlu dilatih untuk tidak melakukan ini atau melepaskan jika mereka tidak bisa terlatih. Itu bertentangan dengan setiap prinsip pemrograman yang baik.
still_dreaming_1
Tetapi secara umum, saya setuju, ini adalah kerja keras gila yang akan menghasilkan banyak mengasapi / bagasi / duplikasi yang akan menyebabkan semua jenis masalah. Saya dulu gila dan merekomendasikan hal-hal seperti ini. Itulah yang saya harap dapat saya katakan setiap hari tentang diri saya sehari sebelumnya karena itu berarti saya tidak pernah berhenti menemukan cara yang lebih baik dalam melakukan sesuatu.
still_dreaming_1
@ still_dreaming_1 wrt "programmer tidak bertanggung jawab": Saya setuju bahwa perilaku ini adalah masalah besar. Namun, struktur tes semacam ini benar-benar mengundang perilaku semacam ini, baik atau buruk. Di samping praktik pengembangan yang buruk, keberatan utama saya terhadap formulir ini adalah benar-benar membunuh kinerja pengujian. Tidak ada yang lebih buruk dari test suite yang berjalan lambat. Rangkaian tes yang berjalan lambat berarti orang tidak akan menjalankan tes secara lokal dan bahkan tergoda untuk melewatkannya pada build menengah - lagi-lagi, masalah orang, tetapi itu terjadi - yang semuanya dapat dihindari dengan memastikan Anda memiliki tes yang berjalan cepat untuk memulai dengan.
fourpastmidnight
2

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 di setiap tes, biasanya lebih mudah untuk menentukan apa yang salah.

Saya tidak bisa memikirkan skenario di mana pernyataan banyak 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.

Guffa
sumber
1
Jika Anda menggabungkan beberapa kondisi menjadi satu pernyataan, maka pada kegagalan semua yang Anda tahu adalah bahwa satu gagal. Dengan beberapa penegasan, Anda tahu secara khusus tentang beberapa dari mereka (yang hingga dan termasuk kegagalan). Pertimbangkan untuk memeriksa array yang dikembalikan berisi nilai tunggal: centang bukan nol, maka ia memiliki tepat satu elemen dan kemudian nilai elemen itu. (Bergantung pada platform) hanya memeriksa nilainya secara langsung dapat memberikan null dereference (kurang membantu daripada pernyataan null yang gagal) dan tidak memeriksa panjang array.
Richard
@ Richard: Mendapatkan hasil dan kemudian mengekstraksi sesuatu dari hasil itu akan menjadi proses dalam beberapa langkah, jadi saya membahasnya pada paragraf kedua dalam jawabannya.
Guffa
2
Rule of thumb: jika Anda memiliki beberapa pernyataan dalam tes, masing-masing harus memiliki pesan yang berbeda. Maka Anda tidak memiliki masalah ini.
Anthony
Dan menggunakan pelari uji kualitas seperti NCrunch akan menunjukkan dengan tepat pada baris apa tes gagal, baik dalam kode uji dan dalam kode yang diuji.
fourpastmidnight
2

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 kebanyakan IDE akan menampilkan perbedaan string dalam dialog perbandingan berdampingan.

Aaron Digulla
sumber
2

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 paling esensial adalah 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.

hagubear
sumber
1

Tujuan dari unit test adalah untuk memberi Anda sebanyak mungkin informasi tentang apa yang gagal tetapi juga untuk membantu menentukan secara akurat 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 dari uji coba untuk menyelesaikan semua masalah.

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

jpierson
sumber
1

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

Tes unit yang tepat harus gagal karena satu alasan, karena itu 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 kedua 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.

CurtainDog
sumber
0

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.

cslotty
sumber
-1

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.

GSF
sumber
-3

Saya bahkan tidak setuju dengan "gagal hanya karena satu alasan" secara umum. Yang lebih penting adalah bahwa tes itu singkat dan membaca jelas imo.

Hal ini tidak selalu dapat dicapai dan ketika pengujian menjadi rumit, nama deskriptif (panjang) dan pengujian lebih sedikit akan lebih masuk akal.

Johan Larsson
sumber