Dişli kod testini nasıl birim olarak yapmalıyım?


704

Şimdiye kadar sadece bir mayın tarlası gibi görünüyor çünkü çok iş parçacıklı kod test kabus kaçındı. İnsanların başarılı bir şekilde yürütmek için iş parçacıklarına dayanan kodları nasıl test ettiklerini veya yalnızca insanların yalnızca iki iş parçacığı belirli bir şekilde etkileşime girdiğinde ortaya çıkan bu tür sorunları nasıl test ettiklerini sormak isterim?

Bu, bugün programcılar için gerçekten önemli bir sorun gibi görünüyor, bu bir imho hakkındaki bilgilerimizi birleştirmek yararlı olacaktır.


2
Aynı konuda bir soru yayınlamayı düşünüyordum. Will aşağıda birçok iyi noktaya değinirken, daha iyisini yapabileceğimizi düşünüyorum. Temiz bir şekilde başa çıkmak için tek bir "yaklaşım" olmadığını kabul ediyorum. Ancak, "olabildiğince iyi test" çıtayı çok düşük yapıyor. Bulgularımla geri döneceğim.
Zach Burlingame

Java'da: Package java.util.concurrent, deterministik JUnit-Testleri yazmaya yardımcı olabilecek bazı kötü bilinen Sınıflar içerir. Bak - CountDownLatch - Semafor - Eşanjör
Synox

Önceki birim test ile ilgili sorunuz için bir bağlantı sağlayabilir misiniz?
Andrew Grimm


7
Bu sorunun 8 yaşında olduğunu ve uygulama kütüphanelerinin bu arada oldukça uzun bir yol kat ettiğini düşünüyorum. "Modern çağ" (2016) 'da çok iş parçacıklı geliştirme esas olarak gömülü sistemlerde ortaya çıkmaktadır. Ancak bir masaüstü veya telefon uygulamasında çalışıyorsanız, önce alternatifleri keşfedin. .NET gibi uygulama ortamları artık yaygın çok iş parçacıklı senaryoların% 90'ını yönetmek veya büyük ölçüde basitleştirmek için araçlar içermektedir. (asnync / bekliyor, PLinq, IObservable, TPL ...). Çok iş parçacıklı kod zor. Tekerleği yeniden icat etmezseniz, tekrar test etmeniz gerekmez.
Paul Williams

Yanıtlar:


245

Bak, bunu yapmanın kolay bir yolu yok. Doğal olarak çok iş parçacıklı bir proje üzerinde çalışıyorum. Olaylar işletim sisteminden geliyor ve bunları aynı anda işlemek zorundayım.

Karmaşık, çok iş parçacıklı uygulama kodunu test etmenin en basit yolu şudur: Test etmek çok karmaşıksa, yanlış yapıyorsunuz. Üzerinde birden çok iş parçacığı olan tek bir örneğiniz varsa ve bu iş parçacıklarının birbirinin üzerinden atladığı durumları test edemiyorsanız, tasarımınızın yeniden yapılması gerekir. Hem bu kadar basit hem de karmaşık.

Birden çok iş parçacığı için, örneklerin aynı anda akan iş parçacıklarını önleyen birçok yol vardır. En basit olanı, tüm nesnelerinizi değişmez hale getirmektir. Tabii ki, bu genellikle mümkün değildir. Dolayısıyla, tasarımınızda iş parçacıklarının aynı örnekle etkileşime girdiği yerleri tanımlamanız ve bu yerlerin sayısını azaltmanız gerekir. Bunu yaparak, çoklu iş parçacığının gerçekte meydana geldiği birkaç sınıfı ayırır ve sisteminizi test etmenin genel karmaşıklığını azaltırsınız.

Ancak, bunu yaparak bile, iki iş parçacığının birbirine bastığı her durumu test edemeyeceğinizi fark etmelisiniz. Bunu yapmak için, aynı testte aynı anda iki iş parçacığı çalıştırmanız ve ardından herhangi bir anda tam olarak hangi satırları yürüttüklerini kontrol etmeniz gerekir. Yapabileceğiniz en iyi şey bu durumu simüle etmektir. Ancak bu, test için özel olarak kodlamanızı gerektirebilir ve bu en iyi şekilde gerçek bir çözüme doğru yarım adımdır.

Muhtemelen iş parçacığı sorunları için kodu test etmenin en iyi yolu, kodun statik analizidir. İş parçacığı kodunuz sonlu bir iş parçacığı güvenli desenleri izlemiyorsa, bir sorununuz olabilir. VS kod analizi, diş çekme hakkında bazı bilgiler içerdiğine inanıyorum, ama muhtemelen çok değil.

İşler şu anda durduğunda (ve muhtemelen gelmek için iyi bir zaman boyunca duracağından), çok iş parçacıklı uygulamaları test etmenin en iyi yolu, dişli kodun karmaşıklığını olabildiğince azaltmaktır. İş parçacıklarının etkileşime girdiği alanları en aza indirin, mümkün olan en iyi şekilde test edin ve tehlikeli alanları tanımlamak için kod analizini kullanın.


1
İzin veren bir dil / çerçeve ile uğraşırsanız kod analizi harikadır. EG: Findbugs, statik değişkenlerle çok basit ve kolay paylaşılan eşzamanlılık sorunları bulacak. Bulamadığı şey, tektonlu tasarım desenleri, tüm nesnelerin birden çok kez oluşturulabileceğini varsayar. Bu eklenti, Spring gibi çerçeveler için yetersiz kalıyor.
Zombies


6
Bu iyi bir tavsiye olsa da, hala "Birden çok iş parçacığının gerekli olduğu bu minimum alanları nasıl test edebilirim?"
Bryan Rayner

5
"Test etmek çok karmaşıksa, yanlış yapıyorsunuz" - hepimiz yazmadığımız eski koda dalmak zorundayız. Bu gözlem herkese tam olarak nasıl yardımcı olur?
Ronna

2
Statik analiz muhtemelen iyi bir fikirdir, ancak test değildir. Bu yazı gerçekten nasıl test edileceği sorusuna cevap vermiyor.
Warren Dew

96

Bu sorunun gönderildiği bir süre oldu, ancak hala cevaplanmadı ...

kleolb02'nin cevabı iyi bir cevaptır. Daha fazla ayrıntıya girmeye çalışacağım.

C # kodu için pratik bir yol vardır. Birim testleri için , çok iş parçacıklı koddaki en büyük zorluk olan tekrarlanabilir testleri programlayabilmeniz gerekir . Çalışan bir test koşum, içine asenkron kod zorlayarak doğru cevabım amaçlarından Yani eşzamanlı .

Gerard Meszardos'un " xUnit Test Patterns " kitabından bir fikir ve "Humble Object" (s. 695) olarak adlandırılıyor: Çekirdek mantık kodunu ve eşzamansız kod gibi kokan her şeyi birbirinden ayırmanız gerekiyor. Bu, çekirdek mantık için senkronize olarak çalışan bir sınıfla sonuçlanır .

Bu sizi çekirdek mantık kodunu eşzamanlı olarak test etme konumuna sokar . Çekirdek mantık üzerinde yaptığınız aramaların zamanlaması üzerinde mutlak bir kontrole sahip olursunuz ve böylece tekrarlanabilir testler yapabilirsiniz . Ve bu, çekirdek mantığı ve asenkron mantığı ayırmaktan elde ettiğiniz kazançtır.

Bu çekirdek mantığın, çekirdek mantığa eşzamansız olarak çağrı almaktan ve bu çağrıları temel mantığa devretmekten sorumlu olan başka bir sınıf tarafından sarılması gerekir . Üretim kodu sadece bu sınıf aracılığıyla temel mantığa erişir. Bu sınıf sadece çağrıları devretmesi gerektiğinden, çok mantığı olmayan çok "aptal" bir sınıftır. Böylece bu asenkron işçi sınıfı için birim testlerinizi minimumda tutabilirsiniz.

Bunun üzerindeki her şey (sınıflar arasındaki etkileşimi test etmek) bileşen testleridir. Ayrıca, bu durumda, "Mütevazi Nesne" desenine bağlı kalırsanız, zamanlama üzerinde mutlak kontrol sahibi olmanız gerekir.


1
Ancak bazen iplikler birbirleriyle iyi işbirliği yapıyorsa, bir şey de test edilmelidir, değil mi? Kesinlikle cevabınızı okuduktan sonra çekirdek mantığı zaman uyumsuz kısımdan ayıracağım. Ama yine de mantığı async arabirimleri üzerinden, hepsi iş parçacığı üzerinde yapılmış bir geri arama ile test edeceğim.
CopperCash

Çok işlemcili sistemler ne olacak?
Technophile

65

Gerçekten zor biri! (C ++) birim testlerimde, bunu kullanılan eşzamanlılık desen çizgileri boyunca birkaç kategoriye ayırdım:

  1. Tek bir iş parçacığında çalışan ve iş parçacığı farkında olmayan sınıflar için birim testleri - kolay, her zamanki gibi test edin.

  2. Eşzamanlı bir genel API'yi ortaya çıkaran Monitor nesneleri (arayanların denetim iş parçacığında senkronize yöntemleri yürütenler) için birim testleri - API'yı uygulayan birden fazla sahte iş parçacığını başlatın . Pasif nesnenin iç koşullarını kullanan senaryolar oluşturun. Uzun bir süre boyunca, birden fazla iş parçacığından temelde onu yenen bir uzun koşu testi ekleyin. Bu bilimsel değil biliyorum ama güven inşa ediyor.

  3. Aktif nesneler için birim testleri (kendi iş parçacıklarını veya kontrol iş parçacıklarını içine alan olanlar) - sınıf tasarımına bağlı olarak değişikliklerle yukarıdaki # 2'ye benzer. Herkese açık API engelliyor veya engellemiyor olabilir, arayanlar vadeli işlemler alabilir, veriler kuyruklara ulaşabilir veya ayıklanmaları gerekebilir. Burada birçok kombinasyon mümkün; beyaz kutu uzakta. Yine de test edilen nesneye çağrı yapmak için birden fazla sahte iş parçacığı gerekir.

Bir yana:

Yaptığım iç geliştirici eğitiminde , Eşzamanlılık Sütunlarını ve bu iki modeli, eşzamanlılık problemlerini düşünmek ve çözmek için birincil çerçeve olarak öğretiyorum . Açıkçası daha gelişmiş kavramlar var, ancak bu temel setlerin mühendisleri çorbadan uzak tutmaya yardımcı olduğunu buldum. Ayrıca, yukarıda açıklandığı gibi, daha fazla birim test edilebilir olan koda yol açar.


51

Son yıllarda birkaç kez birkaç proje için iplik işleme kodu yazarken bu sorunla karşılaştım. Geç cevap veriyorum çünkü diğer cevapların çoğu, alternatifler sunarken test ile ilgili soruyu cevaplamıyor. Cevabım, çok iş parçacıklı koda alternatifin olmadığı durumlara yöneliktir; Ben bütünlük için kod tasarım sorunları ele almak, ama aynı zamanda birim test tartışmak.

Test edilebilir çok iş parçacıklı kod yazma

Yapılacak ilk şey, üretim iş parçacığı işleme kodunuzu, gerçek veri işleme yapan tüm kodlardan ayırmaktır. Bu şekilde, veri işleme tek iş parçacıklı kod olarak test edilebilir ve çok iş parçacıklı kodun yaptığı tek şey iş parçacıklarını koordine etmektir.

Hatırlanması gereken ikinci şey, çok iş parçacıklı koddaki hataların olasılıklı olduğudur; en az sıklıkla kendini gösteren hatalar, üretime gizlice girecek, üretimde bile yeniden üretilmesi zor olacak ve bu nedenle en büyük sorunlara neden olacak hatalardır. Bu nedenle, kodu hızlı bir şekilde yazmanın ve ardından çalışana kadar hata ayıklamanın standart kodlama yaklaşımı, çok iş parçacıklı kod için kötü bir fikirdir; kolay hataların sabit olduğu ve tehlikeli hataların hala orada olduğu kodla sonuçlanacaktır.

Bunun yerine, çok iş parçacıklı kod yazarken, kodu ilk başta hataları yazmaktan kaçınacağınız tutumuyla yazmalısınız. Veri işleme kodunu doğru bir şekilde kaldırdıysanız, iplik işleme kodu, hata yazmadan ve kesinlikle çok fazla hata yazmadan yazma şansınız olacak kadar küçük olmalıdır - tercihen birkaç satır, en az birkaç düzine satır -. , iplik geçirmeyi anlarsanız, zaman ayırın ve dikkatli olun.

Çok iş parçacıklı kod için yazı birimi testleri

Çok iş parçacıklı kod olabildiğince dikkatli bir şekilde yazıldıktan sonra, bu kod için testler yazmaya hala değer. Testlerin birincil amacı, yüksek zamanlama bağımlı yarış koşulu hatalarını test etmek için çok fazla değildir - bu tür yarış koşullarını tekrar tekrar test etmek imkansızdır - daha ziyade, bu tür hataları önlemek için kilitleme stratejinizin birden fazla iş parçacığının amaçlandığı şekilde etkileşime izin verdiğini test etmek .

Doğru kilitleme davranışını düzgün bir şekilde test etmek için, bir testin birden fazla iş parçacığı başlatması gerekir. Testi tekrarlanabilir hale getirmek için, iş parçacıkları arasındaki etkileşimlerin öngörülebilir bir sırada olmasını istiyoruz. Testteki dişleri harici olarak senkronize etmek istemiyoruz, çünkü bu, dişlerin harici olarak senkronize edilmediği yerlerde üretimde oluşabilecek hataları maskeleyecektir. Bu, çok iş parçacıklı kod testleri yazmak zorunda kaldığımda başarılı bir şekilde kullandığım teknik olan iplik senkronizasyonu için zamanlama gecikmelerinin kullanımını bırakır.

Gecikmeler çok kısaysa, test kırılgan hale gelir, çünkü testlerin çalıştırılabileceği farklı makineler arasındaki küçük zamanlama farklılıkları zamanlamanın kapanmasına ve testin başarısız olmasına neden olabilir. Tipik olarak yaptığım, test hatalarına neden olan gecikmelerle başlamak, testin geliştirme makineme güvenilir bir şekilde geçmesi için gecikmeleri arttırmak ve daha sonra gecikmeleri iki katına çıkarmak, böylece testin diğer makinelere geçme şansı yüksektir. Bu, testin makroskopik bir süre alacağı anlamına gelir, ancak benim deneyimime göre, dikkatli test tasarımı bu süreyi bir düzineden fazla sürmeyebilir. Uygulamanızda iş parçacığı koordinasyon kodu gerektiren çok fazla yere sahip olmamanız gerektiğinden, bu test paketiniz için kabul edilebilir olmalıdır.

Son olarak, testiniz tarafından yakalanan hataların sayısını takip edin. Testinizde% 80 kod kapsama alanı varsa, hatalarınızın yaklaşık% 80'ini yakalaması beklenebilir. Testiniz iyi tasarlanmış ancak hiçbir hata bulamıyorsa, yalnızca üretimde görünecek ek hatalara sahip olmamanızın makul bir şansı vardır. Test bir veya iki hata yakalarsa, yine de şanslı olabilirsiniz. Bunun ötesinde, kod işleme kodunuzu dikkatli bir şekilde gözden geçirmeyi veya hatta tamamen yeniden yazmayı düşünmek isteyebilirsiniz, çünkü kod hala kod üretilinceye kadar bulmak çok zor olacak gizli hatalar içerir ve çok o zaman düzeltmesi zor.


3
Test, sadece hataların varlığını ortaya çıkarabilir, yokluklarını gösteremez. Orijinal soru 2 iplikli bir soruyu soruyor, bu durumda kapsamlı testler mümkün olabilir, ancak çoğu zaman mümkün değildir. En basit senaryoların ötesinde herhangi bir şey için mermiyi ısırmanız ve resmi yöntemleri kullanmanız gerekebilir - ancak birim testlerini atlamayın! İlk etapta doğru çok iş parçacıklı kod yazmak zordur, ancak aynı derecede zor bir sorun da regresyona karşı geleceğe hazırdır.
Paul Williams

4
En az anlaşılmış yollardan birinin şaşırtıcı özeti. Cevabınız, ppl'nin genellikle göz ardı ettiği gerçek ayrımcılığa patlama.
16'da

1
Bu uzunluktaki sadece birkaç yüz
Toby Speight

1
@TobySpeight Testler normal birim testlerine kıyasla uzundur. Eğer dişli kod düzgün olabildiğince basit olacak şekilde tasarlanmışsa yarım düzine testin fazlasıyla yeterli olduğunu gördüm - birkaç yüz çoklu okuma testine ihtiyaç duyulması neredeyse kesinlikle aşırı karmaşık bir diş açma düzenlemesini gösterecektir.
Warren Dew

2
Bu, iş parçacığı mantığınızı işlevden olabildiğince ayrı tutmak için iyi bir argüman (biliyorum, söylenenden çok daha kolay). Ve mümkünse, test paketini "her değişiklik" ve "ön taahhüt" setlerine bölmek (böylece dakikadan dakikaya testleriniz çok fazla etkilenmez).
Toby Speight

22

Ayrıca çok iş parçacıklı kodları test ederken ciddi sorunlar yaşadım. Sonra Gerard Meszaros tarafından "xUnit Test Patterns" gerçekten harika bir çözüm buldum. Anlattığı desene Humble nesnesi denir .

Temel olarak, mantığı ortamından ayrılan, test edilmesi kolay ayrı bir bileşene nasıl çıkarabileceğinizi açıklar. Bu mantığı test ettikten sonra, karmaşık davranışı test edebilirsiniz (çoklu iş parçacığı, eşzamansız yürütme vb.)


20

Etrafında oldukça iyi olan birkaç araç var. İşte bazı Java'ların özeti.

Bazı iyi statik analiz araçları içermektedir FindBugs (bazı yararlı ipuçları verir), jlint , Java Pathfinder (JPF & JPF2) ve Bogor .

MultithreadedTC , kendi test durumlarınızı ayarlamanız gereken oldukça iyi bir dinamik analiz aracıdır (JUnit'e entegre edilmiştir).

IBM Research'ten ConTest ilginçtir. Böcekleri rastgele ortaya çıkarmak için her türlü iplik değiştirme davranışını (örneğin uyku ve verim) ekleyerek kodunuzu düzenler.

SPIN , Java (ve diğer) bileşenlerinizi modellemek için gerçekten harika bir araçtır, ancak bazı yararlı çerçevelere sahip olmanız gerekir. Olduğu gibi kullanmak zordur, ancak nasıl kullanılacağını biliyorsanız çok güçlüdür. Oldukça az sayıda araç kaputun altında SPIN kullanıyor.

MultithreadedTC muhtemelen en yaygın olanıdır, ancak yukarıda listelenen statik analiz araçlarından bazıları kesinlikle bakmaya değer.


16

Beklenebilirlik , deterministik birim testleri yazmanıza yardımcı olabilir. Sisteminizde bir yerde bir durumun güncellenmesini beklemenizi sağlar. Örneğin:

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

veya

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

Ayrıca Scala ve Groovy desteği de var.

await until { something() > 4 } // Scala example

1
Beklenmedik parlak - tam olarak ne aradığını!
Forge_7

14

Dişli kodu ve genel olarak çok karmaşık sistemleri test etmenin bir başka yolu da Fuzz Test'idir . Harika değil ve her şeyi bulamaz, ancak yararlı olması ve yapması kolay olması muhtemeldir.

Alıntı:

Fuzz testi veya fuzzing, bir programın girişlerine rastgele veri ("fuzz") sağlayan bir yazılım test tekniğidir. Program başarısız olursa (örneğin, kilitlenerek veya yerleşik kod önerilerini kaldırarak), hatalar not edilebilir. Fuzz testinin en büyük avantajı, test tasarımının son derece basit ve sistem davranışı ile ilgili önyargıları içermemesidir.

...

Fuzz testi genellikle kara kutu testi kullanan büyük yazılım geliştirme projelerinde kullanılır. Bu projeler genellikle test araçları geliştirmek için bir bütçeye sahiptir ve bulanıklık testi, yüksek fayda-maliyet oranını sunan tekniklerden biridir.

...

Bununla birlikte, fuzz testi, kapsamlı testlerin veya resmi yöntemlerin yerine geçmez: sistemin davranışının rastgele bir örneğini sağlayabilir ve birçok durumda bir fuzz testini geçmek, bir yazılımın istisnaları çökmeden yerine ele aldığını gösterebilir doğru davranmak. Bu nedenle, fuzz testi, kalite güvencesi yerine sadece bir hata bulma aracı olarak kabul edilebilir.


13

Bunların çoğunu yaptım ve evet berbat.

Bazı ipuçları:

  • Birden çok test iş parçacığı çalıştırmak için GroboUtils
  • harmanlamaların yinelemeler arasında değişmesine neden olmak için alphaWorks ConTest'ten enstrüman sınıflarına
  • Bir throwablealan oluşturun ve kontrol edin tearDown(bkz. Liste 1). Başka bir iş parçacığında kötü bir istisna yakalarsanız, bunu atılabilir olarak atamanız yeterlidir.
  • Liste 2'de utils sınıfını oluşturdum ve paha biçilmez, özellikle waitForVerify ve waitForCondition'ı buldum, bu da testlerinizin performansını büyük ölçüde artıracak.
  • AtomicBooleanTestlerinizde iyi kullanın . İş parçacığı güvenlidir ve geri arama sınıflarından ve benzerlerinden değerleri saklamak için genellikle bir son başvuru türüne ihtiyacınız olacaktır. Liste 3'teki örneğe bakın.
  • @Test(timeout=60*1000)Eşzamanlılık testleri bazen bozulduğunda sonsuza kadar durabileceğinden , testinize her zaman bir zaman aşımı (örn. ) Verdiğinizden emin olun .

Liste 1:

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

Liste 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;
    }
}
}

Liste 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());
}

2
Zaman aşımı iyi bir fikirdir, ancak bir test zaman aşımına uğrarsa, bu çalışmadaki sonraki sonuçlar şüphelidir. Zaman aşımına uğramış testte hala sizi çalıştırabilecek bazı iş parçacıkları olabilir.
Don Kirkby

12

MT kodunun doğruluğunu test etmek, daha önce de belirtildiği gibi, oldukça zor bir sorundur. Sonunda, kodunuzda yanlış senkronize edilmiş veri yarışları olmadığından emin olmak için kaynar. Buradaki sorun, üzerinde çok fazla kontrole sahip olmadığınız sonsuz sayıda iplik yürütme (araya ekleme) olasılığı olmasıdır ( bu makaleyi mutlaka okuyun ). Basit senaryolarda, akıl yürütme ile doğruluk kanıtlamak mümkün olabilir, ancak genellikle durum böyle değildir. Özellikle senkronizasyonu önlemek / en aza indirmek ve en belirgin / en kolay senkronizasyon seçeneğini kullanmak istemiyorsanız.

Takip ettiğim bir yaklaşım, potansiyel olarak tespit edilemeyen veri yarışlarının gerçekleşmesi için yüksek eşzamanlı test kodu yazmaktır. Ve sonra bu testleri bir süre çalıştırdım :) Bir zamanlar bazı bilgisayar bilimcilerinin bunu yapan bir araç gösterdiği bir konuşma üzerine tökezledim (spesifikasyonlardan rastgele testler yaptıktan sonra onları çılgınca, eşzamanlı olarak, tanımlanmış değişmezleri kontrol ederek kırılması).

Bu arada, MT kodunu test etmenin bu yönünün burada belirtilmediğini düşünüyorum: rastgele kontrol edebileceğiniz kodun değişmezlerini tanımlayın. Ne yazık ki, bu değişmezleri bulmak da oldukça zor bir problem. Ayrıca, yürütme sırasında her zaman beklemeyebilirler, bu yüzden doğru olmasını bekleyebileceğiniz yürütme noktalarını bulmanız / uygulamanız gerekir. Kod yürütmeyi böyle bir duruma getirmek de zor bir sorundur (ve eşzamanlılık sorunlarına neden olabilir. Vay canına, bu çok zor!

Okumak için bazı ilginç bağlantılar:


yazar testte randomizasyonu ifade eder. QuickCheck olabilir , bu birçok dile taşınmıştır. Sen eşzamanlı sistem için bu tür testlere konuşmak izleyebilirsiniz burada
Max

6

Pete Goodliffe , dişli kodun birim testinde bir seriye sahiptir .

Zor. Daha kolay bir yoldan gidiyorum ve gerçek koddan iş parçacığı kodunu soyut tutmaya çalışıyorum. Pete benim yaptığım yolun yanlış olduğunu söylüyor ama ayrılığı doğru anladım ya da şanslıydım.


6
Şimdiye kadar yayınlanan iki makaleyi okudum ve çok yararlı bulmadım. Çok fazla somut tavsiye vermeden zorluklardan bahsediyor. Belki gelecekteki makaleler düzelecektir.
Don Kirkby

6

Java için, JCIP'nin 12. bölümüne bakın . En azından eşzamanlı kodun doğruluğunu ve değişmezlerini test etmek için deterministik, çok iş parçacıklı birim testleri yazmanın bazı somut örnekleri vardır.

Birim testleri ile "kanıtlama" iplik güvenliği çok daha kararlıdır. Benim inancım bunun çeşitli platformlarda / konfigürasyonlarda otomatik entegrasyon testiyle daha iyi sunulmasıdır.


6

Paralel iş parçacıkları üzerinde yürütmek için iki veya daha fazla test yöntemi yazmayı seviyorum ve her biri test edilen nesneye çağrı yapıyor. Farklı iş parçacıklarından gelen çağrıların sırasını koordine etmek için Sleep () çağrılarını kullanıyorum, ancak bu gerçekten güvenilir değil. Ayrıca çok daha yavaştır, çünkü zamanlamanın genellikle işe yarayacağı kadar uzun uyumanız gerekir.

Çok iş parçacıklı TC Java kitaplığını FindBugs yazan aynı gruptan buldum . Sleep () kullanmadan olayların sırasını belirlemenizi sağlar ve güvenilirdir. Henüz denemedim.

Bu yaklaşımın en büyük sınırlaması, sadece sorun yaratacağından şüphelendiğiniz senaryoları test etmenize izin vermesidir. Diğerlerinin söylediği gibi, çok iş parçacıklı kodunuzu, bunları kapsamlı bir şekilde test etme umuduna sahip olmak için az sayıda basit sınıfa ayırmanız gerekir.

Soruna neden olmasını beklediğiniz senaryoları dikkatle test ettikten sonra, bir süre aynı anda istekleri bir araya getiren bilimsel olmayan bir test, beklenmedik sorunlara bakmak için iyi bir yoldur.

Güncelleme: Çok iş parçacıklı TC Java kitaplığıyla biraz oynadım ve iyi çalışıyor. Ayrıca TickingTest adını verdiğim bir .NET sürümüne bazı özelliklerini taşıdım .


5

Dişli parçaların ünite testlerini, kontrol ve izolasyon çerçevelerinin tersine çevrilmesi gibi herhangi bir ünite testinde yaptığım gibi işlerim. Net-arenada ve kutudan çıkarken diş açmanın (diğer şeylerin yanı sıra) tamamen izole edilmesi çok zor (neredeyse imkansız diyebilirim).

Bu yüzden böyle bir şeye benzeyen sarmalayıcılar yazdım (basitleştirilmiş):

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

Oradan IThreadingManager'ı bileşenlerime kolayca enjekte edebilir ve test sırasında beklediğim gibi davranması için tercih edilen izolasyon çerçevemi kullanabilirim.

Şimdiye kadar benim için harika çalıştı ve aynı yaklaşımı thread havuzu, System.Environment, Sleep vb.


5

İlgili cevabıma bir göz atın

Özel bir Bariyer için Test sınıfı tasarlama

Java'ya karşı önyargılıdır, ancak seçeneklerin makul bir özetine sahiptir.

Özetle (IMO) doğruluk sağlayacak bazı fantezi çerçeve kullanımı değil, size çok iş parçacıklı kod tasarımı hakkında nasıl gidin. Endişeleri (eşzamanlılık ve işlevsellik) bölmek, güveni artırmanın çok büyük bir yoludur. Testler Tarafından Yönlendirilen Büyüyen Nesne Yönelimli Yazılım , bazı seçenekleri benden daha iyi açıklıyor.

Statik analiz ve biçimsel yöntemler (bkz. Eşzamanlılık: Durum Modelleri ve Java Programları ) bir seçenektir ancak bunları ticari geliştirmede sınırlı kullanım olarak buldum.

Herhangi bir yük / ıslatma stili testinin problemleri vurgulamak için nadiren garanti edildiğini unutmayın.

İyi şanslar!


Ayrıca tempus-fugitburada kütüphanenizi de belirtmelisiniz helps write and test concurrent code;)
Idolon

4

Kısa süre önce Threadsafe adlı bir araç (Java için) keşfettim. Findbugs'a çok benzeyen ancak özellikle çok iş parçacıklı sorunları tespit etmek için statik bir analiz aracıdır. Test için bir yedek değildir, ancak güvenilir çok iş parçacıklı Java yazmanın bir parçası olarak önerebilirim.

Hatta, sınıf toplamı, güvensiz nesnelere eşzamanlı sınıflar aracılığıyla erişme ve çift kontrollu kilitleme paradigmasını kullanırken eksik uçucu değiştiricileri tespit etme gibi şeyler etrafında çok ince bazı potansiyel sorunları yakalar.

Çok iş parçacıklı Java yazarsanız, denemek .


3

Aşağıdaki makalede 2 çözüm önerilmektedir. Bir semaforu (CountDownLatch) kaydırma ve dahili iş parçacığından verileri harici hale getirme gibi işlevler ekler. Bu amaca ulaşmanın bir başka yolu da Thread Pool'u kullanmaktır (bakınız İlgi Çekici Noktalar).

Sprinkler - Gelişmiş senkronizasyon nesnesi


3
Lütfen buradaki yaklaşımları açıklayın, gelecekte dış bağlantılar ölü olabilir.
Uooo

2

Geçen haftanın çoğunu eşzamanlı kodda hata ayıklama üzerine çalışan bir üniversite kütüphanesinde geçirdim. Temel sorun eşzamanlı kod deterministik değildir. Tipik olarak, akademik hata ayıklama buradaki üç kamptan birine düşmüştür:

  1. Olay iz / oynatma. Bu, bir olay izleyicisi ve daha sonra gönderilen olayları gözden geçirmeyi gerektirir. Bir UT çerçevesinde, bu, olayların bir testin parçası olarak el ile gönderilmesini ve daha sonra ölüm sonrası incelemeler yapılmasını içerir.
  2. Scriptable. Burada, bir dizi tetikleyici ile çalışan kodla etkileşime girersiniz. "X> foo üzerinde baz ()". Bu, belirli bir koşulda belirli bir testi tetikleyen bir çalışma zamanı sisteminizin olduğu bir UT çerçevesi olarak yorumlanabilir.
  3. Etkileşimli. Bu otomatik bir test durumunda işe yaramaz. ;)

Şimdi, yukarıdaki yorumcuların fark ettiği gibi, eşzamanlı sisteminizi daha belirleyici bir hale getirebilirsiniz. Ancak, bunu düzgün bir şekilde yapmazsanız, tekrar sıralı bir sistem tasarlamaya geri dönersiniz.

Benim önerim, neyin dişli hale getirildiği ve neyin dişli haline getirilmediğine dair çok katı bir tasarım protokolüne odaklanmak olacaktır. Arayüzünüzü, elemanlar arasında minimum bağımlılık olacak şekilde kısıtlarsanız, çok daha kolaydır.

İyi şanslar ve sorun üzerinde çalışmaya devam edin.


2

Dişli kod test talihsiz bir görev vardı ve onlar kesinlikle şimdiye kadar yazdığım en zor testler.

Testlerimi yazarken delege ve etkinliklerin bir kombinasyonunu kullandım. Temel olarak , bu anketlerin PropertyNotifyChangedbir WaitCallbackya da bir tür olaylarını kullanmakla ilgilidir ConditionalWaiter.

Bunun en iyi yaklaşım olup olmadığından emin değilim, ama benim için çalıştı.


1

"Çok iş parçacıklı" kod altında varsaymak,

  • durumlu ve değişebilir
  • VE aynı anda birden çok iş parçacığı tarafından erişildi / değiştirildi

Başka bir deyişle, günümüzde çok nadir bir canavar olması gereken özel durumlu güvenli iş parçacığı sınıfını / yöntemini / birimini test etmekten bahsediyoruz .

Bu canavar nadir olduğu için, öncelikle onu yazmak için tüm geçerli mazeretler olduğundan emin olmamız gerekir.

Adım 1. Aynı senkronizasyon bağlamında durumu değiştirmeyi düşünün.

Bugün, oluşturulabilen eşzamanlı ve eşzamansız kod yazmak kolaydır; burada IO veya diğer yavaş işlemler arka plana indirilir, ancak paylaşılan durum bir senkronizasyon bağlamında güncellenir ve sorgulanır. örneğin async / await görevleri ve .NET'te Rx vb. - hepsi tasarımla test edilebilir, "gerçek" Görevler ve zamanlayıcılar, testi deterministik hale getirmek için değiştirilebilir (ancak bu sorunun kapsamı dışındadır).

Kulağa çok kısıtlı gelebilir, ancak bu yaklaşım şaşırtıcı derecede iyi çalışır. Herhangi bir durumu iş parçacığı için güvenli hale getirmeye gerek kalmadan bu tarzdaki tüm uygulamaları yazmak mümkündür (yapıyorum).

Adım 2. Tek senkronizasyon bağlamında paylaşılan durumun manipüle edilmesi kesinlikle mümkün değilse.

Tekerleğin yeniden icat edilmediğinden emin olun / kesinlikle işe göre uyarlanabilecek standart bir alternatif yok. Kodun çok uyumlu olması ve bir ünite içinde yer alması muhtemeldir, örneğin iyi bir şansla, karma harita veya toplama veya herhangi bir şey gibi standart iş parçacığı güvenli veri yapısının özel bir durumudur.

Not: kod büyükse / birden fazla sınıfa yayılıyorsa ve çok iş parçacıklı durum manipülasyonuna ihtiyaç duyuyorsa, tasarımın iyi olmaması ihtimali çok yüksektir, Adım 1'i yeniden düşünün

Adım 3. Bu adıma ulaşılırsa , kendi özel durum bilgisi olan güvenli iplik sınıfımızı / yöntemimizi / birimimizi test etmemiz gerekir .

Dürüst olacağım: Asla böyle bir kod için uygun testler yazmak zorunda kalmadım. Çoğu zaman Adım 1'de, bazen Adım 2'de kaçıyorum. Son kez özel iş parçacığı güvenli kod yazmak zorunda kaldım, yıllar önce birim testini benimsemeden önceydi / muhtemelen yazmam gerekmeyecekti zaten mevcut bilgi ile.

Gerçekten böyle bir kodu test etmek zorunda kaldım ( sonunda, gerçek cevap ) o zaman aşağıdaki birkaç şey denemek istiyorum

  1. Deterministik olmayan stres testi. örneğin 100 iş parçacığını aynı anda çalıştırın ve son sonucun tutarlı olup olmadığını kontrol edin. Bu, birden fazla kullanıcı senaryosunun daha yüksek seviye / entegrasyon testi için daha tipik olmakla birlikte, birim düzeyinde de kullanılabilir.

  2. Testin, bir iş parçacığının diğerinden önce çalışması gereken belirleyici senaryolar oluşturmaya yardımcı olmak için bazı kodlar yerleştirebileceği bazı test 'kancalarını' ortaya çıkarın. Olduğu kadar çirkin, daha iyi bir şey düşünemiyorum.

  3. İş parçacıklarını belirli bir sırada çalıştırmak ve gerçekleştirmek için gecikmeye dayalı testler. Kesinlikle söylemek gerekirse, bu tür testler de deterministik değildir (sistemde donma / dünyayı durdurma GC koleksiyonu, aksi takdirde orkestre edilen gecikmeleri bozabilir), ayrıca çirkindir, ancak kancalardan kaçınmaya izin verir.


0

J2E kodu için, iş parçacıklarının eşzamanlılık testi için SilkPerformer, LoadRunner ve JMeter kullandım. Hepsi aynı şeyi yapıyor. Temel olarak, TCP / IP veri akışını analiz etmek ve uygulama sunucunuza aynı anda istekte bulunan birden fazla kullanıcıyı simüle etmek için gerekli olan proxy sunucu sürümlerini yönetmek için nispeten basit bir arayüz sağlarlar. Proxy sunucusu, isteği işledikten sonra sunucuya gönderilen tüm sayfayı ve URL'yi ve sunucudan gelen yanıtı sunarak, yapılan istekleri analiz etme gibi şeyler yapabilmenizi sağlayabilir.

Güvenli olmayan http modunda bazı hataları bulabilirsiniz, burada en azından gönderilen form verilerini analiz edebilir ve her kullanıcı için sistematik olarak değiştirebilirsiniz. Ancak gerçek testler, https (Güvenli Soket Katmanları) ile çalıştırdığınız zamandır. Daha sonra, oturum ve çerez verilerini sistematik olarak değiştirmekle uğraşmak zorundasınız, bu da biraz daha kıvrımlı olabilir.

Eşzamanlılığı test ederken bulduğum en iyi hata, geliştiricinin oturum açarken LDAP sunucusuna girişte kurulan bağlantı isteğini kapatmak için Java çöp toplama işlemine güvendiğini keşfettiğimde ortaya çıktı. diğer kullanıcıların oturumlarına ve çok kafa karıştırıcı sonuçlara, sunucu dizlerine getirildiğinde ne olduğunu analiz etmeye çalışırken, her birkaç saniyede bir zar zor bir işlem gerçekleştirebilir.

Sonunda, siz veya birileri muhtemelen bahsettiğim gibi blunders kodunu analiz edip analiz etmeniz gerekecek. Ve yukarıda açıklanan sorunu ortaya çıkardığımız gibi, departmanlar arasında açık bir tartışma en yararlı olanıdır. Ancak bu araçlar çok iş parçacıklı kodu test etmek için en iyi çözümdür. JMeter açık kaynak kodludur. SilkPerformer ve LoadRunner tescillidir. Uygulamanızın iş parçacığı güvenli olup olmadığını gerçekten bilmek istiyorsanız, büyük çocuklar bunu yapar. Bunu profesyonel olarak çok büyük şirketler için yaptım, bu yüzden tahmin etmiyorum. Kişisel deneyimlerimden bahsediyorum.

Dikkatli bir kelime: bu araçları anlamak biraz zaman alır. Çok iş parçacıklı programlamaya daha önceden maruz kalmadıkça, sadece yazılımı yüklemek ve GUI'yi çalıştırmak meselesi olmayacaktır. Anlamak için 3 kritik alan kategorisini (formlar, oturum ve çerez verileri) tanımlamaya çalıştım. tüm belgeler.


0

Eşzamanlılık, bellek modeli, donanım, önbellekler ve kodumuz arasında karmaşık bir etkileşimdir. Java söz konusu olduğunda, en azından bu tür testler kısmen jcstress tarafından ele alınmıştır . Bu kütüphaneyi oluşturanların birçok JVM, GC ve Java eşzamanlılık özelliklerinin yazarı olduğu bilinmektedir.

Ancak bu kütüphane bile Java Bellek Modeli spesifikasyonu hakkında iyi bilgiye ihtiyaç duyar, böylece ne test ettiğimizi tam olarak biliriz. Ama bence bu çabanın odak noktası mircobenchmarks. Büyük iş uygulamaları değil.


0

Konuyla ilgili, örnek kodda Rust olarak Rust kullanan bir makale var:

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

Özet olarak, hile eşzamanlı mantığınızı yazmaktır, böylece kanallar ve kondvarlar gibi araçlar kullanarak birden fazla yürütme iş parçacığında yer alan determinizme karşı sağlamdır.

Daha sonra, "bileşenlerinizi" bu şekilde yapılandırdıysanız, bunları test etmenin en kolay yolu, onlara mesaj göndermek için kanalları kullanmaktır ve daha sonra bileşenin belirli beklenen mesajları gönderdiğini iddia etmek için diğer kanalları engellemektir.

Bağlantılı makale, birim testleri kullanılarak tamamen yazılmıştır.


-1

Basit yeni Thread (runnable) .run () test ediyorsanız runnable'ı sırayla çalıştırmak için Thread'i alay edebilirsiniz

Örneğin, test edilen nesnenin kodu böyle bir yeni iş parçacığı çağırıyorsa

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

Sonra yeni iş parçacıkları alay ve çalıştırılabilir bağımsız değişkeni sırayla çalıştırmak yardımcı olabilir

@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 
}

-3

(mümkünse) iş parçacıkları kullanmayın, aktörler / aktif nesneler kullanın. Kolay test edilebilir.


2
@OMTheEternity belki ama yine de en iyi cevap imo.
Dereotu

-5

Test örneği iş parçacığı güvenli yapmak için EasyMock.makeThreadSafe kullanabilirsiniz


Bu, çok iş parçacıklı kodu test etmenin olası bir yolu değildir. Sorun, test kodunun çok iş parçacıklı çalıştığı değil, genellikle çok iş parçacıklı çalışan kodu sınamanızdır. Ve her şeyi senkronize edemezsiniz çünkü artık veri yarışlarını test etmiyorsunuz.
bennidi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.