Testing multi-threading
I'm trying to make sure that my implementation of ObjC multithreading is proper when I transfer the application from single-thread to multiple.
Right now I have unit tests set up to make sure everything works properly in the single-thread envi开发者_如何学Cronment.
What can I do to make sure this holds in multi-threads? I'd like to continue to use unit-testing but not sure how to go about testing multithreading in unit tests.
Clarification: I am implementing the multi-threading using NSBlockOperation / NSOperationQueue.
To test something like this you're going to want to control NSOperationQueue
in your tests.
Suppose the class you are testing is called MySubject
. First, you need to refactor it so that we can inject an NSOperationQueue
--that will allow use to replace it for testing. So get rid of all occurrences of [NSOperationQueue mainQueue]
and replace them with a class variable. Initialize that class variable from a parameter in MySubject
's constructor. As a result you'll have to change all instantiations of MySubject
to pass [NSOperationQueue mainQueue]
.
@interface MySubject: NSObject {
NSOperationQueue* operationQueue;
}
@end
@implementation MySubject
-(MySubject*)initWithOperationQueue:(NSOperationQueue*)queue {
if ( self = [super init] ) {
self.operationQueue = [queue retain];
}
return self;
}
-(void)dealloc {
[operationQueue release];
}
-(void)startOperations {
[operationQueue addOperation:...];
[operationQueue addOperation:...];
}
@end
And clients now look like this:
subject = [[MySubject alloc] initWithOperationQueue:[NSOperationQueue mainQueue]];
[subject startOperations];
Now for the tests, you can create a simple queue for testing... it'll need to implement whatever methods are used by your subject.
@interface MyTestOperationQueue: NSMutableArray {
}
@end
@implementation MySubject
-(void)addOperation:(NSOperation*)operation {
[self addObject:operation];
}
@end
And now your test can looks like this:
testQueue = [[MyTestOperationQueue alloc] init];
subject = [[MySubject alloc] initWithOperationQueue:testQueue];
[subject startOperations];
// You may want to have other tests that execute the queue operations
// in a different order
[[testQueue objectAtIndex:0] start];
[[testQueue objectAtIndex:0] waitUntilFinished];
[[testQueue objectAtIndex:1] start];
[[testQueue objectAtIndex:1] waitUntilFinished];
// Verify results
Of course, this kind of test won't be able to verify that the concurrent operations are safe to be executing simultaneously, but this can cover a lot of the cases that you'll be interested in when trying to design you classes.
In short, you can't. At least, you can't get anywhere near 100% testing coverage. With threads, everything from simultaneous I/O to core count to memory pressure to activity in other processes (or same process) will impact thread scheduling.
For unit tests, you typically want the unit test to block until the threaded subsystem does whatever it needs to do. Often the unit testing thread will asynchronously spawn whatever work needs to be done, then block until a signal occurs (the thread kind of signal, not signal()). There are many different ways to do this, obviously, and much of the details will be specific to your app; need run loops? using GCD (hopefully)? etc....
精彩评论