ios的单元测试

现在单元测试暂时是基于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、有些文件是拷贝进来的,可能系统会找不到,所以需要我们去将它加入编译的环境中:

问题:





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值