现在单元测试暂时是基于XCTestCase这个父类来进行测试,使用到了第三方的开源的框架(OCHamcrest、OCMockito).
现在创建一个测试的工程:工程的主要目的实现递增或者递减的情况。
基本的storyboard的界面如下:look the pricture (simple view as below)
相应的ViewController中的代码如下:
(ViewController.h 文件)
#import <UIKit/UIKit.h>
//#import "Counter.h"
//#define HC_SHORTHAND
//#import <OCHamcrest/OCHamcrest.h>
#import "Counter.h"
@interface ViewController : UIViewController
@property (nonatomic, strong ) Counter* counter;
@property (strong , nonatomic) IBOutlet UILabel *counterLabel;
@property (strong , nonatomic) IBOutlet UIButton *plusButton;
@property (strong , nonatomic) IBOutlet UIButton *minusButton;
- (IBAction)incrementCount:(id)sender;
- (IBAction)minusCount:(id)sender;
- (id)initWithCounter:(Counter*) counter;
@end
(ViewController.m 文件)
#import "ViewController.h"
@interface ViewController ()
@end
#define CounterModelChanged @"changed"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (IBAction)incrementCount:(id)sender
{
float number = [self.counterLabel.text floatValue];
number++;
self.counterLabel.text = [NSString stringWithFormat:@"%f",number];
}
- (IBAction)minusCount:(id)sender
{
float number = [self.counterLabel.text floatValue];
number--;
self.counterLabel.text = [NSString stringWithFormat:@"%f",number];
}
- (id)initWithCounter:(Counter*) counter
{
self = [super init];
if (self) {
_counter = counter;
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(modelChanged:) name:CounterModelChanged object:_counter];
//这个通知信心是怎么设置的?CounterModelChanged 应该是一个字符串才对呀!
}
return self;
}
- (void)modelChanged:(NSNotification * )notification
{
[_counterLabel setText:[NSString stringWithFormat:@"%ld",(long)[_counter count]]];
}
@end
上面实现了相应的功能。
会发现少了相应的计数的实体类 ———— Counter ,下面是创建Counter实体类的方法。
(Counter.h 文件)
#import <Foundation/Foundation.h>
#define HC_SHORTHAND
//#import <OCHamcrest/OCHamcrest.h>
@interface Counter : NSObject
@property (nonatomic,strong )NSUserDefaults * defaults;
@property (nonatomic) NSInteger count;
- (id)initWithUserDefault:(NSUserDefaults *) defaults;
- (void)increment;
- (void)decrement;
- (NSInteger) getCountInDefaults;
@end
(Counter.m)文件
#import "Counter.h"
#define countInDefaultID @"countID"
#define CounterModelChanged @"changed"
@implementation Counter
//1、先要看看这个方法中的功能,
//1.1)分别给两个变量赋值 _defaults 、_count 并且返回一个对象
//所以我们测试就是通过以这个函数为单元,然后测试里面的功能(可以这样理解)
- (id)initWithUserDefault:(NSUserDefaults *) defaults
{
self = [super init];
if (self) {
_defaults = defaults; //这个变量是由外部参数决定的
_count = [self getCountInDefaults]; //这个参数是由getCountInDefaults方法进行获取的
}
return self;
}
//1)计算出来的_count 存储到NSUserDefault中,
//2)发送notification,通知Controller
- (void)increment
{
_count = [self getCountInDefaults ]+ 1;
[_defaults setObject:[NSNumber numberWithInteger:_count] forKey:countInDefaultID];
//发送消息 CounterModelChanged这个也是应该是一个常量
[[NSNotificationCenter defaultCenter] postNotificationName:CounterModelChanged object:self ];
}
- (void)decrement
{
_count = [self getCountInDefaults ]- 1;
[_defaults setObject:[NSNumber numberWithInteger:_count] forKey:countInDefaultID];
//发送消息 CounterModelChanged这个也是应该是一个常量
[[NSNotificationCenter defaultCenter] postNotificationName:CounterModelChanged object:self ];
}
//- (void)decrement;
//
//主要是把
- (NSInteger) getCountInDefaults
{
NSNumber * remindereId = [_defaults objectForKey:countInDefaultID];
//这个countInDefaultID变量好像没有什么变化,哲理应该在全面有相应的定义
if (remindereId) {
remindereId = remindereId; //怎么看起来有点废话呀
}else{
//在NSUserDefault中没有任何的存储的时候,会返回0
//(缺点)它需要依赖外部的文件,因为读取的值是在文件中存储的,
//如果读取的值在文件中不存在或者不是期望的值,都会影响到测试的结果,一般是测试不应该是这样的
//一个稳定并且健壮的测试不应该受到外界文件影响,所以需要是用工具————>mock(登场)
//mock工具可以将依赖去掉,这样将会更加的稳定。mock 工具有:mock ocmockito
remindereId = @0;
}
return [remindereId integerValue];
/*
从功能的角度来看:有两个方面可以进行测试
1)在NSUserDefault中已经存储了值
2)没有任何值存储的情况
为了更加好的测试:我们需要mock一下NSUserDefault,然后通过NSUserDefault返回值来覆盖上面的两种情况
出现两个单元测试的方法
1)testGetCountInDefaultWithNilShouldReturnZero(即为:没有值的时候将会返的回0)
2)testGetCountInDefaultWithNumberThreeShouldReturnIntergerThree //返回原来存储的值
测试技巧:可以将那些方法中的重复的内容放在settup或者teardown中
*/
}
@end
上面就是Couter的类,一个计数的逻辑类。
基于上面的工程,下面就是相应的测试代码:————> 测试(测试用例——> 这个需要对相应的测试进行分析,然后得出用例)
在这个例子里面,我们看到的是只有一个逻辑(也就是model和ViewController部分) 和 (Controller与View之间的部分)进行测试
代码如下:
首先测试model——Controller部分:
#warning 测试的是相关model层的逻辑测试
//mdoel这些上的测试是逻辑在Controller和model之间的交互的测试(逻辑测试)
//controller ———message———> model ———notification—————>Controller
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "Counter.h"
#define HC_SHORTHAND
#import <OCHamcrest/OCHamcrest.h>
#define MOCKITO_SHORTHAND
#import <OCMockito/OCMockito.h>
#define CounterModelChanged @"CounterModelChanged"
@interface CounterTest2 : XCTestCase
@end
@implementation CounterTest2
{
Counter * sut;
NSUserDefaults * mockDefaults;
int modelChangedCount;
NSInteger modelChangedValue;
}
- (void)setUp {
[super setUp];
mockDefaults = mock([NSUserDefaults class]);
sut = [[Counter alloc]initWithUserDefault:mockDefaults];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(counterModelChanged:) name:CounterModelChanged object:sut];
}
- (void)tearDown
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
sut = nil;
[super tearDown];
}
- (void)testExample {
XCTAssert(YES, @"Pass");
}
- (void)testPerformanceExample {
[self measureBlock:^{
}];
}
//检车返回的结果
//这里都是需要第三方的库
- (void)testGetCountInDefaultWithNilShouldReturnZero{
//given的部分
//这个方法是mock中的方法,强制mock对象返回一个所希望的值
[given([mockDefaults objectForKey:@"currentID"]) willReturn:nil];
assertThatInteger([sut getCountInDefaults],equalToInteger(0));
}
- (void)testGetCountInDefaultWithNumberThreeShouldReturnIntergerThree{
//given的部分
// [given(mockDefaults objectForKey:@"currentID") willReturn:@3];//这个方法可以在哪里查找到?
// assertThatInteger([sut getCountInDefaults],equalToInteger(3));
}
-
(void)counterModelChanged:(NSNotification *)notification
{
++modelChangedCount;
Counter * counter = (Counter*)[notification object];
modelChangedCount = [counter count];
}
//检查方法的调用
- (void)testIncrementShouldInvokeSetObject
{
[given([mockDefaults objectForKey:@"currentId"]) willReturn:@3];
[sut increment];
//下面的验证语句应该怎么样使用?
// [verify(mockDefaults) setNumber:@4 forkey:@"currentId"];
// [verify(mockDefaults) setObject:@4 forkey:@"currentId"];
}
//这个说明测试的增加没有对
- (void)testIncrementShouldPostNotification
{
//可以看到相应的测试的错误
[given([mockDefaults objectForKey:@"currentId"]) willReturn:@3];
[sut increment];
assertThatInt(modelChangedCount,is(equalToInt(1)));
assertThatInt(modelChangedCount, is(equalToInteger(4)));
}
@end
/*
1、注入依赖,通过mock方法强制依赖返回结果,从而验证方法返回结果是否正确(注入依赖其实就是一个切入一个监听的点)
(这里所用到的激素hi监听然后进行通知的设置,获取监听的结果进行设置,这里是使用到了相应的NSUserDefault的相关的内容,获取外面的文件的内容需呀进过相应检查)——————> 检查的是返回的结果
2、注入依赖,通过mock方法来判断是否是我们需要的方法进行调用。——————>检查方法的调用
3、被测试方法必须发送notification,这样测试方法必须有相应的测试的接收器。
*/
接下来是测试Controller与View的部分:
代码:
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "Counter.h"
#import "ViewController.h"
#define HC_SHORTHAND
#import <OCHamcrest/OCHamcrest.h>
#define MOCKITO_SHORTHAND
#import <OCMockito/OCMockito.h>
//这个是用来测试viwe的情况
@interface CounterViewControllerTest : XCTestCase
@end
@implementation CounterViewControllerTest
{
CounterViewControllerTest * sut;
Counter * mockCounter;
}
- (void)setUp {
[super setUp];
//需要引入相应的头文件
mockCounter = mock([Counter class]);
//这里应该也是要验证方法是否别调用正确
// sut = [[ViewController alloc] initWithCounter:mockCounter];
// [sut view];//有这个方法吗?
}
- (void)tearDown {
sut = nil;
[super tearDown];
}
- (void)testExample {
// This is an example of a functional test case.
XCTAssert(YES, @"Pass");
}
- (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)testPlusButtonShouldBeConnected
{
// assertThat([sut plusButton],is(notNilValue()));
}
//确定按钮是否绑定了正确的事情并且触发了正确的方法,即为点击了之后是否触发 加1
- (void)testPlusButtonAction{
//这个方法哪里来的调用
// UIButton *button = [sut plusButton];
// UIButton *button = sut.plusButton;
// assertThat([button actionsForTarget:sut forControlEvent:UIControlEventTouchUpInside],contains(@"incrementCount:",nil));
}
//检测counter是否发送了正确的消息,测试了是否调用了counter的increment方法,来保证Controller发送了正确的消息
- (void)testIncrementCountShouldAdkCounterToIncrement{
// [sut increamentCount:nil];
// [verify(mockCounter) increment];
}
//Controller中接受了model的消息之后,将会主动更新View显示的内容,所以还需要一些单元的测试来确认views的状态
- (void)testModleChangedNotificationShouldUpdateCountLabel{
[given([mockCounter count]) willReturnInteger:2];
// [[NSNotificationCenter defaultCenter] postNotificationName:CounterModelChanged object:mockCounter];
// assertThat([[sut counterLabel] text],is(@"2"));
}
@end
上面的代码运行之后出现的情况是:
这个错误的产生有可能是原来代码的问题,或者是我们写的测试代码有问题,可以先检查我们自己的代码,然后再确定是不是应用中的程序有误。
在这个过程容易出现的问题:
1、fatal error: 'XCTest/XCTest.h' file not found出现这个错误很可能是因为我们使用cocoa pod 加入了其他的框架ps:
ps:
在报错的Target中的Building settings中FRAMEWORK_SEARCH_PATHS添加
$(PLATFORM_DIR)/Developer/Library/Frameworks
添加了上面这一句之后(即为添加了相应的框架的搜索空间),这个路径应该系统自带的库的路径。
可以查看下面的图片:
2、老是会出现ocHamcrest.h文件找不到,一般的原因是:
我们在使用cocoaPod的时候,可以查看相应的界面是怎么样的。
可以查看一下相应的里面的文件的结构:
target 'iosCounter' do
end
//上面这里我们是放一下在开发的时候存放要安装的软件的配置文件,eg:AFNetframework类似这样的框。我们习惯性的放在上面,而没有看到下面。
//下面这一段是放有关测试的框架
target 'iosCounterTests' do
pod 'OCHamcrest', '~> 4.1.1'
end
而在其上面的快是存放应用开发的框架的配置文件。
如图:
使用cocoaPod的时候没有这个头文件。
#define HC_SHORTHAND
#import <OCHamcrestIOS/OCHamcrestIOS.h>
只有这个头文件。
#define HC_SHORTHAND
#import <OCHamcrest/OCHamcrest.h>
我们可以通过相应的cocoaPod来安装相应的OCHamcrest文件,然后在test文件中增加头文件:
#define HC_SHORTHAND
#import <OCHamcrest/OCHamcrest.h>
//cocoaPod 的方式应该ios中也是可以使用这个框架的,虽然github上面是又说引入ios
的框架是:
#define HC_SHORTHAND
#import <OCHamcrestIOS/OCHamcrestIOS.h>
但是下载了之后,并没有找到这个框架。<OCHamcrestIOS/OCHamcrestIOS.h>
3、下面是关于OCMockito 框架的使用
#define MOCKITO_SHORTHAND
#import <OCMockito/OCMockito.h>
4、出现问题,就是我们常常会出现的问题?还没有解决。
diff: /../Podfile.lock: No such file or directory
diff: /Manifest.lock: No such file or directory
error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.
ps:
1)一般的要求就是执行:pod install 但是没有显示什么效果。
即为上面的两个pod的配置文件没有找到。所以pod的依赖关系存在问题。
看到后面的一句错误的提示就是我们的sandbox(沙盒)和 Podfile.lock(cocoaPod依赖文件)不存在问题。
可能上面的安装和更行都没有成功。可以尝试下面的方法。
2)下面的这个方法是
先关相应的Xcode空间,
然后执行
rm -rf MyProject.xcworkspace //删除原理的xcworkspace文件,
pod install //然后再重新创建
在pod install之前,请确保已经执行pod setup命令。//即为pod已经建立。
这个问题后来从新创建了一次就可以了,很郁闷不知道为什么??哎哎,下次遇到再研究吧。
3)比较好的查看错误的连接
http://stackoverflow.com/questions/17072396/cocoapods-errors-on-project-build/19131855#19131855
OCMockito的链接:
https://github.com/jonreid/OCMockito
5、有些文件是拷贝进来的,可能系统会找不到,所以需要我们去将它加入编译的环境中:
问题: