2015年10月16日

Objective-CでCSVデータを扱う

田中です。

CSVデータの読み込みを行うのにライブラリを探していたところ、以下のライブラリが便利そうでした。

CHCSVParser

使い方

#import "CHCSVParser.h"

@interface ViewController ()
<
  CHCSVParserDelegate
>

@property (nonatomic) NSMutableArray *rows;
@property (nonatomic) NSMutableArray *columns;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *fileName = @"sample";
    NSBundle *bundle = [NSBundle mainBundle];
    NSString *path = [bundle pathForResource:fileName ofType:@"csv"];
    NSURL *url = [NSURL fileURLWithPath:path];
    
    CHCSVParser *parser = [[CHCSVParser alloc] initWithContentsOfDelimitedURL:url delimiter:','];
    parser.delegate = self;

    // 出力にダブルクォートのエスケープ無し
    parser.sanitizesFields = YES;

    // パース開始
    [parser parse];
}

#pragma mark - CHCParserDelegate methods


// パース開始
- (void)parserDidBeginDocument:(CHCSVParser *)parser
{
    self.rows = [NSMutableArray new];
}

// パース完了
- (void)parserDidEndDocument:(CHCSVParser *)parser
{
    // 完了時の処理
}

// パースエラー
- (void)parser:(CHCSVParser *)parser didFailWithError:(NSError *)error
{

}

// 列の読み込み開始
- (void)parser:(CHCSVParser *)parser didBeginLine:(NSUInteger)recordNumber
{
    self.columns = [NSMutableArray new];
}

// フィールドの値読み込み
- (void)parser:(CHCSVParser *)parser didReadField:(NSString *)field atIndex:(NSInteger)fieldIndex
{
    [self.columns addObject:field];
}

// 列の読み込み終了
- (void)parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber
{
    [self.rows addObject:self.columns];

    self.columns = nil;
}

@end


CSVデータを扱う場合、カンマと改行に気をつけてデータの読み込みを行えばいいですが、値の中にカンマや改行された文やダブルクォートなどがある場合、何も考えずに読み込むとそれらがエスケープされていたりカンマの読み込み位置が間違ったりしてしまいます。
その辺りすべて面倒見てくれるので便利でしたが、値の保持などは自分で作業しないといけないのが少し手間なので自分で管理クラスを作成した方が後々楽そうです。
posted by Seesaa京都スタッフ at 19:13| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする

2015年10月01日

Xcodeで行うUIテスト

iOS担当のエモトです.

今回は約3ヶ月前に公開したテストに関する記事の続きになります.前回において,User Interface Testing / Recording のことに触れましたが,当時はXcode7がbetaだったので控えましたが,ついに問題なく書ける日がやってきました.

UIテストを使ってみる

使い方は非常に簡単です.シミュレーターを起動して,赤い丸ボタンの録画ボタンを押して,実際のUI操作を録画するだけです.
uiteest.png

そして,そのテストコードを動かせば,録画された動作を自動で行います.XCTAssertを組み合わせれば,画面操作して得られた結果を判定することもできます.簡単な操作でUIテストが行えるのでとても魅力です,しかし,問題として,まだ非同期テストには対応していませんので,万能ではありません.使用する場所は限られますが,標準でUIテストをサポートしてくれたのはうれしいことですね.
posted by Seesaa京都スタッフ at 12:00| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする

2015年06月17日

テストフレームワークXCTest

iOS担当のエモトです.私事ではありますが,ラブライブ関係者様,劇場版の興行収益ランキング1位おめでとうございます.私も微力ながら貢献させていただきました.

さて先日のWWDCにて, Xcodeのアップデートが発表されました.ネイティヴアプリに変わったwatchOS2やオープンソースになるSwift2.0などと比べると話題性はあまり大きくないですが,他の新機能にはなかなか興味深いものがあり,Xcode7の正式リリースが楽しみです.

私が気になるものの1つは,User Interface Testing / Recording です.XcodeはテストフレームワークXCTestを用いたテストが可能ですが,UIテストは非対応でした.UIテスト用のサードライブラリはこれまでもありましたが,近年iOS端末の解像感が多様化してきたので使用を控えていました.標準でサポートされるのであれば,是非使っていきたいですね.

そのUI Testing / Recordingの紹介といきたいですが,Xcode7はいまbeta(2015/06/17 現在)ということもあり控えるとして,今回はテストフレームワークXCTestの簡単な使い方です(UI Testing / Recordingは一般公開されてからですね).

XCTestを使ってみる

例として,二つの実数を加算するメソッドをもつクラス(Calculator)を作成して,このメソッドをテストしてみます.実際は単純な足し算ですが,あとで非同期でのテストを試すため,わざと遅延処理をいれた完了ブロックを追加しています.

Calculator.h
#import <Foundation/Foundation.h>

extern NSString *const kNotificationCalculatorFinished;
extern NSString *const kNotificationCalculatorFailed;
typedef void (^CalculatorBlock) (float answer);

@interface Calculator : NSObject

@property (nonatomic) float answer;
@property (nonatomic, retain) NSString *answerString;

-(float)add:(float)v1
         to:(float)v2
 completion:(CalculatorBlock)completion;

@end
Calculator.m
#import "Calculator.h"

NSString *const kNotificationCalculatorFinished = @"kNotificationCalculatorFinished";
NSString *const kNotificationCalculatorFailed = @"kNotificationCalculatorFailed";
NSTimeInterval const kDelayInterval = 0.5;

@implementation Calculator

-(float)add:(float)v1 to:(float)v2 completion:(CalculatorBlock)completion
{
    self.answer = v1+v2;
    self.answerString = [NSString stringWithFormat:@"%@", @(self.answer)];
    
    if (completion) {
        dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW,
                                             kDelayInterval * (NSTimeInterval)NSEC_PER_SEC);
        dispatch_after(when, dispatch_get_main_queue(), ^{
            completion(self.answer);
        });
    }
    
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc postNotificationName:kNotificationCalculatorFinished
                      object:nil];
    
    return self.answer;
}
@end

テストをしてみた

プロジェクト作成時に特に設定を変更していなければ「“プロジェクト名”Tests」というフォルダーがプロジェクトに作成されていると思います(ない場合はターゲット追加からテストを追加してください).その中でmファイルにテストコードを書いていきます.例として,加算メソッドのテストを書いてみます.
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "Calculator.h"

@interface SampleTestAppTests : XCTestCase
{
    Calculator *calculator_;
}
@end

@implementation SampleTestAppTests

- (void)setUp {
    [super setUp];
    calculator_ = [[Calculator alloc] init];
}

- (void)tearDown {
    [super tearDown];
}

- (void)testAdd
{
    float v1 = 1;
    float v2 = 2;
    float v3 = [calculator_ add:v1 to:v2 completion:nil];
    XCTAssertEqual(v3, 3);
}
@end
ここで決まりとして,テストコードは頭文字に必ずtestをつけます.この頭文字がないとテストコードと認識してくれません.テストコードを書き終えたら,左側のツリー状からコードを選択するか,ソース部の行番号の左あたりをクリックしてテストを実行します.なにも問題がなければテストは成功です.

xctest_run2.png

非同期でもテストがしたい

テストは非同期にも対応しています.幾つかの方法があり,以下のようにしてテストコードを書くことができます.
- (void)testAddAsync
{
    XCTestExpectation *expectation = [self expectationWithDescription:@"add async"];
    
    float v1 = 1;
    float v2 = 2;
    [calculator_ add:v1 to:v2 completion:^(float answer) {
        XCTAssertEqual(answer, 3);
        [expectation fulfill];
    }];
    
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        NSLog(@"%s, %@", __func__, error.localizedDescription);
    }];
}

- (void)testAddNotification
{
    XCTestExpectation *exp = [self expectationForNotification:kNotificationCalculatorFinished
                                                       object:nil
                                                      handler:^BOOL(NSNotification * __nonnull notification)
                              {
                                  NSLog(@"%s, notification %@", __func__, notification);
                                  [exp fulfill];
                                  return true;
                              }];
    
    float v1 = 1;
    float v2 = 2;
    [calculator_ add:v1 to:v2 completion:nil];
    
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        NSLog(@"%s, %@", __func__, error.localizedDescription);
    }];
}

- (void)testAddKVO1
{
    [self keyValueObservingExpectationForObject:calculator_
                                        keyPath:@"answerString"
                                  expectedValue:@"3"];
    
    float v1 = 1;
    float v2 = 2;
    [calculator_ add:v1 to:v2 completion:nil];
    
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        NSLog(@"%s, %@", __func__, error.localizedDescription);
    }];
}

- (void)testAddKVO2
{
    [self keyValueObservingExpectationForObject:calculator_
                                        keyPath:@"answerString"
                                        handler:^BOOL(id  __nonnull observedObject, NSDictionary * __nonnull change)
     {
         NSLog(@"%s, observedObject %@, change %@", __func__, observedObject, change);
         
         NSString *correct = @"3";
         
         //        Calculator *tempCalc = (Calculator*)observedObject;
         NSString *newStr = change[@"new"];
         return [newStr isEqualToString:correct];
     }];
    
    float v1 = 1;
    float v2 = 2;
    [calculator_ add:v1 to:v2 completion:nil];
    
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        NSLog(@"%s, %@", __func__, error.localizedDescription);
    }];
}


XCTestに関して簡単に紹介しました.より詳細な情報はアップルが公式ドキュメントを公開しているので,それを参照してもらえると幸いです.
posted by Seesaa京都スタッフ at 17:16| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする