Bagaimana saya harus menguji kode ulir?

704

Sejauh ini saya menghindari mimpi buruk yang menguji kode multi-threaded karena sepertinya terlalu banyak ladang ranjau. Saya ingin bertanya bagaimana orang telah pergi tentang pengujian kode yang bergantung pada utas untuk eksekusi yang sukses, atau hanya bagaimana orang pergi tentang menguji jenis-jenis masalah yang hanya muncul ketika dua utas berinteraksi dengan cara tertentu?

Ini sepertinya masalah yang sangat penting bagi programmer saat ini, akan sangat berguna untuk menyatukan pengetahuan kita tentang imho yang satu ini.

jkp
sumber
2
Saya berpikir untuk mengirim pertanyaan tentang masalah yang sama persis ini. Sementara Will membuat banyak poin bagus di bawah, saya pikir kita bisa melakukan yang lebih baik. Saya setuju tidak ada "pendekatan" tunggal untuk menangani hal ini dengan bersih. Namun, "menguji sebaik mungkin" adalah mengatur bilah sangat rendah. Saya akan kembali dengan temuan saya.
Zach Burlingame
Di Jawa: Paket java.util.concurrent berisi beberapa Kelas yang tidak diketahui, yang dapat membantu untuk menulis JUnit-Tests deterministik. Lihatlah - CountDownLatch - Semaphore - Exchanger
Synox
Bisakah Anda memberikan tautan ke pertanyaan terkait pengujian unit sebelumnya?
Andrew Grimm
7
Saya pikir ini penting untuk dicatat bahwa pertanyaan ini sudah berumur 8 tahun, dan aplikasi perpustakaan sudah cukup lama. Dalam "era modern" (2016) pengembangan multi-utas muncul terutama dalam sistem embedded. Tetapi jika Anda bekerja di desktop atau aplikasi telepon, jelajahi alternatifnya terlebih dahulu. Lingkungan aplikasi seperti .NET sekarang termasuk alat untuk mengelola atau sangat menyederhanakan mungkin 90% dari skenario multi-threading umum. (asnync / tunggu, PLinq, IObservable, TPL ...). Kode multi-utas sulit. Jika Anda tidak menemukan kembali roda, Anda tidak perlu tes ulang.
Paul Williams

Jawaban:

245

Dengar, tidak ada cara mudah untuk melakukan ini. Saya sedang mengerjakan proyek yang inheren multithreaded. Acara datang dari sistem operasi dan saya harus memprosesnya secara bersamaan.

Cara paling sederhana untuk menangani pengujian kode aplikasi multithreaded yang kompleks adalah: Jika terlalu rumit untuk diuji, Anda salah melakukannya. Jika Anda memiliki satu instance yang memiliki beberapa utas yang bertindak atasnya, dan Anda tidak dapat menguji situasi di mana utas ini saling bertindihan, maka desain Anda harus diulang. Baik sesederhana dan serumit ini.

Ada banyak cara untuk memprogram untuk multithreading yang menghindari utas berjalan melalui instance secara bersamaan. Yang paling sederhana adalah membuat semua benda Anda tidak berubah. Tentu saja, itu biasanya tidak mungkin. Jadi, Anda harus mengidentifikasi tempat-tempat di desain Anda di mana utas berinteraksi dengan contoh yang sama dan mengurangi jumlah tempat-tempat itu. Dengan melakukan ini, Anda mengisolasi beberapa kelas di mana multithreading benar-benar terjadi, mengurangi kompleksitas keseluruhan pengujian sistem Anda.

Tetapi Anda harus menyadari bahwa bahkan dengan melakukan ini Anda masih tidak dapat menguji setiap situasi di mana dua utas saling menginjak. Untuk melakukan itu, Anda harus menjalankan dua utas secara bersamaan dalam pengujian yang sama, kemudian mengontrol dengan tepat garis apa yang mereka jalankan pada saat tertentu. Yang terbaik yang dapat Anda lakukan adalah mensimulasikan situasi ini. Tetapi ini mungkin mengharuskan Anda untuk kode khusus untuk pengujian, dan itu hanya setengah langkah menuju solusi yang benar.

Mungkin cara terbaik untuk menguji kode untuk masalah threading adalah melalui analisis statis kode. Jika kode berulir Anda tidak mengikuti serangkaian pola aman utas yang terbatas, maka Anda mungkin memiliki masalah. Saya percaya Analisis Kode dalam VS memang mengandung beberapa pengetahuan tentang threading, tapi mungkin tidak banyak.

Lihatlah, saat ini keadaan sedang berlangsung (dan mungkin akan menjadi waktu yang tepat untuk datang), cara terbaik untuk menguji aplikasi multithreaded adalah mengurangi kompleksitas kode ulir sebanyak mungkin. Minimalkan area di mana utas berinteraksi, ujilah sebaik mungkin, dan gunakan analisis kode untuk mengidentifikasi area berbahaya.

undur_gongor
sumber
1
Analisis kode sangat bagus jika Anda berurusan dengan bahasa / kerangka kerja yang memungkinkannya. EG: Findbugs akan menemukan masalah konkurensi yang sangat sederhana dan mudah dibagikan dengan variabel statis. Apa yang tidak dapat ditemukannya adalah pola desain tunggal, ia menganggap semua objek dapat dibuat beberapa kali. Plugin ini sangat tidak memadai untuk kerangka kerja seperti Spring.
Zombies
3
sebenarnya ada obatnya: benda aktif. drdobbs.com/parallel/prefer-using-active-objects-instead-of-n/…
Dill
6
Meskipun ini adalah saran yang bagus, saya masih bertanya, "bagaimana saya menguji area minimal di mana beberapa thread diperlukan?"
Bryan Rayner
5
"Jika terlalu rumit untuk diuji, Anda salah melakukannya" - kita semua harus menyelami kode warisan yang tidak kita tulis. Bagaimana pengamatan ini membantu seseorang dengan tepat?
Ronna
2
Analisis statis kemungkinan merupakan ide yang bagus, tetapi ini bukan pengujian. Posting ini benar-benar tidak menjawab pertanyaan, yaitu tentang bagaimana cara menguji.
Warren Dew
96

Sudah lama ketika pertanyaan ini diposting, tetapi masih belum dijawab ...

Jawaban kleolb02 adalah jawaban yang bagus. Saya akan mencoba masuk ke detail lebih lanjut.

Ada cara, yang saya praktekkan untuk kode C #. Untuk pengujian unit, Anda harus dapat memprogram tes yang dapat direproduksi , yang merupakan tantangan terbesar dalam kode multithreaded. Jadi jawaban saya bertujuan memaksakan kode asinkron ke dalam test harness, yang bekerja secara serempak .

Ini adalah ide dari buku Gerard Meszardos " xUnit Test Patterns " dan disebut "Humble Object" (hlm. 695): Anda harus memisahkan kode logika inti dan apa pun yang berbau seperti kode asinkron satu sama lain. Ini akan menghasilkan kelas untuk logika inti, yang bekerja secara serempak .

Ini menempatkan Anda pada posisi untuk menguji kode logika inti secara sinkron . Anda memiliki kontrol mutlak atas waktu panggilan yang Anda lakukan pada logika inti dan dengan demikian dapat melakukan tes yang dapat direproduksi . Dan ini adalah keuntungan Anda dari memisahkan logika inti dan logika asinkron.

Logika inti ini perlu dibungkus oleh kelas lain, yang bertanggung jawab untuk menerima panggilan ke logika inti secara serempak dan mendelegasikan panggilan-panggilan ini ke logika inti. Kode produksi hanya akan mengakses logika inti melalui kelas itu. Karena kelas ini seharusnya hanya mendelegasikan panggilan, ini adalah kelas yang sangat "bodoh" tanpa banyak logika. Jadi, Anda dapat menjaga tes unit Anda untuk kelas pekerja asikronus ini minimal.

Apa pun di atas itu (menguji interaksi antar kelas) adalah tes komponen. Juga dalam kasus ini, Anda harus dapat memiliki kontrol mutlak atas waktu, jika Anda tetap berpegang pada pola "Objek Humble".

Theo Lenndorff
sumber
1
Tetapi kadang-kadang jika benang bekerja sama dengan baik satu sama lain juga sesuatu harus diuji, bukan? Jelas saya akan memisahkan logika inti dari bagian async setelah membaca jawaban Anda. Tapi saya masih akan menguji logika melalui antarmuka async dengan callback work-on-all-threads-telah-dilakukan.
CopperCash
Bagaimana dengan sistem multi-prosesor?
Technophile
65

Memang tangguh! Dalam pengujian unit (C ++) saya, saya memecahnya menjadi beberapa kategori di sepanjang garis pola konkurensi yang digunakan:

  1. Tes unit untuk kelas yang beroperasi dalam satu utas dan tidak menyadari - mudah, tes seperti biasa.

  2. Tes unit untuk objek Monitor (yang menjalankan metode yang disinkronkan di utas kontrol penelepon) yang mengekspos API publik yang disinkronkan - instantiate beberapa utas tiruan yang menggunakan API. Buat skenario yang menggunakan kondisi internal objek pasif. Termasuk satu tes lagi yang pada dasarnya mengalahkan heck keluar dari banyak utas untuk jangka waktu yang lama. Ini tidak ilmiah saya tahu tetapi itu membangun kepercayaan diri.

  3. Tes unit untuk objek Aktif (yang merangkum utas atau utas kontrol sendiri) - mirip dengan # 2 di atas dengan variasi tergantung pada desain kelas. API publik dapat memblokir atau non-memblokir, penelepon dapat memperoleh masa depan, data dapat tiba di antrian atau perlu dequeued. Ada banyak kombinasi yang mungkin di sini; kotak putih. Masih membutuhkan beberapa utas tiruan untuk melakukan panggilan ke objek yang sedang diuji.

Sebagai tambahan:

Dalam pelatihan pengembang internal yang saya lakukan, saya mengajarkan Pillars of Concurrency dan dua pola ini sebagai kerangka kerja utama untuk memikirkan dan menguraikan masalah concurrency. Jelas ada konsep yang lebih maju di luar sana tetapi saya telah menemukan bahwa serangkaian dasar ini membantu menjaga insinyur keluar dari sup. Ini juga mengarah pada kode yang lebih dapat diuji unit, seperti dijelaskan di atas.

David Joyner
sumber
51

Saya telah menghadapi masalah ini beberapa kali dalam beberapa tahun terakhir ketika menulis kode penanganan utas untuk beberapa proyek. Saya memberikan jawaban terlambat karena sebagian besar jawaban lainnya, sambil memberikan alternatif, sebenarnya tidak menjawab pertanyaan tentang pengujian. Jawaban saya ditujukan kepada kasus-kasus di mana tidak ada alternatif untuk kode multithreaded; Saya membahas masalah desain kode untuk kelengkapan, tetapi juga membahas pengujian unit.

Menulis kode multithread yang dapat diuji

Hal pertama yang harus dilakukan adalah memisahkan kode penanganan utas produksi Anda dari semua kode yang melakukan pemrosesan data aktual. Dengan begitu, pemrosesan data dapat diuji sebagai kode ulir tunggal, dan satu-satunya yang dilakukan kode multithread adalah mengoordinasikan utas.

Hal kedua yang perlu diingat adalah bahwa bug dalam kode multithread adalah probabilistik; bug yang paling sering memanifestasikan dirinya adalah bug yang akan menyelinap masuk ke dalam produksi, akan sulit untuk mereproduksi bahkan dalam produksi, dan dengan demikian akan menyebabkan masalah terbesar. Untuk alasan ini, pendekatan pengkodean standar penulisan kode dengan cepat dan kemudian debugging sampai berfungsi adalah ide yang buruk untuk kode multithreaded; itu akan menghasilkan kode di mana bug mudah diperbaiki dan bug berbahaya masih ada.

Sebaliknya, ketika menulis kode multithread, Anda harus menulis kode dengan sikap bahwa Anda akan menghindari penulisan bug di tempat pertama. Jika Anda telah menghapus kode pemrosesan data dengan benar, kode penanganan ulir harus cukup kecil - lebih disukai beberapa baris, paling buruk beberapa lusin baris - sehingga Anda memiliki kesempatan untuk menulisnya tanpa menulis bug, dan tentunya tanpa menulis banyak bug , jika Anda memahami threading, luangkan waktu Anda, dan berhati-hatilah.

Unit tes menulis untuk kode multithreaded

Setelah kode multithreaded ditulis selengkap mungkin, masih ada baiknya menulis tes untuk kode tersebut. Tujuan utama dari tes ini bukan untuk menguji bug kondisi ras yang sangat tergantung waktu - tidak mungkin untuk menguji kondisi ras seperti itu berulang - tetapi untuk menguji bahwa strategi penguncian Anda untuk mencegah bug seperti itu memungkinkan beberapa thread untuk berinteraksi sebagaimana dimaksud .

Untuk menguji perilaku penguncian yang benar dengan benar, tes harus memulai beberapa utas. Untuk membuat tes berulang, kami ingin interaksi antara utas terjadi dalam urutan yang dapat diprediksi. Kami tidak ingin menyinkronkan utas secara eksternal dalam pengujian, karena itu akan menutupi bug yang dapat terjadi dalam produksi di mana utas tidak disinkronkan secara eksternal. Itu meninggalkan penggunaan penundaan waktu untuk sinkronisasi utas, yang merupakan teknik yang telah saya gunakan dengan sukses setiap kali saya harus menulis tes kode multithread.

Jika penundaan terlalu pendek, maka pengujian menjadi rapuh, karena perbedaan waktu kecil - katakanlah antara mesin yang berbeda di mana tes dapat dijalankan - dapat menyebabkan waktu dimatikan dan tes gagal. Apa yang biasanya saya lakukan adalah mulai dengan penundaan yang menyebabkan kegagalan pengujian, tingkatkan penundaan sehingga pengujian dapat diandalkan pada mesin pengembangan saya, dan kemudian gandakan penundaan di luar itu sehingga pengujian memiliki peluang bagus untuk melewati mesin lain. Ini berarti bahwa pengujian akan memakan waktu makroskopis, meskipun menurut pengalaman saya, desain pengujian yang cermat dapat membatasi waktu itu tidak lebih dari selusin detik. Karena Anda seharusnya tidak memiliki banyak tempat yang membutuhkan kode koordinasi utas dalam aplikasi Anda, itu seharusnya dapat diterima untuk test suite Anda.

Akhirnya, catat jumlah bug yang ditangkap oleh pengujian Anda. Jika pengujian Anda memiliki cakupan kode 80%, maka diharapkan dapat menangkap sekitar 80% bug Anda. Jika pengujian Anda dirancang dengan baik tetapi tidak menemukan bug, ada kemungkinan Anda tidak memiliki bug tambahan yang hanya akan muncul dalam produksi. Jika tes menangkap satu atau dua bug, Anda mungkin masih beruntung. Di luar itu, dan Anda mungkin ingin mempertimbangkan peninjauan yang cermat atau bahkan penulisan ulang lengkap kode penanganan utas Anda, karena kemungkinan kode tersebut masih mengandung bug tersembunyi yang akan sangat sulit ditemukan hingga kode tersebut diproduksi, dan sangat sulit untuk diperbaiki.

Warren Dew
sumber
3
Pengujian hanya dapat mengungkapkan keberadaan bug, bukan ketidakhadiran mereka. Pertanyaan awal menanyakan tentang masalah 2-utas, dalam hal ini pengujian menyeluruh mungkin dilakukan, tetapi seringkali tidak. Untuk apa pun di luar skenario paling sederhana, Anda mungkin harus menggigit peluru dan menggunakan metode formal - tetapi jangan lewati tes unit! Menulis kode multi-utas yang benar sulit pada awalnya, tetapi masalah yang sama sulitnya adalah pembuktian di masa depan terhadap regresi.
Paul Williams
4
Ringkasan luar biasa dari salah satu cara yang paling tidak dipahami. Jawaban Anda tepat pada segregasi nyata yang biasanya diabaikan oleh ppl.
prash
1
Selusin detik adalah waktu yang cukup lama, bahkan jika Anda hanya memiliki beberapa ratus tes dengan panjang itu ...
Toby Speight
1
@TobySpeight Tes ini panjang dibandingkan dengan tes unit normal. Saya telah menemukan bahwa setengah lusin tes lebih dari cukup jika kode ulir dirancang dengan sesederhana mungkin, meskipun - memerlukan beberapa ratus tes multithreading hampir pasti akan menunjukkan pengaturan threading yang terlalu rumit.
Warren Dew
2
Itu argumen yang bagus untuk menjaga agar logika utas Anda dapat dipisahkan dari fungsionalitas yang Anda bisa (saya tahu, lebih mudah dikatakan daripada dilakukan). Dan, jika mungkin, memecah suite uji menjadi set "setiap perubahan" dan "pra-komit" (jadi tes menit-ke-menit Anda tidak terlalu terpengaruh).
Toby Speight
22

Saya juga memiliki masalah serius dalam menguji kode multi-threaded. Kemudian saya menemukan solusi yang sangat keren di "Pola Tes xUnit" oleh Gerard Meszaros. Pola yang ia gambarkan disebut objek Humble .

Pada dasarnya ini menjelaskan bagaimana Anda dapat mengekstrak logika menjadi komponen terpisah yang mudah diuji yang dipisahkan dari lingkungannya. Setelah Anda menguji logika ini, Anda dapat menguji perilaku yang rumit (multi-threading, eksekusi asinkron, dll ...)

Olifant
sumber
20

Ada beberapa alat di sekitar yang cukup bagus. Berikut ini adalah ringkasan dari beberapa yang Java.

Beberapa alat analisis statis yang baik termasuk FindBugs (memberikan beberapa petunjuk bermanfaat), JLint , Java Pathfinder (JPF & JPF2), dan Bogor .

MultithreadedTC adalah alat analisis dinamis yang cukup baik (terintegrasi ke dalam JUnit) di mana Anda harus menyiapkan kotak uji sendiri.

ConTest dari IBM Research menarik. Itu instrumen kode Anda dengan memasukkan semua jenis perilaku pengubah utas (misalnya tidur & hasil) untuk mencoba mengungkap bug secara acak.

SPIN adalah alat yang sangat keren untuk memodelkan komponen Java Anda (dan lainnya), tetapi Anda perlu memiliki beberapa kerangka kerja yang bermanfaat. Sulit untuk digunakan apa adanya, tetapi sangat kuat jika Anda tahu cara menggunakannya. Cukup banyak alat yang menggunakan SPIN di bawah tenda.

MultithreadedTC mungkin yang paling utama, tetapi beberapa alat analisis statis yang tercantum di atas pasti pantas untuk dilihat.

xagyg
sumber
16

Keramahtamahan juga dapat berguna untuk membantu Anda menulis tes unit deterministik. Ini memungkinkan Anda untuk menunggu sampai beberapa negara bagian di sistem Anda diperbarui. Sebagai contoh:

await().untilCall( to(myService).myMethod(), greaterThan(3) );

atau

await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1));

Ini juga memiliki dukungan Scala dan Groovy.

await until { something() > 4 } // Scala example
Johan
sumber
1
Penantiannya brilian - persis apa yang saya cari!
Forge_7
14

Cara lain untuk (agak) menguji kode ulir, dan sistem yang sangat kompleks pada umumnya adalah melalui Pengujian Fuzz . Ini tidak bagus, dan tidak akan menemukan segalanya, tetapi kemungkinan berguna dan mudah dilakukan.

Mengutip:

Pengujian fuzz atau fuzzing adalah teknik pengujian perangkat lunak yang memberikan data acak ("fuzz") ke input suatu program. Jika program gagal (misalnya, karena crash, atau dengan gagal kode bawaan), cacat dapat dicatat. Keuntungan besar pengujian fuzz adalah desain pengujiannya sangat sederhana, dan bebas dari prasangka tentang perilaku sistem.

...

Pengujian fuzz sering digunakan dalam proyek pengembangan perangkat lunak besar yang menggunakan pengujian kotak hitam. Proyek-proyek ini biasanya memiliki anggaran untuk mengembangkan alat uji, dan pengujian fuzz adalah salah satu teknik yang menawarkan rasio manfaat dan biaya tinggi.

...

Namun, pengujian fuzz bukan merupakan pengganti untuk pengujian lengkap atau metode formal: itu hanya dapat memberikan sampel acak dari perilaku sistem, dan dalam banyak kasus melewati tes fuzz hanya dapat menunjukkan bahwa perangkat lunak menangani pengecualian tanpa menabrak, daripada berperilaku dengan benar. Dengan demikian, pengujian fuzz hanya dapat dianggap sebagai alat pencarian bug daripada jaminan kualitas.

Robert Gould
sumber
13

Saya sudah melakukan banyak ini, dan ya itu menyebalkan.

Beberapa tips:

  • GroboUtils untuk menjalankan beberapa utas uji
  • alphaWorks ConTest ke kelas instrumen untuk menyebabkan interleavings bervariasi antara iterasi
  • Buat throwablebidang dan periksa di tearDown(lihat Daftar 1). Jika Anda menangkap pengecualian buruk di utas lain, cukup tetapkan itu untuk dibuang.
  • Saya membuat kelas utils di Listing 2 dan telah menemukannya sangat berharga, terutama waitForVerify dan waitForCondition, yang akan sangat meningkatkan kinerja tes Anda.
  • Manfaatkan dengan baik AtomicBooleandalam tes Anda. Ini adalah thread yang aman, dan Anda akan sering membutuhkan jenis referensi akhir untuk menyimpan nilai dari kelas panggilan balik dan sejenisnya. Lihat contoh di Listing 3.
  • Pastikan untuk selalu memberikan batas waktu tes Anda (misalnya, @Test(timeout=60*1000)), karena tes konkurensi kadang-kadang dapat menggantung selamanya ketika mereka rusak.

Listing 1:

@After
public void tearDown() {
    if ( throwable != null )
        throw throwable;
}

Listing 2:

import static org.junit.Assert.fail;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Random;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.time.StopWatch;
import org.easymock.EasyMock;
import org.easymock.classextension.internal.ClassExtensionHelper;
import static org.easymock.classextension.EasyMock.*;

import ca.digitalrapids.io.DRFileUtils;

/**
 * Various utilities for testing
 */
public abstract class DRTestUtils
{
    static private Random random = new Random();

/** Calls {@link #waitForCondition(Integer, Integer, Predicate, String)} with
 * default max wait and check period values.
 */
static public void waitForCondition(Predicate predicate, String errorMessage) 
    throws Throwable
{
    waitForCondition(null, null, predicate, errorMessage);
}

/** Blocks until a condition is true, throwing an {@link AssertionError} if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param errorMessage message use in the {@link AssertionError}
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, String errorMessage) throws Throwable 
{
    waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() {
        public void execute(Object errorMessage)
        {
            fail((String)errorMessage);
        }
    }, errorMessage);
}

/** Blocks until a condition is true, running a closure if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param closure closure to run
 * @param argument argument for closure
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, Closure closure, Object argument) throws Throwable 
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    if ( checkPeriod_ms == null )
        checkPeriod_ms = 100;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    while ( !predicate.evaluate(null) ) {
        Thread.sleep(checkPeriod_ms);
        if ( stopWatch.getTime() > maxWait_ms ) {
            closure.execute(argument);
        }
    }
}

/** Calls {@link #waitForVerify(Integer, Object)} with <code>null</code>
 * for {@code maxWait_ms}
 */
static public void waitForVerify(Object easyMockProxy)
    throws Throwable
{
    waitForVerify(null, easyMockProxy);
}

/** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a
 * max wait time has elapsed.
 * @param maxWait_ms Max wait time. <code>null</code> defaults to 30s.
 * @param easyMockProxy Proxy to call verify on
 * @throws Throwable
 */
static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy)
    throws Throwable
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for(;;) {
        try
        {
            verify(easyMockProxy);
            break;
        }
        catch (AssertionError e)
        {
            if ( stopWatch.getTime() > maxWait_ms )
                throw e;
            Thread.sleep(100);
        }
    }
}

/** Returns a path to a directory in the temp dir with the name of the given
 * class. This is useful for temporary test files.
 * @param aClass test class for which to create dir
 * @return the path
 */
static public String getTestDirPathForTestClass(Object object) 
{

    String filename = object instanceof Class ? 
        ((Class)object).getName() :
        object.getClass().getName();
    return DRFileUtils.getTempDir() + File.separator + 
        filename;
}

static public byte[] createRandomByteArray(int bytesLength)
{
    byte[] sourceBytes = new byte[bytesLength];
    random.nextBytes(sourceBytes);
    return sourceBytes;
}

/** Returns <code>true</code> if the given object is an EasyMock mock object 
 */
static public boolean isEasyMockMock(Object object) {
    try {
        InvocationHandler invocationHandler = Proxy
                .getInvocationHandler(object);
        return invocationHandler.getClass().getName().contains("easymock");
    } catch (IllegalArgumentException e) {
        return false;
    }
}
}

Listing 3:

@Test
public void testSomething() {
    final AtomicBoolean called = new AtomicBoolean(false);
    subject.setCallback(new SomeCallback() {
        public void callback(Object arg) {
            // check arg here
            called.set(true);
        }
    });
    subject.run();
    assertTrue(called.get());
}
Kevin Wong
sumber
2
Waktu tunggu adalah ide yang bagus, tetapi jika ujian habis, hasil selanjutnya dalam jangka waktu itu adalah dugaan. Tes habis waktu mungkin masih memiliki beberapa utas berjalan yang dapat mengacaukan Anda.
Don Kirkby
12

Menguji kode MT untuk kebenaran, seperti yang telah disebutkan, merupakan masalah yang cukup sulit. Pada akhirnya, hal itu dilakukan untuk memastikan bahwa tidak ada ras data yang disinkronkan secara salah dalam kode Anda. Masalahnya adalah ada banyak kemungkinan eksekusi thread (interleavings) di mana Anda tidak memiliki banyak kontrol (pastikan untuk membaca ini artikel , meskipun). Dalam skenario sederhana dimungkinkan untuk benar-benar membuktikan kebenaran dengan alasan tetapi ini biasanya tidak terjadi. Terutama jika Anda ingin menghindari / meminimalkan sinkronisasi dan tidak menggunakan opsi sinkronisasi yang paling jelas / termudah.

Pendekatan yang saya ikuti adalah menulis kode uji yang sangat konkuren untuk membuat kemungkinan ras data yang tidak terdeteksi terjadi. Dan kemudian saya menjalankan tes-tes itu untuk beberapa waktu :) Saya pernah menemukan sebuah pembicaraan di mana beberapa ilmuwan komputer di mana memamerkan alat semacam itu melakukan ini (secara acak merancang tes dari spesifikasi dan kemudian menjalankannya secara liar, bersamaan, memeriksa invarian yang ditentukan untuk dilanggar).

Ngomong-ngomong, saya pikir aspek pengujian kode MT ini belum disebutkan di sini: identifikasi invarian dari kode yang dapat Anda periksa secara acak. Sayangnya, menemukan invarian tersebut juga merupakan masalah yang cukup sulit. Juga mereka mungkin tidak tahan sepanjang waktu selama eksekusi, jadi Anda harus menemukan / menegakkan poin eksekusi di mana Anda dapat mengharapkannya benar. Membawa eksekusi kode ke keadaan seperti itu juga merupakan masalah yang sulit (dan mungkin akan menimbulkan masalah konkurensi. Wah, ini sangat sulit!

Beberapa tautan menarik untuk dibaca:

bennidi
sumber
penulis mengacu pada pengacakan dalam pengujian. Mungkin QuickCheck , yang telah porting ke banyak bahasa. Anda dapat menonton pembicaraan tentang pengujian semacam itu untuk sistem konkuren di sini
Max
6

Pete Goodliffe memiliki seri pengujian unit pada kode ulir .

Sulit. Saya mengambil jalan keluar yang lebih mudah dan mencoba untuk menjaga kode threading diabstraksi dari tes yang sebenarnya. Pete memang menyebutkan bahwa cara saya melakukannya salah, tetapi saya punya pemisahan yang benar atau saya baru saja beruntung.

graham.reeds
sumber
6
Saya membaca dua artikel yang diterbitkan sejauh ini, dan saya tidak menemukannya sangat membantu. Dia hanya berbicara tentang kesulitan tanpa memberikan banyak nasihat konkret. Mungkin artikel yang akan datang akan membaik.
Don Kirkby
6

Untuk Java, lihat bab 12 dari JCIP . Ada beberapa contoh konkret penulisan deterministik, tes unit multi-utas untuk setidaknya menguji kebenaran dan invarian kode konkuren.

Keamanan benang "terbukti" dengan unit test jauh lebih mudah. Keyakinan saya adalah bahwa ini lebih baik dilayani oleh pengujian integrasi otomatis pada berbagai platform / konfigurasi.

Scott Bale
sumber
6

Saya suka menulis dua atau lebih metode pengujian untuk dieksekusi pada thread paralel, dan masing-masing membuat panggilan ke objek yang diuji. Saya telah menggunakan panggilan Sleep () untuk mengoordinasikan urutan panggilan dari utas berbeda, tapi itu tidak terlalu bisa diandalkan. Ini juga jauh lebih lambat karena Anda harus tidur cukup lama sehingga waktunya biasanya bekerja.

Saya menemukan pustaka TC Java Multithreaded dari grup yang sama yang menulis FindBugs. Ini memungkinkan Anda menentukan urutan acara tanpa menggunakan Sleep (), dan itu dapat diandalkan. Saya belum mencobanya.

Keterbatasan terbesar untuk pendekatan ini adalah hanya memungkinkan Anda menguji skenario yang Anda curigai akan menimbulkan masalah. Seperti yang orang lain katakan, Anda benar-benar perlu mengisolasi kode multithreaded Anda ke sejumlah kecil kelas sederhana untuk memiliki harapan untuk menguji mereka secara menyeluruh.

Setelah Anda dengan hati-hati menguji skenario yang Anda harapkan menyebabkan masalah, tes tidak ilmiah yang melempar banyak permintaan simultan di kelas untuk sementara waktu adalah cara yang baik untuk mencari masalah yang tidak terduga.

Pembaruan: Saya telah bermain sedikit dengan pustaka TC Java Multithreaded, dan berfungsi dengan baik. Saya juga telah mem-porting beberapa fitur-fiturnya ke versi .NET yang saya sebut TickingTest .

Don Kirkby
sumber
5

Saya menangani tes unit komponen ulir dengan cara yang sama saya menangani uji unit apa pun, yaitu dengan inversi kerangka kontrol dan isolasi. Saya mengembangkan di arena .Net dan di luar kotak threading (antara lain) sangat sulit (saya katakan hampir tidak mungkin) untuk sepenuhnya mengisolasi.

Karena itu saya telah menulis pembungkus yang terlihat seperti ini (disederhanakan):

public interface IThread
{
    void Start();
    ...
}

public class ThreadWrapper : IThread
{
    private readonly Thread _thread;

    public ThreadWrapper(ThreadStart threadStart)
    {
        _thread = new Thread(threadStart);
    }

    public Start()
    {
        _thread.Start();
    }
}

public interface IThreadingManager
{
    IThread CreateThread(ThreadStart threadStart);
}

public class ThreadingManager : IThreadingManager
{
    public IThread CreateThread(ThreadStart threadStart)
    {
         return new ThreadWrapper(threadStart)
    }
}

Dari sana saya dapat dengan mudah menyuntikkan IThreadingManager ke komponen saya dan menggunakan kerangka isolasi pilihan saya untuk membuat thread berperilaku seperti yang saya harapkan selama pengujian.

Sejauh ini itu bekerja sangat baik untuk saya, dan saya menggunakan pendekatan yang sama untuk kumpulan thread, hal-hal di System.Environment, Sleep dll.

scim
sumber
5

Lihatlah jawaban terkait saya di

Merancang kelas Uji untuk Penghalang kustom

Itu bias terhadap Jawa tetapi memiliki ringkasan yang masuk akal dari opsi.

Singkatnya meskipun (IMO) itu bukan penggunaan beberapa kerangka kerja mewah yang akan memastikan kebenaran tetapi bagaimana Anda merancang kode multithreaded Anda. Memisahkan masalah (konkurensi dan fungsionalitas) sangat membantu meningkatkan kepercayaan diri.Perangkat Lunak Orientasi Obyek yang Tumbuh Dipandu Oleh Pengujian menjelaskan beberapa opsi yang lebih baik daripada yang saya bisa.

Analisis statis dan metode formal (lihat, Concurrency: Model Negara dan Program Java ) adalah sebuah pilihan, tetapi saya telah menemukan mereka menjadi penggunaan terbatas dalam pengembangan komersial.

Jangan lupa bahwa tes gaya beban / rendam jarang dijamin untuk menyoroti masalah.

Semoga berhasil!

Toby
sumber
Anda juga harus menyebutkan tempus-fugitperpustakaan Anda di sini, yang helps write and test concurrent code;)
Idolon
4

Saya baru saja menemukan (untuk Java) alat yang disebut Threadsafe. Ini adalah alat analisis statis mirip dengan findbugs tetapi khusus untuk menemukan masalah multi-threading. Ini bukan pengganti untuk pengujian tetapi saya dapat merekomendasikannya sebagai bagian dari penulisan Java multi-threaded yang andal.

Ia bahkan menangkap beberapa masalah potensial yang sangat halus di sekitar hal-hal seperti subsumsi kelas, mengakses objek yang tidak aman melalui kelas bersamaan dan menemukan pengubah volatil yang hilang saat menggunakan paradigma penguncian diperiksa ganda.

Jika Anda menulis Java multithreaded mencobanya.

Feldoh
sumber
3

Artikel berikut menyarankan 2 solusi. Membungkus semaphore (CountDownLatch) dan menambahkan fungsionalitas seperti mengeksternalisasi data dari utas internal. Cara lain untuk mencapai tujuan ini adalah dengan menggunakan Thread Pool (lihat Tempat Menarik).

Sprinkler - Objek sinkronisasi lanjutan

Effi Bar-She'an
sumber
3
Tolong jelaskan pendekatannya di sini, tautan eksternal mungkin mati di masa depan.
Uooo
2

Saya menghabiskan sebagian besar minggu lalu di perpustakaan universitas mempelajari debugging kode bersamaan. Masalah utama adalah kode konkuren adalah non-deterministik. Biasanya, debugging akademik telah jatuh ke dalam salah satu dari tiga kubu di sini:

  1. Event-trace / replay. Ini membutuhkan pemantau acara dan kemudian meninjau peristiwa yang dikirim. Dalam kerangka kerja UT, ini akan melibatkan pengiriman acara secara manual sebagai bagian dari tes, dan kemudian melakukan tinjauan post-mortem.
  2. Skrip Di sinilah Anda berinteraksi dengan kode yang sedang berjalan dengan serangkaian pemicu. "Di x> foo, baz ()". Ini bisa ditafsirkan ke dalam kerangka kerja UT di mana Anda memiliki sistem run-time yang memicu pengujian yang diberikan pada kondisi tertentu.
  3. Interaktif. Ini jelas tidak akan berfungsi dalam situasi pengujian otomatis. ;)

Sekarang, seperti yang diperhatikan oleh komentator di atas, Anda dapat merancang sistem konkuren Anda menjadi keadaan yang lebih deterministik. Namun, jika Anda tidak melakukannya dengan benar, Anda kembali merancang sistem sekuensial lagi.

Saran saya adalah untuk fokus pada memiliki protokol desain yang sangat ketat tentang apa yang di-threaded dan apa yang tidak di-threaded. Jika Anda membatasi antarmuka Anda sehingga ada ketergantungan minimal antara elemen, itu jauh lebih mudah.

Semoga berhasil, dan terus kerjakan masalah tersebut.

Paul Nathan
sumber
2

Saya memiliki tugas yang tidak menguntungkan untuk menguji kode berulir dan mereka jelas merupakan tes tersulit yang pernah saya tulis.

Saat menulis tes saya, saya menggunakan kombinasi delegasi dan acara. Pada dasarnya ini semua tentang menggunakan PropertyNotifyChangedacara dengan WaitCallbackatau semacamnyaConditionalWaiter jajak pendapat itu.

Saya tidak yakin apakah ini pendekatan terbaik, tetapi itu berhasil bagi saya.

Dale Ragan
sumber
1

Dengan asumsi di bawah kode "multi-threaded" berarti sesuatu itu

  • stateful dan bisa berubah
  • DAN diakses / dimodifikasi oleh beberapa utas secara bersamaan

Dengan kata lain kita berbicara tentang pengujian kelas-metode / unit / unit thread-safe kustom - yang seharusnya menjadi binatang yang sangat langka saat ini.

Karena binatang buas ini langka, pertama-tama kita perlu memastikan bahwa ada semua alasan yang sah untuk menuliskannya.

Langkah 1. Pertimbangkan mengubah kondisi dalam konteks sinkronisasi yang sama.

Saat ini mudah untuk menulis kode konkuren dan asinkron yang dapat dikomposisikan di mana IO atau operasi lambat lainnya diturunkan ke latar belakang tetapi keadaan bersama diperbarui dan dipertanyakan dalam satu konteks sinkronisasi. mis. async / menunggu tugas dan Rx dalam .NET dll. - semuanya dapat diuji dengan desain, "nyata" Tugas dan penjadwal dapat diganti untuk membuat pengujian deterministik (namun ini di luar ruang lingkup pertanyaan).

Mungkin terdengar sangat terbatas tetapi pendekatan ini bekerja dengan sangat baik. Dimungkinkan untuk menulis seluruh aplikasi dengan gaya ini tanpa perlu membuat status utas aman (saya lakukan).

Langkah 2. Jika memanipulasi keadaan bersama pada konteks sinkronisasi tunggal sama sekali tidak mungkin.

Pastikan roda tidak diciptakan kembali / pasti tidak ada alternatif standar yang dapat disesuaikan untuk pekerjaan itu. Seharusnya kode itu sangat kohesif dan terkandung dalam satu unit misalnya dengan peluang yang baik itu adalah kasus khusus dari beberapa struktur data aman-benang standar seperti peta hash atau koleksi atau apa pun.

Catatan: jika kode besar / mencakup beberapa kelas DAN membutuhkan manipulasi keadaan multi-thread maka ada peluang yang sangat tinggi bahwa desain tidak baik, pertimbangkan kembali Langkah 1

Langkah 3. Jika langkah ini tercapai, kita perlu menguji kelas / metode / unit thread-safe stateful kami sendiri .

Saya akan sangat jujur: Saya tidak pernah harus menulis tes yang tepat untuk kode tersebut. Sebagian besar waktu saya lolos pada Langkah 1, kadang-kadang pada Langkah 2. Terakhir kali saya harus menulis kode khusus thread-safe bertahun-tahun yang lalu sebelum saya mengadopsi unit testing / mungkin saya tidak perlu menulisnya dengan pengetahuan saat ini pula.

Jika saya benar-benar harus menguji kode tersebut ( akhirnya, jawaban aktual ) maka saya akan mencoba beberapa hal di bawah ini

  1. Pengujian stres non-deterministik. mis. jalankan 100 utas secara bersamaan dan periksa apakah hasil akhirnya konsisten. Ini lebih tipikal untuk pengujian level / integrasi yang lebih tinggi dari beberapa skenario pengguna tetapi juga dapat digunakan pada level unit.

  2. Paparkan beberapa 'kait' uji di mana tes dapat menyuntikkan beberapa kode untuk membantu membuat skenario deterministik di mana satu utas harus melakukan operasi sebelum yang lain. Seburuk itu, saya tidak bisa memikirkan sesuatu yang lebih baik.

  3. Pengujian yang digerakkan oleh penundaan untuk membuat thread berjalan dan melakukan operasi dalam urutan tertentu. Sebenarnya, tes semacam itu juga non-deterministik (ada kemungkinan sistem membekukan / menghentikan pengumpulan GC dunia yang dapat mendistorsi penundaan yang diatur sebelumnya), juga jelek tetapi memungkinkan untuk menghindari kaitan.

Kola
sumber
0

Untuk kode J2E, saya telah menggunakan SilkPerformer, LoadRunner dan JMeter untuk pengujian konkurensi utas. Mereka semua melakukan hal yang sama. Pada dasarnya, mereka memberi Anda antarmuka yang relatif sederhana untuk mengelola versi server proxy mereka, diperlukan, untuk menganalisis aliran data TCP / IP, dan mensimulasikan beberapa pengguna membuat permintaan simultan ke server aplikasi Anda. Server proksi dapat memberi Anda kemampuan untuk melakukan hal-hal seperti menganalisis permintaan yang dibuat, dengan menyajikan seluruh halaman dan URL yang dikirim ke server, serta respons dari server, setelah memproses permintaan.

Anda dapat menemukan beberapa bug dalam mode http tidak aman, di mana Anda setidaknya bisa menganalisis data formulir yang sedang dikirim, dan secara sistematis mengubahnya untuk setiap pengguna. Tetapi tes yang sebenarnya adalah ketika Anda menjalankan di https (Lapisan Socket Aman). Kemudian, Anda juga harus bersaing dengan secara sistematis mengubah data sesi dan cookie, yang bisa sedikit lebih berbelit-belit.

Bug terbaik yang pernah saya temukan, saat menguji konkurensi, adalah ketika saya menemukan bahwa pengembang telah mengandalkan pengumpulan sampah Java untuk menutup permintaan koneksi yang dibuat saat login, ke server LDAP, saat masuk. Hal ini mengakibatkan pengguna terekspos. ke sesi pengguna lain dan hasil yang sangat membingungkan, ketika mencoba menganalisis apa yang terjadi ketika server dibuat berlutut, hampir tidak dapat menyelesaikan satu transaksi, setiap beberapa detik.

Pada akhirnya, Anda atau seseorang mungkin harus bekerja keras dan menganalisis kode untuk kesalahan seperti yang saya sebutkan tadi. Dan diskusi terbuka lintas departemen, seperti yang terjadi, ketika kami membuka masalah yang dijelaskan di atas, sangat berguna. Tetapi alat ini adalah solusi terbaik untuk menguji kode multi-threaded. JMeter adalah open source. SilkPerformer dan LoadRunner adalah hak milik. Jika Anda benar-benar ingin tahu apakah aplikasi Anda aman, itulah yang dilakukan anak-anak besar. Saya telah melakukan ini untuk perusahaan yang sangat besar secara profesional, jadi saya tidak menebak. Saya berbicara dari pengalaman pribadi.

Peringatan: diperlukan beberapa saat untuk memahami alat-alat ini. Ini tidak akan menjadi masalah hanya dengan menginstal perangkat lunak dan menjalankan GUI, kecuali Anda sudah memiliki beberapa paparan pemrograman multi-threaded. Saya telah mencoba mengidentifikasi 3 kategori penting bidang yang perlu dipahami (formulir, sesi, dan data cookie), dengan harapan bahwa setidaknya mulai dengan memahami topik-topik ini akan membantu Anda fokus pada hasil yang cepat, sebagai lawan dari harus membaca keseluruhan seluruh dokumentasi.

Ayam Merah
sumber
0

Concurrency adalah interaksi yang kompleks antara model memori, perangkat keras, cache dan kode kami. Dalam kasus Jawa, setidaknya tes semacam itu sebagian telah ditangani terutama oleh jcstress . Pembuat perpustakaan itu dikenal sebagai penulis banyak fitur konkurensi JVM, GC dan Java.

Tetapi bahkan perpustakaan ini membutuhkan pengetahuan yang baik tentang spesifikasi Java Memory Model sehingga kami tahu persis apa yang kami uji. Tapi saya pikir fokus dari upaya ini adalah mircobenchmarks. Bukan aplikasi bisnis besar.

Mohan Radhakrishnan
sumber
0

Ada artikel tentang topik tersebut, menggunakan Rust sebagai bahasa dalam kode contoh:

https://medium.com/@polyglot_factotum/rust-concurrency-five-easy-pieces-871f1c62906a

Singkatnya, triknya adalah untuk menulis logika konkursi Anda sehingga kuat untuk non-determinisme yang terlibat dengan beberapa utas eksekusi, menggunakan alat seperti saluran dan condvars.

Kemudian, jika itu adalah cara Anda menyusun "komponen" Anda, cara termudah untuk mengujinya adalah dengan menggunakan saluran untuk mengirim pesan kepada mereka, dan kemudian memblokir saluran lain untuk menyatakan bahwa komponen tersebut mengirim pesan yang diharapkan.

Artikel yang ditautkan ke sepenuhnya ditulis menggunakan unit-tes.

gterzian
sumber
-1

Jika Anda menguji Thread baru yang sederhana (runnable) .run () Anda dapat mengejek Thread untuk menjalankan runnable secara berurutan

Misalnya, jika kode objek yang diuji meminta utas baru seperti ini

Class TestedClass {
    public void doAsychOp() {
       new Thread(new myRunnable()).start();
    }
}

Kemudian mengejek Thread baru dan menjalankan argumen runnable secara berurutan dapat membantu

@Mock
private Thread threadMock;

@Test
public void myTest() throws Exception {
    PowerMockito.mockStatic(Thread.class);
    //when new thread is created execute runnable immediately 
    PowerMockito.whenNew(Thread.class).withAnyArguments().then(new Answer<Thread>() {
        @Override
        public Thread answer(InvocationOnMock invocation) throws Throwable {
            // immediately run the runnable
            Runnable runnable = invocation.getArgumentAt(0, Runnable.class);
            if(runnable != null) {
                runnable.run();
            }
            return threadMock;//return a mock so Thread.start() will do nothing         
        }
    }); 
    TestedClass testcls = new TestedClass()
    testcls.doAsychOp(); //will invoke myRunnable.run in current thread
    //.... check expected 
}
Avraham Shalev
sumber
-3

(jika mungkin) tidak menggunakan utas, gunakan aktor / objek aktif. Mudah diuji.

Dill
sumber
2
@OMTheEternity mungkin tetapi jawabannya imo masih terbaik.
Dill
-5

Anda dapat menggunakan EasyMock.makeThreadSafe untuk membuat pengujian instance threadsafe

pengguna590444
sumber
Ini sama sekali bukan cara yang mungkin untuk menguji kode multithreaded. Masalahnya bukan bahwa kode uji menjalankan multi-threaded tetapi Anda menguji kode yang biasanya berjalan multi-threaded. Dan Anda tidak dapat menyinkronkan semuanya karena Anda sebenarnya tidak menguji ras data lagi.
bennidi