ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS] SnapshotTesting with SwiftUI (v1.11.0)
    iOS 2023. 4. 5. 08:48

    SnapshotTesting

    SnapshotTesting์„ ํ†ตํ•ด UI์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๊ณ , ์ฝ”๋“œ ๋ณ€๊ฒฝ์ด ๋นŒ๋“œํ•œ UI์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

    SnapshotTesting์ด๋ž€?

    Snapshot Test๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์‹œ์ ์˜ UI์˜ ์Šค๋ƒ…์ƒท์„ ๊ธฐ์ค€์„  (baseline)์ด๋ผ๊ณ  ํ•˜๋Š” ์œ ํšจํ•œ UI ์Šค๋ƒ…์ƒท๊ณผ ๋น„๊ตํ•˜์—ฌ UI๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ๋Š” ํ˜„์žฌ ์Šค๋ƒ…์ƒท์„ ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท๊ณผ ๋น„๊ตํ•˜๊ณ , ์Šค๋ƒ…์ƒท ๊ฐ„์— ์ฐจ์ด๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, UI๋Š” ๋ณ€๊ฒฝ๋œ ๊ฒƒ์ด๋ฏ€๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

     

    GitHub - pointfreeco/swift-snapshot-testing: ๐Ÿ“ธ Delightful Swift snapshot testing.

    ๐Ÿ“ธ Delightful Swift snapshot testing. Contribute to pointfreeco/swift-snapshot-testing development by creating an account on GitHub.

    github.com

    ์ถœ์ฒ˜ : Raywenderlich

     

     

    Snapshot Testing Strategies

    SnapshotTesting ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์ด๋ฏธ์ง€ ์ „๋žต (image strategy) ๋˜๋Š” ์žฌ๊ท€ ์„ค๋ช… ์ „๋žต (recursive description strategy)์„ ์‚ฌ์šฉํ•˜์—ฌ UIView, UIViewController๋“ค์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

    ์ด๋ฏธ์ง€ ์ „๋žต (Image Strategy)์€ UI์˜ ์‹ค์ œ ์Šค๋ƒ…์ƒท์„ ์ด๋ฏธ์ง€ ํŒŒ์ผ๋กœ ๊ฐ€์ ธ์˜จ ๋‹ค์Œ, ์ด๋ฏธ์ง€๋ฅผ ํ”ฝ์…€ ๋‹จ์œ„๋กœ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€๊ฐ€ ๋‹ค๋ฅด๋ฉด UI๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์ด๊ณ , ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

    ์žฌ๊ท€์  ์„ค๋ช… ์ „๋žต (Recursive description Strategy)์€ ๋ทฐ ๊ณ„์ธต ๊ตฌ์กฐ์˜ ๋ฌธ์ž์—ด description์„ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. description์ด ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ, UI๊ฐ€ ๋‹ฌ๋ผ์ง€๊ณ , ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

    ๋งˆ์ง€๋ง‰์œผ๋กœ, SnapshotTesting์€ ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ ๊ณ„์ธต์„ ์ผ๋ฐ˜ ํ…์ŠคํŠธ๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ณ„์ธต ์ „๋žต (hierarchy strategy)์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๊ณ , ์ด๋Š” UIViewController์—์„œ๋งŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

    Using the Image Strategy

    UIViewController๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ฉด ์ œ๊ณตํ•˜๋Š” ๋‹ค์–‘ํ•œ ์˜ต์…˜

    • drawHierarchyInKeyWindow: Bool = false
      • ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์˜ Key Window์—์„œ ๋ทฐ๋ฅผ ๋ Œ๋”๋งํ•˜๊ณ , appearance, visual effects ์‚ฌ์šฉ
    • on: ViewImageConfig
      • ViewImageConfig ์˜ต์…˜์œผ๋กœ ๋‹ค์–‘ํ•œ ์žฅ์น˜ ์„ ํƒ ๊ฐ€๋Šฅ
    • precision: Float = 1
      • ํ…Œ์ŠคํŠธ ํ†ต๊ณผ๋ฅผ ์œ„ํ•ด ์ผ์น˜ํ•ด์•ผ ํ•˜๋Š” ํ”ฝ์…€์˜ ํผ์„ผํŠธ๋กœ, ๊ธฐ๋ณธ์ ์œผ๋กœ 1์ด๊ณ , ์ด๋Š” ์ฆ‰ ํ”ฝ์…€์˜ 100%๊ฐ€ ์ผ์น˜ํ•ด์•ผํ•จ์„ ์˜๋ฏธ
    • size: CGSize = nil
      • ํ…Œ์ŠคํŠธ ๋ทฐ์˜ ์Šค๋ƒ…์ƒท์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๋ทฐ ํฌ๊ธฐ
    • traits: UITraitCollection = .init()
      • ํ…Œ์ŠคํŠธํ•  ๋งŽ์€ ํŠน์„ฑ๋“ค ์ง€์ • ๊ฐ€๋Šฅ

    ๊ธฐ๋ณธ ์Šค๋ƒ…์ƒท ์ƒ์„ฑํ•˜๊ธฐ

    ์ฒ˜์Œ์œผ๋กœ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•˜๋ฉด, ์•„์ง ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท (baseline snapshot)์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

    ์ฒ˜์Œ ์‹คํ–‰ํ•˜๋ฉด, SnapshotTesting์€ ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์„ ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ† ๋ฆฌ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

    ์œ„์™€ ๊ฐ™์ด  Snapshots  ํด๋”๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , ๊ทธ ์•ˆ์— ์ž๋™์œผ๋กœ ์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋œ๋‹ค!

     

    ์ด๋ ‡๊ฒŒ ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์ด ์ƒ์„ฑ๋˜๊ณ  ๋‚˜๋ฉด, ์ดํ›„์— ์ง„ํ–‰๋˜๋Š” ํ…Œ์ŠคํŠธ ์‹คํ–‰์€ ์ƒˆ ์Šค๋ƒ…์ƒท์„ ๊ฐ€์ ธ์™€์„œ ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.

    ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์ด ์ƒ์„ฑ๋˜์—ˆ์„ ๋•Œ ๋‚˜์˜ค๋Š” ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
    ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€

     

    ์ฒ˜์Œ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ, ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ์™€ ๋น„๊ตํ•  ์ฐธ์กฐ ์Šค๋ƒ…์ƒท์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. 

    ํ…Œ์ŠคํŠธ๊ฐ€ ์ฒ˜์Œ ์‹คํ–‰๋˜๋ฉด, ๊ทธ๋•Œ์„œ์•ผ SnapshotTesting ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

     

    ์—ฌ๊ธฐ์„œ image strategy๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์€ ์‹ค์ œ UI ๋ทฐ์˜ ๊ทธ๋ž˜ํ”ฝ ํ‘œํ˜„์„ ๊ณ ์œ  ์ฝ˜ํ…์ธ  ํฌ๊ธฐ (intrinsic content size)๋กœ ์ €์žฅํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

    ๊ธฐ๋ณธ ์Šค๋ƒ…์ƒท์„ ์ƒ์„ฑํ•˜๊ณ  ๋‹ค์‹œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ด์ œ ํ…Œ์ŠคํŠธ์— ์„ฑ๊ณตํ•ฉ๋‹ˆ๋‹ค!

     

     

    UI ๋ณ€๊ฒฝ ์‹œ ๊ธฐ๋ณธ ์Šค๋ƒ…์ƒท ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

    ๋ทฐ์˜ ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ํ•œ, ํ…Œ์ŠคํŠธ๋Š” ์„ฑ๊ณตํ•ฉ๋‹ˆ๋‹ค.

    ๋งŒ์•ฝ ๋ทฐ๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค๋ฉด, SnapshotTesting์€ ์ด๋Ÿฐ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

    Newly-taken snapshot does not match reference.

    ์ฆ‰, ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท๊ณผ ์ƒˆ๋กœ์šด ์Šค๋ƒ…์ƒท์ด ์ผ์น˜ํ•˜์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์ธ๋ฐ, ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€์—๋Š” ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท๊ณผ ์ƒˆ ์Šค๋ƒ…์ƒท์˜ ๊ฒฝ๋กœ๊ฐ€ ํฌํ•จ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค!

    Finder์—์„œ Command + Shift + G๋ฅผ ๋ˆŒ๋Ÿฌ์„œ ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•˜๊ธฐ!

     

    UI๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•˜๋ ค๋ฉด ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์„ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    assertSnapShot(matching:as:) ์œ„์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    isRecording = true

    isRecording์„ true๋กœ ์„ค์ •ํ•˜๋ฉด, SnapshotTesting ํ”„๋ ˆ์ž„์›Œํฌ์— ์ƒˆ ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์„ ์ƒ์„ฑํ•  ๊ฒƒ์„ ์•Œ๋ฆฌ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

     

     

    ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•˜์ง€๋งŒ SnapshotTesting์€ ํ…Œ์ŠคํŠธ๋ฅผ ์ฒ˜์Œ ์‹คํ–‰ํ–ˆ์„ ๋•Œ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ƒˆ๋กœ์šด ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

    ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋ฉด Record mode is on ์ด๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    isRecording = true๋ฅผ ์„ค์ •ํ•˜๋ฉด, ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•˜๊ณ  ์ƒˆ๋กœ์šด ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์ด ์ƒ์„ฑ๋œ๋‹ค.
    ์ƒ์„ธ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€

     

     

    ์ด์ œ ์ƒˆ๋กœ์šด ๊ธฐ์ค€ ์Šค๋ƒ…์ƒท์ด ์ƒ์„ฑ๋˜์—ˆ์œผ๋ฏ€๋กœ, isRecording = true๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

    ๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด, ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•ฉ๋‹ˆ๋‹ค.

    ํ…Œ์ŠคํŠธ ์„ฑ๊ณต

     

    ์ƒ์„ธ ๋ทฐ ํ…Œ์ŠคํŠธํ•˜๊ธฐ

    ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ๋‹ค์–‘ํ•œ ์•„์ดํฐ ๋ฒ„์ „, ํ™”๋ฉด ๋ฐฉํ˜•, ๊ธฐ๊ธฐ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ…Œ์ŠคํŠธ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ๋‹คํฌ ๋ชจ๋“œ์—์„œ ์•ฑ์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    setUpWithError ๋‚ด์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์—์„œ ๋งค๋ฒˆ ํ…Œ์ŠคํŠธ ์ค‘์ธ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

    ํŠน์ • ์•„์ดํฐ ๋ฒ„์ „ ํ…Œ์ŠคํŠธ

    /// Testing for Specific iPhone Versions
    func testBookDetailViewOniPhone() throws {
      assertSnapshot(
        matching: viewController,
        as: .image(on: .iPhoneX) // SnapshotTesting์—๊ฒŒ ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ iPhoneX ์Šคํฌ๋ฆฐ ์‚ฌ์ด์ฆˆ๋กœ ๋ Œ๋”๋งํ•˜๋„๋ก ์ง€์‹œ
      )
    }
    

    ํŠน์ • ๊ธฐ๊ธฐ ๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ

    /// Testing for Device Orientation
    func testBookDetailViewOniPhoneLandscape() throws {
      assertSnapshot(
        matching: viewController,
        as: .image(on: .iPhoneX(.landscape)) // ๊ฐ€๋กœ ๋ฐฉํ–ฅ์œผ๋กœ UI ํ…Œ์ŠคํŠธ
      )
    }
    
    /// Testing for Device Orientation
    func testBookDetailViewOniPadPortrait() throws {
      assertSnapshot(
        matching: viewController,
        as: .image(on: .iPadPro11(.portrait))
      )
    }
    

    ๋‹คํฌ ๋ชจ๋“œ ํ…Œ์ŠคํŠธ

    /// Testing for Dark Mode
    func testBookDetailViewOniPhoneDarkMode() throws {
      let traitDarkMode = UITraitCollection(userInterfaceStyle: .dark)
      
      assertSnapshot(
        matching: viewController,
        as: .image(on: .iPhoneX, traits: traitDarkMode)
      )
    }
    

    ํ…Œ์ŠคํŠธ๊ฐ€ ๋ชจ๋‘ ์„ฑ๊ณตํ•˜๊ณ , ์Šค๋ƒ…์ƒท์ด ์ €์žฅ๋œ ๊ฒƒ ํ™•์ธ ๊ฐ€๋Šฅ!

     

     

    Snapshot Testing Tutorial for SwiftUI: Getting Started

    Learn how to test your SwiftUI iOS views in a simple and fast way using snapshot testing.

    www.kodeco.com

     

    ๋Œ“๊ธ€

Designed by Tistory.