单元测试是敏捷开发实践的组成部分之一,其目的是提高软件开发效率和维护代码的健康。它的目标是证明软件可以正常运行,而不是发现bug(发现bug的目的与开发成本正相关,虽然发现bug是保证软件质量的一种手段,但显然与降低软件质量的目的无关软件开发成本。运行在相反的方向)。单元测试是软件质量的保证。比如重构之后,我们要保证软件产品的正常运行。iOS非常幸运。苹果的开发工具Xcode可以在创建项目的时候带上XCTest,包括单元测试和UI测试。这次我们将从两个方面来谈谈单元测试。1.开发如果在创建项目时自带,可以在项目文件的TARGETS中看到一个对应的项目名,以Tests结尾。如果没有,您也可以自己创建。单击下面的加号并输入测试。可以看到对应的bundle,这里我们选择UnitTestingBundle。创建后会多出一个文件夹,里面新建的工程名称与新建工程名称相同,可以在里面创建每个文件的单元测试。右键单击文件夹上的NewFile并选择UnitTestCaseClass以创建单元测试文件。这就是创建过程的全部内容。接下来简单介绍一下单元测试的一些用途。单元测试首先是测试方法的可行性,所以需要断言,看是否正确。XCTest提供了许多可用的断言。下面是一些常见的断言:XCTAssertNotNil(expression,...)XCTAssertNil(expression,...)XCTAssertTrue(expression,...)XCTAssertFalse(expression,...)XCTAssertEqualObjects(expression1,expression2,...)XCTAssertEqual(expression1,expression2,...)XCTAssertGreaterThan(expression1,expression2,...)等等可以通过XCTestAssertions.h找到。这里的断言很容易理解,可以通过方法名来解释。比如第一个XCTAssertNotNil表示表达式或对象一定不能为空才能通过,否则测试失败。接下来我们来看一个简单的例子:这里有一个简单的实现加法的类和方法。#importNS_ASSUME_NONNULL_BEGIN@interfaceCalcMethod:NSObject+(NSInteger)plus:(NSInteger)aandB:(NSInteger)b;@endNS_ASSUME#import"CalcMethod.h"@implementationCalcMethod+(NSInteger)plus:(NSInteger)aandB:(NSInteger)b{returna+b;}@end要实现它的单元测试,首先新建一个单元测试文件,只有.m,没有头文件。#import@interfaceCalcMethodTests:XCTestCase@end@implementationCalcMethodTests-(void)setUp{//在此处放置设置代码。Thismethoddiscalledbeforetheinvocationofeachtestmethodintheclass.}-(void)tearDown{//Puttearddowncodehere.Thismethoddiscalledaftertheinvocationofeachtestmethodintheclass).}-(voleid)/Thisisanexampleofafunctionaltestcase.//UseXCTAssertandrelatedfunctionstooverifyyourtestsproducethecorrectresults.}-(void)testPerformanceExample{//Thisisanexampleofaperformancetestcase.[selfmeasureBlock:^{//Putthecodeyouwanttomeasurethetimeofhere.}];可以看到系统在执行Up之前提供了几个方法。会调用它来初始化一些参数;tearDown是在每个方法执行完之后实现的一些销毁方法;testExample是具体测试的方法,也可以自己定义,但是必须以test开头;testPerformanceExample用于测试性能。放在measureBlock中会运行10次来测量每次使用的时间,一般是使用或者大量计算产生的耗时。这只是一个简单的加法运算,所以不需要。添加一个测试方法:-(void)testCalcMethod{XCTAssertEqual([CalcMethodplus:1andB:2],3,@"1+2=3");}然后把Scheme切换到对应的Test,如果没有,自己管理在SchemeAdd,然后点击方法前面的菱形块对方法进行测试,或者点击command+u对所有测试文件和方法进行单元测试。切到左边的测试栏,这样可以更方便的点击测试,测试通过会显示一个勾。二、常见问题处理1、第一次执行单元测试时,发现编译失败,无法报类型。原因是单元测试对引用的要求比较严格。以前可能在编译中直接通过,但是单元测试不行。解决方法也很简单,将所有相关文件导入相应的头文件即可。这只是针对上面报的错误,可能还有更多的错误,需要自己配对。如果App中有自己开发的Framework工程,想对Framework进行单元测试,下一步就是解决Framework2中的一些单元测试问题,第三方库或者pod的头文件不能成立。这是因为如果你的framework是通过pod导入的,那么你就不需要自己处理pod的头文件管理,pod会被处理集成到App中。但是单元测试不起作用,因此您需要将标头添加到您自己的框架和单元测试包中。切换到项目文件的BuildSettingsàHeaderSearchPaths,添加自己的Pod对应的头文件路径,包括单元测试的bundle和单元测试对应的Framework项目。3.ReportIncludeofnon-modularheaderinsideframeworkmodule这个还是在unittestbundle的BuildSettingsàAllowNon-modularIncludesInFrameworkModules里,把这个设置改成YES就行了。4、使用pod集成后,App调试报找不到XCTest。这是因为pod包含的文件太粗糙了。使用**替换所有子目录,导致单元测试的.m一起包含在pod文件中。方案一:精确Pod需要的文件路径方案二:规范单元测试文件名,在pod配置中排除5.如果要测试一些网络请求或者异步操作怎么办?如果你直接在测试方法method中写一些异步操作,在回调中做断言,你会发现不管对不对都会直接pass。所以Apple也提供了一个用于单元测试的异步块。/*!*@method-waitForExpectationsWithTimeout:handler:**@paramtimeout*必须满足所有期望的时间量。**@paramhandler*如果提供,则将在超时或fulfillmentoffall*期望时调用处理程序。超时始终被视为最失败。**@discussion*-waitForExpectationsWithTimeout:handler:createExpectofsynchronizationFortimeoutWithoutlyonlyonea*:canbeactiveatanygiventime,but*multiplediscretesequencesof{expectations->wait}canbechainedtogether.**-waitForExpectationsWithTimeout:handler:runstherunloopwhilehandlingeventsuntilallexpectations*arefulfilledorthetimeoutisreached.Clientsshouldnotmanipulatetherun*loopwhileusingthisAPI.*/-(void)waitForExpectationsWithTimeout:(NSTimeInterval)timeouthandler:(nullableXCWaitCompletionHandler)handler;/*!*@method-expectationForNotification:object:handler:**@discussion*AconveniencemethodforasynchronousteststhatobserveNSNotificationsfromthedefault*NSNotifica应用tionCenter.**@paramnotificationName*Thenotificationtoregisterfor.**@paramobjectToObserve*Theobjecttoobserve.**@paramhandler*Optionalhandler,/seeXCNotificationExpectationHandler.Ifnotprovided,theexpectation*willbefulfilledbythefirstnotificationmatchingthespecifiednamefromthe*observedobject.**@return*Createsandreturnsanexpectationassociatedwiththetestcase.*/-(XCTexpectation)NotificationExpectation*:NotificationExpectation*:(NSNotificationName)notificationName方法我提出两个常用的方法,可以阻塞线程,收到通知或者超时后继续。更多方法参见XCTestCase+AsynchronousTesting.h。下面是使用方法:(void)testAsync{dispatch_async(dispatch_get_global_queue(0,0),^{//DoSomethingsasync//XCAssert[[NSNotificationCenterdefaultCenter]postNotificationName:@"UnitTestsNotify"object:nil];});do{[selfexpectationForNotification:@"UnitTestsNotify"object:nilhandler:nil];[selfwaitForExpectationsWithTimeout:30handler:nil];}while(0);}也可以使用宏定义来简化这个方法,更直观方便多个电话。#defineWAITdo{\[selfexpectationsForNotification:@"UnitTestsNotify"object:nilhandler:nil];\[selfwaitForExpectationsWithTimeout:30handler:nil];\}while(0);#defineNOTIFY\[[NSNotificationCenterdefaultCenter]postNotificationName:@"UnitTestsNotify"object:nil];-(void)testAsync{dispatch_async(dispatch_get_global_queue(0,0),^{//DoSomethingsasync//XCAssertNOTIFY});WAIT}-AI)”]点此阅读作者更多好文