[英]How can I unit test a method that depends on a volatile variable in Objective-C?
Here's a simplified version of my class: 这是我班的简化版:
@interface RTMovieBuilder : NSObject
@property (atomic, getter = isCancelled) volatile BOOL cancelled;
@property (nonatomic, weak) id<BuilderDelegate>delegate;
- (void)moviesFromJSON:(id)JSON;
- (Movie *)movieFromDictionary:(NSDictionary *)dict;
- (void)cancel;
@end
@implementation RTMovieBuilder
- (void)moviesFromJSON:(id)JSON
{
// Check for errors -> If good, then do...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self syncrouslyCreateMoviesFromJSON:JSON];
});
}
- (void)syncrouslyCreateMoviesFromJSON:(id)JSON
{
NSMutableArray *movies = [NSMutableArray array];
for (NSDictionary *dict in JSON)
{
if ([self isCancelled])
return;
else
[movies addObject:[self movieFromDictionary:dict]];
}
[self notifyDelegateCreatedObjects:movies];
}
- (Movie *)movieFromDictionary:(NSDictionary *)dict
{
Movie *movie = [[Movie alloc] init];
// Set movie properties based on dictionary...
return movie;
}
- (void)cancel
{
[self setCancelled:YES];
}
// ... Other methods omitted for brevity's sake
@end
The property cancelled
is atomic
and volatile
because it may be accessed by other threads (ie the main thread may call cancel
method to stop the operation). cancelled
的属性是atomic
volatile
因为它可以被其他线程访问(即主线程可以调用cancel
方法来停止操作)。 (I believe these are needed, if not, please note why it's not in your answer.) (我认为这些是必需的,如果不需要,请注意为什么它不在您的答案中。)
I am trying to write unit tests to make sure this will work before writing the view controller class. 我正在尝试编写单元测试,以确保在编写视图控制器类之前可以进行测试。
How can I write a unit test that will simulate a call to cancel
while RTMovieBuilder
is in the middle of creating movies
? 如何在
RTMovieBuilder
制作movies
的过程中编写一个单元测试来模拟cancel
通话的单元测试?
Edit 编辑
Here's a unit test I have already written which tests to make sure that notifyDelegateCreatedObjects:
isn't called if cancel
is called first. 这是我已经编写的单元测试,以确保如果先调用
cancel
则不会调用notifyDelegateCreatedObjects:
- (void)testIfCancelledDoesntNotifyDelegateOfSuccess
{
// given
RTMovieBuilder *builder = [[RTMovieBuilder alloc] init];
builder.delegate = mockProtocol(@protocol(BuilderDelegate));
// when
[builder cancel];
[builder notifyDelegateCreatedObjects:@[]];
// then
[verifyCount(builder.delegate, never()) builder:builder createdObjects:anything()];
}
I'm using OCHamcrest
and OCMockito
. 我正在使用
OCHamcrest
和OCMockito
。 This test passes. 该测试通过。
I would avoid trying to simulate thread timing in unit tests and focus more on figuring out what all the possible end states could be regardless of where the timing falls, and write tests for code under those conditions. 我将避免尝试在单元测试中模拟线程时序,而将精力更多地放在弄清无论时序落在何处,所有可能的最终状态可能是什么,并在这些条件下编写代码测试。 This avoids endless complexity in your tests, as bbum points out as well.
bbum指出,这避免了测试的无限复杂性。
In your case it seems the condition you need to be testing for is if the call to notifyDelegateCreatedObjects
happens after the action is canceled, because the cancel came too late. 在您的情况下,似乎需要测试的条件是,在取消操作之后是否发生对
notifyDelegateCreatedObjects
的调用,因为取消来得太迟了。 So instead just unit test the handling of that scenario downstream in your notifyDelegateCreatedObjects
method, or whatever class is being notified of that aborted event because of the thread timing. 因此,只需在
notifyDelegateCreatedObjects
方法中对该单元的下游处理进行单元测试,或者由于线程计时而正在通知该中止事件的任何类。
I know this is not a specific answer to your question but I think its a better approach to achieve the same unit testing goal. 我知道这不是您问题的具体答案,但我认为这是达到相同单元测试目标的更好方法。
There is no reason to use volatile
if your property is atomic
and you always go through the setter/getter. 如果您的属性是
atomic
属性,并且您总是经过设置器/获取器,则没有理由使用volatile
。
As well, this is a bit of re-inventing the wheel, as noted in the comments. 同样,如评论中所述,这是在重新发明轮子。
In general trying to unit test cancellation with any hope of full coverage is very hard because you can't really effectively test all possible timing interactions. 通常,很难对完全覆盖的希望进行单元测试取消,因为您不能真正有效地测试所有可能的计时交互。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.