一、单元测试的定义
  在计算机编程中,单元测试(英语:UnitTesting)又称为模块测试,是针对程序模块(软件设计的小单位)来进行正确性检验的测试工作。程序单元是应用的小可测试部件。
  在过程化编程中,一个单元是单个程序、函数、过程等;对于面向对象编程,小单元是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
  根据不同场景,单元的定义也不一样,通常我们将C语言的单个函数或者面向对象语言的单个类视作测试的单元。在使用单元测试的过程中,我们要知道这一点:
  单元测试并不是为了证明代码的正确性,它只是一种用来帮助我们发现错误的手段
  单元测试不是药,它确实能帮助我们找到大部分代码逻辑上的bug,同时,为了提高测试覆盖率,这能逼迫我们对代码不断进行重构,提高代码质量等。
  二、iOS单元测试
  xcode本身的测试框架集成:在Xcode4.x中集成了测试框架OCUnit,UITests是iOS9推出的新特性。目前我们在创建项目的时候会默认选中有关测试的这两项:IncludeUnitTests、IncludeUITests。在创建项目之后,会自动生成一个appName+Tests的文件夹目录,下面存放着单元测试的文件。
  根据测试的目的大致可以将单元测试分为这三类:
  a.性能测试:测试代码执行花费的时间
  b.逻辑测试:测试代码执行结果是否符合预期
  c.异步测试:测试多线程操作代码
  UnitTest文件里面方法介绍:
  1.(void)setUp{//每一个测试用例开始前调用,用来初始化相关数据
  [supersetUp];
  //Putsetupcodehere.
  Thismethodiscalledbeforethe
  invocationofeachtestmethodintheclass.
  }
  2.(void)tearDown{//测试用例完成后调用,可以用来释放变量等结尾操作
  //Putteardowncodehere.This
  methodiscalledaftertheinvocation
  ofeachtestmethodintheclass.
  [supertearDown];
  }
  3.(void)testExample{//用来执行我们需要的测试操作,正常情况下,我们不使用这个方法,而是创建名为test+测试目的的方法来完成我们需要的操作(注意:此时自定义的方法需要以test开头方能进行测试,否则左边是不显示菱形的)
  //Thisisanexampleofa
  functionaltestcase.
  //UseXCTAssertandrelated
  functionstoverifyyourtests
  producethecorrectresults.
  }
  4.(void)testPerformanceExample{//会将方法中的block代码耗费时长打印出来--默认执行了10次,打印出了平均耗时,和各次的耗时,大误差不超过10%。其中运行之后block这行右侧显示的是平均耗时。
  [selfmeasureBlock:^{
  //Putthecodeyouwant
  tomeasurethetimeofhere.
  }];
  }
  在每个测试用例方法的左侧有个菱形的标记,点击这个标记可以单独的运行这个测试方法。如果测试通过没有发生任何断言错误,那么这个菱形会变成绿色勾选状态。使用快捷键command+U直接依次调用所有的单元测试。
  另外,可以在左侧的文件栏中选中单元测试栏目,然后直观的看到所有测试的结果。同样的点击右侧菱形位置的按钮可以运行单个测试方法或者文件:

  为了保证单元测试的正确性,我们应当保证测试用例中只存在一个类或者只发生一个类变量的属性修改。下面是我们测试中常用的宏定义:(XCTest带有许多内建的断言)
  XCTAssertNotNil(a1,format…)当a1不为nil时成立
  XCTAssert(expression,format...)当expression结果为YES成立
  XCTAssertTrue(expression,format...)当expression结果为YES成立;
  XCTAssertEqualObjects(a1,a2,format...)判断相等,当[a1isEqualTo:a2]返回YES的时候成立
  XCTAssertEqual(a1,a2,format...)当a1==a2返回YES时成立
  XCTAssertNotEqual(a1,a2,format...)当a1!=a2返回YES时成立
  &&
  XCTFail(format…)生成一个失败的测试;
  XCTAssertNil(a1,format...)为空判断,a1为空时通过,反之不通过;
  XCTAssertNotNil(a1,format…)不为空判断,a1不为空时通过,反之不通过;
  XCTAssert(expression,format...)当expression求值为TRUE时通过;
  XCTAssertTrue(expression,format...)当expression求值为TRUE时通过;
  XCTAssertFalse(expression,format...)当expression求值为False时通过;
  XCTAssertEqualObjects(a1,a2,format...)判断相等,[a1isEqual:a2]值为TRUE时通过,其中一个不为空时,不通过;
  XCTAssertNotEqualObjects(a1,a2,format...)判断不等,[a1isEqual:a2]值为False时通过;
  XCTAssertEqual(a1,a2,format...)判断相等(当a1和a2是C语言标量、结构体或联合体时使用,判断的是变量的地址,如果地址相同则返回TRUE,否则返回NO);
  XCTAssertNotEqual(a1,a2,format...)判断不等(当a1和a2是C语言标量、结构体或联合体时使用);
  XCTAssertEqualWithAccuracy(a1,a2,accuracy,format...)判断相等,(double或float类型)提供一个误差范围,当在误差范围(+/-accuracy)以内相等时通过测试;
  XCTAssertNotEqualWithAccuracy(a1,a2,accuracy,format...)判断不等,(double或float类型)提供一个误差范围,当在误差范围以内不等时通过测试;
  XCTAssertThrows(expression,format...)异常测试,当expression发生异常时通过;反之不通过;(很变态)XCTAssertThrowsSpecific(expression,specificException,format...)异常测试,当expression发生specificException异常时通过;反之发生其他异常或不发生异常均不通过;
  XCTAssertThrowsSpecificNamed(expression,specificException,exception_name,format...)异常测试,当expression发生具体异常、具体异常名称的异常时通过测试,反之不通过;
  XCTAssertNoThrow(expression,format…)异常测试,当expression没有发生异常时通过测试;
  XCTAssertNoThrowSpecific(expression,specificException,format...)异常测试,当expression没有发生具体异常、具体异常名称的异常时通过测试,反之不通过;
  XCTAssertNoThrowSpecificNamed(expression,specificException,exception_name,format...)异常测试,当expression没有发生具体异常、具体异常名称的异常时通过测试,反之不通过
  三、测试
  1、逻辑测试:逻辑测试的目的是为了检测在代码执行前后发生的变化是否符合预期
  e.g:
  @interfaceTestModel1:NSObject
  @property(nonatomic,copy)
  NSStringname;
  @property(nonatomic,strong)
  NSNumberage;
  @property(nonatomic,assign)
  NSUIntegerflags;
  (instancetype)modelWithName:
  (NSString)nameage:(NSNumber)
  ageflags:(NSUInteger)flags;
  (instancetype)initWithDictionary:
  (NSDictionary*)dict;
  (NSDictionary*)modelToDictionary;
  @end
  @implementationTestModel1
  (instancetype)modelWithName:
  (NSString)nameage:(NSNumber
  )ageflags:(NSUInteger)flags
  {
  TestModel1*model=[[selfalloc]init];
  model.name=name;
  model.age=age;
  model.flags=flags;
  returnmodel;
  }
  (instancetype)initWithDictionary:
  (NSDictionary*)dict
  {
  self.name=dict[@"name"];
  self.age=dict[@"age"];
  self.flags=[dict[@"flags"]
  integerValue];
  returnself;
  }
  (NSDictionary*)modelToDictionary
  {
  return@{@"name":self.name,@"age":
  self.age,@"flags":[NSNumber
  numberWithInteger:self.flags]};
  }
  @end
  然后在测试文件里面:
  (void)testModelConvert
  {
  NSStringjson=@"{"name":
  "SindriLin","age"
  :22,"flags":987654321}";
  NSMutableDictionarydict=
  [[NSJSONSerializationJSONObjec
  tWithData:[jsondataUsingEncoding:
  NSUTF8StringEncoding]options:
  kNilOptionserror:nil]mutableCopy];
  TestModel1model=[[TestModel1
  alloc]initWithDictionary:dict];
  XCTAssertNotNil(model);
  XCTAssertTrue([model.name
  isEqualToString:@"SindriLin"]);
  XCTAssertTrue([model.ageisEqual:
  @(22)]);
  XCTAssertEqual(model.flags,987654321);
  XCTAssertTrue([modelisKindOfClass:
  [TestModel1class]]);
  model=[TestModel1modelWithName:
  @"Tessie"age:dict[@"age"]flags:
  562525];
  XCTAssertNotNil(model);
  XCTAssertTrue([model.nameisEqual
  ToString:@"Tessie"]);
  XCTAssertTrue([model.ageisEqual:dict[@"age"]]);
  XCTAssertEqual(model.flags,562525);
  NSDictionarymodelJSON=[model
  modelToDictionary];
  XCTAssertTrue([modelJSONisEqual:
  dict]==NO);
  dict[@"name"]=@"Tessie";
  dict[@"flags"]=@(562525);
  XCTAssertTrue([modelJSONisEqual:
  dict]);
  }
  2、性能测试:
  在平常的工作中,我们还可以通过:instrument(xcode->product->profile)工具很好的查找到项目中的代码耗时点,(后面介绍)。先介绍单元测试的性能测试:
  测试文件:
  (void)testPerformanceExample{//会将方法中的block代码耗费时长打印出来--默认执行了10次,打印出了平均耗时,和各次的耗时,大误差不超过10%。
  //Thisisanexampleofaperformance
  testcase.[selfmeasureBlock:^{
  //Putthecodeyouwanttomeasure
  thetimeofhere.
  [TestModel1randomModels];
  //for(inti=0;i<100;++i){
  //NSLog(@"wgj:%d",i);
  //}
  }];
  }
  自定义的model文件中添加:
  (NSArray<TestModel1*>)randomModels
  {
  NSMutableArraymodels=@[].mutableCopy;
  NSArray*names=@[
  @"xiaoli01",@"xiaoli02",@"xiaoli03",
  @"xiaoli04",@"xiaoli05"
  ];
  NSArray*ages=@[
  @15, @20, @25, @30, @35
  ];
  NSArray*flags=@[
  @123,@456,@789,@012,@234
  ];
  for(NSUIntegeridx=0;idx<100;idx++){
  TestModel1*model=[selfmodelWithName:
  names[arc4random()%names.count]age:
  ages[arc4random()%ages.count]flags:
  [flags[arc4random()%flags.count]
  unsignedIntegerValue]];
  [modelsaddObject:model];
  [NSThreadsleepForTimeInterval:0.01];
  }
  returnmodels;
  }

  在平常的test方法中,也会打印测试方法的执行时间,例如下面,但是没有上面这种在性能测试方法中测的准确。


  打印台会打印各测试方法的耗时,直接使用单元测试来获取某段代码的执行时间要比使用instrument快的多(instrument定位更精确)。通过性能测试直观的获取执行时间后,我们可以根据需要来决定是否将这些代码放到子线程中执行来优化代码(很多时候,数据转换会占用大量的CPU计算资源)