iOS 單元測試

什麼是單元測試

單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,通常來講,要根據實際狀況去斷定其具體含義,如 C 語言中單元指一個函數,Java 裏單元指一個類,圖形化的軟件中能夠指一個窗口或一個菜單等。總的來講,單元就是人爲規定的最小的被測功能模塊。git

單元測試是在軟件開發過程當中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其餘部分相隔離的狀況下進行測試。github

爲何要作單元測試

1)在一個複雜的項目中添加某功能模塊時,能夠快捷的進行鍼對性測試,而不用將整個項目 Run 起來。web

2)能夠便捷的對某個具體方法進行測試。編程

單元測試有哪些

iOS 開發(或者 MacOS、tvOS、watchOS 等)中,單元測試有多種方式,主要分爲 Xcode 提供的以及第三方測試框架這兩類:數組

  • Xcode自帶 XCTest:XCTest 是 Xcode自帶的單元測試工具,其前身是 OCUnit,隨着 Xcode 的發展,XCTest 已經愈來愈完善,功能也越強大。bash

  • 第三方框架 GHUnit:GHUnit 是 GitHub 上著名的開源測試框架,可視化、開源、擴展等功能,讓其相比 XCTest 更增強大(如今的 XCTest 也很完善了,不過 GHUnit 比較老,如今已經中止維護,不建議使用) OCMock:OCMock 也是 Github 上的著名開源測試框架,用於 Mock、Stub,爲測試提供數據做假功能。框架

以上三種就是比較主流的測試 Xcode 單元測試途徑,還有一些 BDD 行爲測試框架:異步

什麼是 BDD BDD(Behavior Driven Development),即行爲驅動開發,是敏捷開發技術之一,經過天然語言定義系統行爲,以功能使用者的角度,編寫需求場景,且這些行爲描述能夠直接造成需求文檔,同時也是測試標準。async

  • Specta:GitHub 上輕量級的 BDD 測試框架。
  • Expecta:與 Specta 同個做者,是一個功能強大的匹配框架。
  • Kiwi:Kiwi 是重量級的,集 OCMock、Specta、Expecta 所擁有的功能與一身的 BDD DSL 測試框架。

其餘的,還有一些其餘測試工具、測試方式,如:函數

  • Nocilla:強大的 HTTP 模擬測試工具。
  • OHHTTPStubs:也是 HTTP 模擬測試工具。
  • TUDelorean:基於 Objective-C 的時間模擬測試工具。
  • KIF:集成/界面測試工具,其它的還有 Frank、Calabash 等。
  • GitHub + Jenkins + TestFlight:自動化測試。
  • Monkey Test:隨機測試。

XCTest 測試

一、建立測試類

注意,全部測試類都繼承自 XCTestCase。

二、測試類的結構

默認建立的單元測試類爲一個 .m 文件,裏面包含了如下四個方法:

  • - (void)setUp:在每一個測試用例開始前調用,能夠作一些測試準備工做,爲可選方法。
  • - (void)tearDown:在每一個測試用例結束後調用,能夠作一些測試收尾工做,爲可選方法。
  • - (void)testExample:默認建立的測試用例。
  • - (void)testPerformanceExample:性能測試方法。

三、測試流程

當咱們默認執行測試時,系統找到全部的測試類,並執行每一個測試方法;咱們也能夠選擇性地執行某些測試而已,好比,在 scheme 中 disable 某個用例,或者直接在測試導航欄中每一個測試用例後面的運行按鈕,單獨執行某個測試。

默認流程以下:

上一個測試類 -> 當前類+ (void)setUp -> [ - (void)setUp -> 測試方法 -> - (void)tearDown ] (循環直至當前類測試方法所有執行完) -> 當前類 + (void)tearDown -> 下一個測試類

四、測試方法

測試方法以 test 爲前綴,沒有參數,返回值爲 void,方法中用斷言來判斷測試的正確性:

- (void)testColorIsRed {
   // Set up, call test subject API. (Code could be shared in setUp method.)
   // Test logic and values, assertions report pass/fail to testing framework.
   // Tear down. (Code could be shared in tearDown method.
}

- (void)testThatItDoesURLEncoding {
    // given
    NSString *searchQuery = @"$content$amp;?@";
    HTTPRequest *request = [HTTPRequest requestWithURL:@"/search?q=%@", searchQuery];

    // when
    NSString *encodedURL = request.URL;

    // then
    XCTAssertEqualObjects(encodedURL, @"/search?q=%24%26%3F%40");
}
複製代碼

五、測試斷言

斷言通常由判斷條件、字符串 format、字符串參數組成,參數可選,在 XCTest 中,斷言有如下分類:

  • Unconditional Fail:XCTFail,失敗時候拋出。
  • Equality Tests:用於斷言兩個表達式相等或不相等,如:XCTAssertEqual、XCTAssertEqualWithAccuracy、XCTAssertNotEqual、 XCTAssertGreaterThan。
  • Boolean Tests:斷言布爾表達式真假,如:XCTAssertTrue、XCTAssertFalse。
  • Nil Tests:空斷言,如:XCTAssertNil、XCTAssertNotNil。
  • Exception Tests:斷言表達式拋出或不拋出異常,如:XCTAssertThrows、XCTAssertThrowsSpecific、XCTAssertNoThrow。

Specta、Expecta 測試

SpectaExpecta 都是出自 Github 做者 Orta 之手,他最出名的開源框架莫過於 Cocoapods

Specta

Specta 是一個輕量級 BBD 測試框架,其爲 DSL (Domain-Specific Language) 模式,讓測試更加接近於天然語言描述,更加易懂。

一、主要有如下特色:

  • 容易集成到項目中。
  • 基於XCTest編寫,能夠很好地與XCTest配合使用。

二、語法介紹

  1. SpecBegin 聲明瞭一個測試類,SpecEnd 結束類聲明

  2. describe (context) 塊聲明瞭一組實例

  3. it (example/specify) 是一個單一的樣例

  4. beforeAll 是一個執行於所有同級塊以前的塊,僅僅執行一次。afterAll 與beforeAll相反,是在所有同級塊以後執行的塊。僅僅執行一次。

  5. beforeEach/afterEach,在每個同級塊執行的時候,都會執行一次,而beforeAll/afterAll僅僅會執行一次

  6. it/waitUntil/done()。異步調用,注意完畢異步操做以後。必須調用done()函數。例如如下:

it(@"should do some stuff asynchronously", ^{
	waitUntil(^(DoneCallback done) {
	  // Async example blocks need to invoke done() callback.
	  done();
	});
});
複製代碼
  1. sharedExamplesFor 和 itShouldBehaveLike結合在一塊兒。可以實現在不一樣的spec之間共享同一套test case sharedExamplesFor 設置多個spec之間共享的test case,第一個參數做爲標識符。經過itShouldBehaveLike來執行spec中test case。第一個參數傳入sharedExamplesFor設置時使用的標識符。

注意:在describe局部使用sharedExamplesFor定義shared examples。可以在它做用域內覆蓋全局的shared examples。

  1. pending,僅僅打印一條log信息。不作測試。這個語句會給出一條警告,可以做爲一開始集中書寫行爲描寫敘述時還未實現的測試的提示。

三、通常使用:

SpecBegin(Car)
    describe(@"Car", ^{
        __block Car *car;

        // Will be run before each enclosed it
        beforeEach(^{
            car = [Car new];
        });

        // Will be run after each enclosed it
        afterEach(^{
            car = nil;
        });

        // An actual test
        it(@"should be red", ^{
            expect(car.color).to.equal([UIColor redColor]);
        });

        describe(@"when it is started", ^{
            beforeEach(^{
                [car start];
            });

            it(@"should have engine running", ^{
                expect(car.engine.running).to.beTruthy();
            });
        });

        describe(@"move to", ^{
            context(@"when the engine is running", ^{
                beforeEach(^{
                    car.engine.running = YES;
                    [car moveTo:CGPointMake(42,0)];
                });

                it(@"should move to given position", ^{
                    expect(car.position).to.equal(CGPointMake(42, 0));
                });
            });

            context(@"when the engine is not running", ^{
                beforeEach(^{
                    car.engine.running = NO;
                    [car moveTo:CGPointMake(42,0)];
                });

                it(@"should not move to given position", ^{
                    expect(car.engine.running).to.beTruthy();
                });
            });
        });
    });
SpecEnd
複製代碼

上面例子對 Car 這個類作測試,經過多個上下文嵌套(describe/context),結合不一樣的條件(beforeEach),來做出不一樣的斷言(it);當咱們某個測試失敗時,咱們會收到一段很明確的錯誤信息,好比:汽車啓動後應該移動到指定位置這個用例測試失敗,那麼咱們會收到 Car move to when the engine is running should move to given position 這麼一段話。這樣很是接近天然語言的描述會讓咱們很快知道錯誤出在哪裏。

四、注意:

  • 若是想用 SPEC_BEGIN 和 SPEC_END 替代 SpecBegin and SpecEnd,應該在引入頭文件以前寫上 #define SPT_CEDAR_SYNTAX

  • 若是要使用 XCTest Resporter,那麼在 Test Scheme 中,把 SPTXCTestReporter 字段值改成 SPECTA_REPORTER_CLASS

  • 把環境變量 SPECTA_SHUFFLE 設置爲 1 啓用測試拖拽(test shuffling)

Expecta

Expecta 是基於 Objective-C/Cocoa 的斷言框架,XCTest 自帶的斷言 XCAssert 有好幾個基礎操做,不過基礎的斷言不太豐富,和 Specta 也沒有很適配。 Expecta 不同,將匹配過程從斷言中剝離開,能夠很好地適配 Specta 的 DSL 斷言塊。

一、Expecta 有如下幾個特色:

  • 沒有類型限制,好比數值 1,並不用關心它是整形仍是浮點數

  • 鏈式編程,可讀性高,如:expect(foo).notTo.equal(1)

  • 反向匹配,斷言不匹配只需加上 .notTo 或者 .toNot,如:expect(x).notTo.equal(y)

  • 延時匹配,能夠在鏈式表達式中加入 .will、.willNot、.after(interval) 等操做來延時匹配

  • 可擴展,支持增長自定義匹配

二、基礎匹配 API:

expect(x).to.equal(y); // x 與 y 相等
expect(x).to.beIdenticalTo(y); // x 與 y 相等且內存地址相同
expect(x).to.beNil(); // x 爲 nil
expect(x).to.beTruthy(); // x 爲 true(非 0)
expect(x).to.beFalsy(); // x 爲 false(0 值)
複製代碼

三、異步匹配

describe(@"WebImage", ^{
	beforeAll(^{
	    // 設置默認延時匹配時間
	    [Expecta setAsynchronousTestTimeout:2];
	});

	it(@"will not be nil", ^{
	    //	使用默認延時匹配
	    expect(webImage).willNot.beNil();
	});

	it(@"should equal 42 after 3 seconds", ^{
	    // 不使用默認延時匹配,手動設置爲3秒
	    expect(webImage).after(3).to.equal(42);
	});
});
複製代碼

四、自定義使用

#import <Specta/Specta.h>
#import <Expecta/Expecta.h>
#import "ImageModel.h"

SpecBegin(ImageModel);

__block ImageModel *model;

beforeEach(^{
    model = [[ImageModel alloc] initWithUrl:@"http://pic37.nipic.com/20140113/8800276_184927469000_2.png" title:@"天空獨角馬" described:@"在黃色的沙漠裏,特別突兀"];
});

it(@"should not nil", ^{
    expect(model).toNot.beNil();
});

it(@"equal", ^{
    expect(model.url).to.equal(@"http://pic37.nipic.com/20140113/8800276_184927469000_2.png");
    expect(model.title).to.equal(@"天空獨角馬");
    expect(model.described).to.equal(@"在黃色的沙漠裏,特別突兀");
});

SpecEnd;
複製代碼

總結

Expecta 和 Specta 須要配合使用,與 XCTest 同樣都是基於 XCTestCase 實現。在斷言的使用上,XCTest 太過死板,Expecta 和 Specta 則很靈活,能夠知足大部分場景需求。

相關文章
相關標籤/搜索