Ich habe das schon oft gemacht, und ja, es ist ätzend.
Einige Tipps:
- GroboUtils für die Ausführung mehrerer Test-Threads
- alphaWorks ConTest Klassen zu instrumentieren, damit die Verschachtelungen zwischen den Iterationen variieren
- Erstellen einer
throwable
und überprüfen Sie es in tearDown
(siehe Listing 1). Wenn Sie eine schlechte Ausnahme in einem anderen Thread abfangen, weisen Sie sie einfach throwable zu.
- Ich habe die utils-Klasse in Listing 2 erstellt und habe festgestellt, dass sie von unschätzbarem Wert ist, insbesondere waitForVerify und waitForCondition, die die Leistung Ihrer Tests erheblich steigern werden.
- Nutzen Sie die
AtomicBoolean
in Ihren Tests. Es ist thread-sicher, und Sie werden oft einen finalen Referenztyp benötigen, um Werte von Callback-Klassen und ähnlichem zu speichern. Siehe das Beispiel in Listing 3.
- Stellen Sie sicher, dass Ihr Test immer eine Zeitüberschreitung enthält (z.B.,
@Test(timeout=60*1000)
), da Gleichzeitigkeitstests manchmal ewig hängen bleiben können, wenn sie kaputt sind.
Auflistung 1:
@After
public void tearDown() {
if ( throwable != null )
throw throwable;
}
Auflistung 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;
}
}
}
Auflistung 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 Stimmen
Ich hatte vor, eine Frage zu genau diesem Thema zu stellen. Obwohl Will im Folgenden viele gute Punkte anführt, denke ich, dass wir es besser machen können. Ich stimme zu, dass es keinen einzigen "Ansatz" gibt, um das Problem sauber zu lösen. Aber "so gut wie möglich zu testen" setzt die Messlatte sehr niedrig an. Ich werde mit meinen Ergebnissen zurückkommen.
0 Stimmen
In Java: Das Paket java.util.concurrent enthält einige schlecht bekannte Klassen, die helfen können, deterministische JUnit-Tests zu schreiben. Werfen Sie einen Blick auf - CountDownLatch - Semaphor - Austauscher
0 Stimmen
Können Sie bitte einen Link zu Ihrer vorherigen Frage zu Unit-Tests angeben?
0 Stimmen
@Andrew Grimm: stackoverflow.com/questions/11060/
0 Stimmen
stackoverflow.com/questions/4418373/
20 Stimmen
Ich denke, es ist wichtig, darauf hinzuweisen, dass diese Frage 8 Jahre alt ist und die Anwendungsbibliotheken in der Zwischenzeit einen weiten Weg zurückgelegt haben. In der "modernen Ära" (2016) kommt Multi-Thread-Entwicklung vor allem in eingebetteten Systemen vor. Wenn Sie jedoch an einer Desktop- oder Telefonanwendung arbeiten, sollten Sie sich zunächst über die Alternativen informieren. Anwendungsumgebungen wie .NET enthalten inzwischen Tools, die wahrscheinlich 90 % der üblichen Multi-Threading-Szenarien verwalten oder stark vereinfachen. (asnync/await, PLinq, IObservable, die TPL...). Multithreading-Code ist schwierig. Wenn man das Rad nicht neu erfindet, muss man es auch nicht neu testen.
0 Stimmen
Dies mag eine unpopuläre Idee sein, aber wenn Sie Ihren Code in Rust schreiben, ohne
unsafe
Blöcken kann der Compiler tatsächlich rennfreie Threadsicherheit garantieren. Für mich ist dies ein noch wichtigeres Merkmal von Rust als die Speichersicherheit, die normalerweise erwähnt wird.