Authored by 周蓉君

重构YHAssemblyAssistant,增加其测试样例。Review by 阿瑟。

... ... @@ -31,6 +31,7 @@ extern NSString * const JsonKeyDataTypeErrors;
@property (strong, nonatomic, readonly) YHDevice *device;
@property (strong, nonatomic, readonly) YHStatus *currentStatus;
@property (strong, nonatomic) NSMutableDictionary *immediUploadItemDic; // 用于立即发送策略下(device信息是相同的)
@property (strong, nonatomic) NSFileManager *fileManager; // 属性注入 DI
+ (instancetype)sharedInstance;
... ...
... ... @@ -44,14 +44,16 @@ static dispatch_queue_t persisting_queue() {
@property (strong, nonatomic) NSString *eventFilePath;
@property (strong, nonatomic) NSString *eventFileName;
- (void)writeToFile:(YHAnalyItemData *)itemData;
- (void)postWithJsonData:(NSDictionary *)data;
- (BOOL)writeEventToFile:(YHEvent *)eventData;
- (BOOL)writeErrorToFile:(YHError *)errorData;
- (void)removeLocalFile;
@end
@implementation YHAssemblyAssistant
@synthesize fileManager = _fileManager;
#pragma mark - 初始化
+ (instancetype)sharedInstance
... ... @@ -79,6 +81,14 @@ static dispatch_queue_t persisting_queue() {
#pragma mark - Property
- (NSFileManager *)fileManager
{
if (!_fileManager) {
_fileManager = [NSFileManager defaultManager];
}
return _fileManager;
}
- (NSString *)eventFilePath
{
if (!_eventFilePath || [_eventFilePath isEqualToString:@""]) {
... ... @@ -100,9 +110,8 @@ static dispatch_queue_t persisting_queue() {
- (BOOL)canPersisting
{
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:self.eventFilePath]) {
return [fm createDirectoryAtPath:self.eventFilePath withIntermediateDirectories:YES attributes:nil error:nil];
if (![self.fileManager fileExistsAtPath:self.eventFilePath]) {
return [self.fileManager createDirectoryAtPath:self.eventFilePath withIntermediateDirectories:YES attributes:nil error:nil];
}
return YES;
... ... @@ -115,7 +124,7 @@ static dispatch_queue_t persisting_queue() {
- (NSDictionary *)getUploadData
{
return [[NSDictionary alloc] initWithContentsOfFile:self.eventFileName];
return [NSDictionary dictionaryWithContentsOfFile:self.eventFileName];
}
- (void)prepareImmediUploadDic
... ... @@ -134,96 +143,47 @@ static dispatch_queue_t persisting_queue() {
if ([allStatus count]) {
[allStatus replaceObjectAtIndex:0 withObject:self.currentStatus.jsonDictionary];
[self.immediUploadItemDic setObject:allStatus forKey:JsonKeyDataTypeStatus];
} else {
[self prepareImmediUploadDic];
}
}
#pragma mark - 持久化数据
- (void)saveItemData:(YHAnalyItemData *)itemData
{
if (itemData == nil) {
NSLog(@"Input can't be nil.");
return;
}
if ([self canPersisting]) {
if (itemData.dataType == YHItemDataTypeEvent) {
YHEvent *event = (YHEvent *)itemData;
// 将持久化任务抛入 persistingQueue 队列
dispatch_async(persisting_queue(), ^{
NSMutableDictionary *collectData = [[NSMutableDictionary alloc]initWithContentsOfFile:self.eventFileName];
//无本地持久化数据
if (!collectData) {
collectData = [[NSMutableDictionary alloc]init];
//直接添加 immediUploadDataDic 内容(里面包含 device 信息)
[collectData addEntriesFromDictionary:self.immediUploadItemDic];
//组合event信息
if (event.jsonDictionary) {
NSMutableArray *events = [[NSMutableArray alloc]initWithObjects:event.jsonDictionary,nil];
self.allEventsCount = [events count];
[collectData setObject:events forKey:JsonKeyDataTypeEvents];
}
} else { // 持久化文件不为空
// 检查当前session与本地文件中最近存储的session是否相同
NSMutableArray *status = [collectData objectForKey:JsonKeyDataTypeStatus];
if ([status count]) {
NSMutableDictionary *tempStatus = [status lastObject];
//如果不同则插入新的状态
if (![[tempStatus objectForKey:JsonKeyStatusSID] isEqualToString:self.currentStatus.sid]) {
[status addObject:self.currentStatus.jsonDictionary];
// 更新items中status
[collectData setObject:status forKey:JsonKeyDataTypeStatus];
}
}
if ([collectData.allKeys containsObject:JsonKeyDataTypeEvents]) {
NSMutableArray *oldEvents = [collectData objectForKey:JsonKeyDataTypeEvents];
if (!oldEvents) {
oldEvents = [[NSMutableArray alloc]init];
}
[oldEvents addObject:event.jsonDictionary];
self.allEventsCount = [oldEvents count];
[collectData setObject:oldEvents forKey:JsonKeyDataTypeEvents];
} else {
NSMutableArray *newEvents = [[NSMutableArray alloc]initWithObjects:event.jsonDictionary, nil];
self.allEventsCount = [newEvents count];
[collectData setObject:newEvents forKey:JsonKeyDataTypeEvents];
}
}
BOOL success = [collectData writeToFile:self.eventFileName atomically:YES];
if (success) {
// 当event数量大于阀值或者文件超过指定大小,上传本地数据
unsigned long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:self.eventFileName error:NULL] fileSize];
if (self.allEventsCount > kMaxLocalEventsCount || fileSize > kMaxLocalRecoderFileSize) {
// 加互斥,上传时不进行写操作
@synchronized(self){
[self uploadDiskData];
}
}
if ([self writeEventToFile:event]) {
[self autoUploadData];
}
});
} else if(itemData.dataType == YHItemDataTypeError){ //error
YHError *errorData = (YHError *)itemData;
YHError *errorData = (YHError *)itemData;
NSDictionary *st = [errorData.param objectForKey:JsonKeyErrorST];
if ([st.allKeys containsObject:CrashReporterKeyCallstack]) { //判断是否为crash 类型 error
// crash 类型error的本地化任务须在主线程中完成
[self writeToFile:errorData];
[self writeErrorToFile:errorData];
} else {
// 将持久化任务抛入 persistingQueue 队列
dispatch_async(persisting_queue(), ^{
[self writeToFile:errorData];
[self writeErrorToFile:errorData];
});
}
} else {
// 不处理非 Event 和 Error 类型的数据
NSLog(@"Don't save other data except event and error.");
}
} else {
// 不能存储
... ... @@ -233,29 +193,47 @@ static dispatch_queue_t persisting_queue() {
- (void)uploadImmedilyWithEvent:(YHAnalyItemData *)itemData
{
if (itemData == nil) {
NSLog(@"Input can't be nil.");
return;
}
if (itemData.dataType == YHItemDataTypeEvent) {
YHEvent *event = (YHEvent *)itemData;
if (event) {
[self.immediUploadItemDic setObject:[[NSMutableArray alloc]initWithObjects:event.jsonDictionary, nil] forKey:JsonKeyDataTypeEvents];
[self postWithJsonData:self.immediUploadItemDic];
[[YHNetworkAssistant sharedInstance] postWhitAPI:kYASApiMethod parameters:self.immediUploadItemDic from:self success:^(id responseObject) {
} failure:^(NSError *error) {
NSLog(@"error = %@", error.localizedDescription);
}];
}
} else if(itemData.dataType == YHItemDataTypeError) {
YHError *error = (YHError *)itemData;
if (error) {
[self.immediUploadItemDic setObject:[[NSMutableArray alloc]initWithObjects:error.jsonDictionary, nil] forKey:JsonKeyDataTypeErrors];
[self postWithJsonData:self.immediUploadItemDic];
[[YHNetworkAssistant sharedInstance] postWhitAPI:kYASApiMethod parameters:self.immediUploadItemDic from:self success:^(id responseObject) {
} failure:^(NSError *error) {
NSLog(@"error = %@", error.localizedDescription);
}];
}
} else {
// 不处理非 Event 和 Error 类型的数据
NSLog(@"Don't upload other data except event and error.");
}
}
- (void)uploadDiskData
{
NSDictionary *uploadData = [[YHAssemblyAssistant sharedInstance] getUploadData];
NSDictionary *uploadData = [self getUploadData];
if (uploadData) {
[[YHNetworkAssistant sharedInstance] postWhitAPI:kYASApiMethod parameters:uploadData from:self success:^(id responseObject) {
// NSLog(@"responseObject = %@", responseObject);
[self removeLocalFile];
} failure:^(NSError *error) {
NSLog(@"error = %@", error.localizedDescription);
... ... @@ -265,6 +243,11 @@ static dispatch_queue_t persisting_queue() {
- (BOOL)updateLocation:(CLLocation *)location
{
if (location == nil) {
NSLog(@"Input can't be nil.");
return NO;
}
NSNumber *loNumber = [NSNumber numberWithDouble:location.coordinate.longitude];
NSNumber *laNumber = [NSNumber numberWithDouble:location.coordinate.latitude];
NSString *lo = [loNumber stringValue];
... ... @@ -284,10 +267,74 @@ static dispatch_queue_t persisting_queue() {
#pragma mark - private method
- (void)writeToFile:(YHAnalyItemData *)itemData
// 将一个事件写入到文件
- (BOOL)writeEventToFile:(YHEvent *)event
{
if (event == nil) {
NSLog(@"Input can't be nil.");
return NO;
}
NSMutableDictionary *collectData = [[NSMutableDictionary alloc] initWithContentsOfFile:self.eventFileName];
//无本地持久化数据
if (!collectData) {
collectData = [[NSMutableDictionary alloc]init];
//直接添加 immediUploadDataDic 内容(里面包含 device 信息)
[collectData addEntriesFromDictionary:self.immediUploadItemDic];
//组合event信息
if (event.jsonDictionary) {
NSMutableArray *events = [[NSMutableArray alloc]initWithObjects:event.jsonDictionary,nil];
self.allEventsCount = [events count];
[collectData setObject:events forKey:JsonKeyDataTypeEvents];
}
} else { // 持久化文件不为空
// 检查当前session与本地文件中最近存储的session是否相同
NSMutableArray *status = [collectData objectForKey:JsonKeyDataTypeStatus];
if ([status count]) {
NSMutableDictionary *tempStatus = [status lastObject];
//如果不同则插入新的状态
if (![[tempStatus objectForKey:JsonKeyStatusSID] isEqualToString:self.currentStatus.sid]) {
[status addObject:self.currentStatus.jsonDictionary];
// 更新items中status
[collectData setObject:status forKey:JsonKeyDataTypeStatus];
}
}
if ([collectData.allKeys containsObject:JsonKeyDataTypeEvents]) {
NSMutableArray *oldEvents = [collectData objectForKey:JsonKeyDataTypeEvents];
if (!oldEvents) {
oldEvents = [[NSMutableArray alloc]init];
}
[oldEvents addObject:event.jsonDictionary];
self.allEventsCount = [oldEvents count];
[collectData setObject:oldEvents forKey:JsonKeyDataTypeEvents];
} else {
NSMutableArray *newEvents = [[NSMutableArray alloc]initWithObjects:event.jsonDictionary, nil];
self.allEventsCount = [newEvents count];
[collectData setObject:newEvents forKey:JsonKeyDataTypeEvents];
}
}
return [collectData writeToFile:self.eventFileName atomically:YES];
}
// 将一个错误写入到文件
- (BOOL)writeErrorToFile:(YHError *)errorData;
{
YHError *errorData = (YHError *)itemData;
NSMutableDictionary *collectData = [[NSMutableDictionary alloc]initWithContentsOfFile:self.eventFileName];
if (errorData == nil) {
NSLog(@"Input can't be nil.");
return NO;
}
NSMutableDictionary *collectData = [[NSMutableDictionary alloc] initWithContentsOfFile:self.eventFileName];
if (!collectData) {
collectData = [[NSMutableDictionary alloc]init];
//直接添加 immediUploadDataDic 内容(里面包含 device 信息)
... ... @@ -313,7 +360,6 @@ static dispatch_queue_t persisting_queue() {
// 更新items中status
[collectData setObject:status forKey:JsonKeyDataTypeStatus];
}
}
// 更新error信息
... ... @@ -330,26 +376,29 @@ static dispatch_queue_t persisting_queue() {
}
}
[collectData writeToFile:self.eventFileName atomically:YES];
return [collectData writeToFile:self.eventFileName atomically:YES];
}
- (void)postWithJsonData:(NSDictionary *)data
// 当触发预设的条件时,自动上传数据
- (void)autoUploadData
{
if (data) {
// 当event数量大于阀值或者文件超过指定大小,上传本地数据
unsigned long long fileSize = [[self.fileManager attributesOfItemAtPath:self.eventFileName error:NULL] fileSize];
if (self.allEventsCount > kMaxLocalEventsCount || fileSize > kMaxLocalRecoderFileSize) {
[[YHNetworkAssistant sharedInstance] postWhitAPI:kYASApiMethod parameters:data from:self success:^(id responseObject) {
// NSLog(@"responseObject = %@", responseObject);
} failure:^(NSError *error) {
NSLog(@"error = %@", error.localizedDescription);
}];
// 加互斥,上传时不进行写操作
@synchronized(self){
[self uploadDiskData];
}
}
}
// 删除本地持久化文件
- (void)removeLocalFile
{
if ([[NSFileManager defaultManager] fileExistsAtPath:self.eventFileName]) {
[[NSFileManager defaultManager] removeItemAtPath:self.eventFileName error:nil];
if ([self.fileManager fileExistsAtPath:self.eventFileName]) {
[self.fileManager removeItemAtPath:self.eventFileName error:nil];
}
self.allEventsCount = 0;
}
... ...
... ... @@ -8,10 +8,21 @@
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "OCMock.h"
#import "YHAssemblyAssistant.h"
@interface YHAssemblyAssistant (XCTestCase)
@property (strong, nonatomic) NSString *eventFilePath;
@property (strong, nonatomic) NSString *eventFileName;
@end
@interface YHAssemblyAssistantLogicTests : XCTestCase {
YHAssemblyAssistant *assembly;
id mock;
}
@end
... ... @@ -21,24 +32,206 @@
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
NSLog(@"%@ setUp", self.name);
assembly = [[YHAssemblyAssistant alloc] init];
XCTAssertNotNil(assembly, @"Can't create a instance of YHAssemblyAssistant.");
assembly.eventFilePath = @"/AppData/Library/Caches/YHLogSystem";
assembly.eventFileName = @"/AppData/Library/Caches/YHLogSystem/LogSystem.plist";
mock = OCMClassMock([NSFileManager class]);
OCMStub([mock defaultManager]).andReturn(mock);
assembly.fileManager = mock;
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
assembly = nil;
mock = nil;
[super tearDown];
NSLog(@"%@ tearDown", self.name);
}
- (void)testCanPersistingSuccess {
NSLog(@"%@ start", self.name);
OCMExpect([mock fileExistsAtPath:[OCMArg any]]).andReturn(NO);
OCMExpect([mock createDirectoryAtPath:[OCMArg any] withIntermediateDirectories:YES attributes:nil error:nil]).andReturn(YES);
BOOL isCanPersisting = [assembly canPersisting];
OCMVerifyAll(mock);
XCTAssert(isCanPersisting, @"Data should be persisting when create directory success.");
NSLog(@"%@ end", self.name);
}
- (void)testCanPersistingFailed {
NSLog(@"%@ start", self.name);
OCMExpect([mock fileExistsAtPath:[OCMArg any]]).andReturn(NO);
OCMExpect([mock createDirectoryAtPath:[OCMArg any] withIntermediateDirectories:YES attributes:nil error:nil]).andReturn(NO);
BOOL isCanPersisting = [assembly canPersisting];
OCMVerifyAll(mock);
XCTAssertFalse(isCanPersisting, @"Data should not be persisting when create directory failed.");
NSLog(@"%@ end", self.name);
}
- (void)testCanPersistingExist {
NSLog(@"%@ start", self.name);
OCMExpect([mock fileExistsAtPath:[OCMArg any]]).andReturn(YES);
[[mock reject] createDirectoryAtPath:[OCMArg any] withIntermediateDirectories:YES attributes:nil error:nil];
BOOL isCanPersisting = [assembly canPersisting];
OCMVerifyAll(mock);
XCTAssert(isCanPersisting, @"Data should be persisting when directory exist.");
NSLog(@"%@ end", self.name);
}
- (void)testGetAllEventCountInitValueEqualZero {
NSLog(@"%@ start", self.name);
XCTAssertEqual([assembly getAllEventCount], 0, @"allEventCount should be zero initially.");
NSLog(@"%@ end", self.name);
}
- (void)testGetAllEventCountIncrease {
NSLog(@"%@ start", self.name);
NSLog(@"%@ end", self.name);
}
- (void)testExample {
// This is an example of a functional test case.
XCTAssert(YES, @"Pass");
- (void)testGetUploadDataSuccess {
NSLog(@"%@ start", self.name);
NSDictionary *uploadDataDic = @{@"key" : @"value"};
id dicMock = OCMClassMock([NSDictionary class]);
OCMExpect([dicMock dictionaryWithContentsOfFile:[OCMArg any]]).andReturn(uploadDataDic);
NSDictionary *returnDic = [assembly getUploadData];
XCTAssertEqualObjects(returnDic, uploadDataDic, @"event file doesn't exist or data doesn't store by Dictionary format.");
OCMVerifyAll(dicMock);
NSLog(@"%@ end", self.name);
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
- (void)testGetUploadDataFailed {
NSLog(@"%@ start", self.name);
id dicMock = OCMClassMock([NSDictionary class]);
OCMExpect([dicMock dictionaryWithContentsOfFile:[OCMArg any]]).andReturn(nil);
NSDictionary *returnDic = [assembly getUploadData];
XCTAssertNil(returnDic, @"it should return nil when event file doesn't exist or data doesn't store by Dictionary format.");
OCMVerifyAll(dicMock);
NSLog(@"%@ end", self.name);
}
- (void)testPrepareImmediUploadDic {
NSLog(@"%@ start", self.name);
[assembly prepareImmediUploadDic];
NSDictionary *deviceJsonDic = [assembly.immediUploadItemDic objectForKey:JsonKeyDataTypeDevice];
NSMutableArray *statusJsonArray = [assembly.immediUploadItemDic objectForKey:JsonKeyDataTypeStatus];
NSDictionary *currentJsonDic = [assembly.currentStatus jsonDictionary];
XCTAssertNotNil(deviceJsonDic, @"Should contain device JsonDic after prepare immediUploadDic.");
XCTAssertNotNil(statusJsonArray, @"Should contain statusJsonDic Array after prepare immediUploadDic.");
XCTAssertEqualObjects([statusJsonArray objectAtIndex:0], currentJsonDic);
NSLog(@"%@ end", self.name);
}
- (void)testUpdateImmediUploadDic {
NSLog(@"%@ start", self.name);
[assembly updateImmediUploadDic];
NSMutableArray *statusJsonArray = [assembly.immediUploadItemDic objectForKey:JsonKeyDataTypeStatus];
NSDictionary *currentJsonDic = [assembly.currentStatus jsonDictionary];
XCTAssertNotNil(statusJsonArray, @"Should contain statusJsonDic Array after prepare immediUploadDic.");
XCTAssertEqualObjects([statusJsonArray objectAtIndex:0], currentJsonDic);
NSLog(@"%@ end", self.name);
}
- (void)testSaveItemDataWithInputNil {
NSLog(@"%@ start", self.name);
id partialMock = OCMPartialMock(assembly);
[[partialMock reject] canPersisting];
[assembly saveItemData:nil];
OCMVerifyAll(partialMock);
NSLog(@"%@ end", self.name);
}
- (void)testUploadImmedilyWithEvent {
NSLog(@"%@ start", self.name);
NSLog(@"%@ end", self.name);
}
- (void)testUploadImmedilyWithError {
NSLog(@"%@ start", self.name);
NSLog(@"%@ end", self.name);
}
- (void)testUploadDiskDataSuccess {
NSLog(@"%@ start", self.name);
NSLog(@"%@ end", self.name);
}
- (void)testUploadDiskDataFailed {
NSLog(@"%@ start", self.name);
NSLog(@"%@ end", self.name);
}
- (void)testUpdateLocationSuccess {
NSLog(@"%@ start", self.name);
CLLocation *location = [[CLLocation alloc] initWithLatitude:20.0f longitude:120.0f];
NSString *lo = [[NSNumber numberWithDouble:location.coordinate.longitude] stringValue];
NSString *la = [[NSNumber numberWithDouble:location.coordinate.latitude] stringValue];
BOOL bReturn = [assembly updateLocation:location];
XCTAssert(bReturn);
XCTAssert([assembly.currentStatus.lo isEqualToString:lo]);
XCTAssert([assembly.currentStatus.la isEqualToString:la]);
NSLog(@"%@ end", self.name);
}
- (void)testUpdateLocationFailed {
NSLog(@"%@ start", self.name);
CLLocation *location = [[CLLocation alloc] initWithLatitude:20.0f longitude:120.0f];
BOOL bReturn1 = [assembly updateLocation:location];
BOOL bReturn2 = [assembly updateLocation:location];
XCTAssert(bReturn1);
XCTAssertFalse(bReturn2);
NSLog(@"%@ end", self.name);
}
@end
... ...