호댕의 iOS 개발

[CI/CD] GitHub Actions를 이용한 테스트 자동화 본문

Software Engineering

[CI/CD] GitHub Actions를 이용한 테스트 자동화

호르댕댕댕 2023. 7. 11. 23:14

GitHub Actions를 도입하게 된 배경

테스트 코드를 짜더라도 다른 작업으로 바쁘게 되면 테스트 코드가 방치될 수 있다. 

Xcode에서 빌드를 하더라도, 테스트를 따로 돌려보지 않으면 테스트 코드에 문제가 생겼는지 아닌지 알 수 없기 때문이다. 

 

그래서 여기 저기 작업을 하고 코드를 수정하다보면 어느새 실패하는 테스트 코드가 속속 생겨날 수 있고, 아예 테스트 코드가 빌드조차 되지 않을 수 있다 😱

 

따라서 코드의 품질 향상을 위해 도입한 테스트 코드가 오히려 짐덩이처럼 될 수 있다고 생각한다. 

 

어떻게 하면 매번 테스트를 돌려보지 않으면서도 테스트 코드의 장점을 가지고 갈 수 있을까 고민을 하다가, GitHub Actions를 통해 테스트실행을 자동화하고 유지보수를 용이하게 하는 방법을 생각해봤다. 

 

이렇게 일정 상황에서 테스트 실행을 자동화하고 테스트가 잘 통과했는지 알아서 확인한다면, 작업 생산성도 올라가고 만약 테스트 코드에 문제가 생기더라도 바로바로 처리를 할 수 있을 것이다!! 

 

 

이제 말로만 들어보던 GitHub Actions를 직접 해보자!!! 

 

GitHub Actions 도입

Repository를 보면 Actions 탭을 찾아볼 수 있다! 

 

여기를 누르고 우리는 Swift Package를 테스트할 것이니 Swift에 있는 Configure 버튼을 누르자.

그러면 어느정도 기본적인 템플릿을 제공해준다. 

 

기본적인 개념은 GitHub Docs에 잘 정리되어 있다. (더 확인해보고 싶으시다면 링크로 확인해주세요)

 

About workflows - GitHub Docs

Get a high-level overview of GitHub Actions workflows, including triggers, syntax, and advanced features.

docs.github.com

 

WorkFlow

워크플로우는 하나 이상의 작업들로 구성하여 자동으로 실행되도록 미리 정해놓는 프로세스이다. 

 

일단 기본적으로 workflow는 YAML 파일로 정의되어 있어서 YAML 문법을 따라야 한다. 

여기선 들여쓰기가 매우매우 중요하다. (이것때문에 중간에 한참 삽질을 했다)

 

이번에 WorkFlow를 만들면서 나는 YAML Checker 라는 웹사이트에서 YAML 문법을 체크했다. 

 

YAML Checker - The Best YAML Validator

A fast and easy-to-use YAML syntax validator for developers, devops, or anyone else using YAML syntax

yamlchecker.com

친절하게 어디서 문제가 생겼는지 알려준다. 

GitHub Actions에서도 잘못된 문법의 YAML 파일을 작성하면 실패했다고 알려준다.

가끔은 제대로 알려주는데, 가끔 엄한 데에서 문제가 있다고 해서 한참 삽질을 했다. 

YAML Checker를 통해 확인하는 것이 더 나은 것 같다

 

그리고 workflow는 .github/workflows 이라는 폴더에 생기는데 이는 숨김 파일로 되어 있어 cmd + shift + . 을 통해 숨김 파일을 보고 확인해야 한다. 

 

물론 터미널로도 확인은 가능하다. (ls -a)

 

  • 이벤트 : 워크플로우가 실행되도록 트리거가 되는 하나 이상의 조건
  • 하나 이상의 작업이 각각의 runner 머신에서 실행되며 이는 하나 이상의 step으로 구성됨 
  • 각각의 step은 사용자가 정의한 스크립트를 실행하게 됨

워크플로우 직접 구성해보기

name: auto_test // WorkFlow의 이름

on: // Event
  pull_request: // main 브랜치로 pull request가 발생할 때 아래 있는 jobs가 실행됨
    branches: [ "main" ]

jobs:
  build:

    runs-on: macos-latest // apple 플랫폼에서 실행함 -> 해당 가상 머신 환경에서 실행됨

    steps:
    - uses: actions/checkout@v3
    - name: Test // Step의 이름
      run: |
        xcodebuild clean test -project JTAppleCalendarExample.xcodeproj -scheme JTAppleCalendarExample -destination 'platform=iOS Simulator,name=iPhone 14,OS=latest'
        // Xcode로 clean과 test를 진행함
        // 프로젝트는 JTAppleCalendarExample.xcodeproj임
        // iOS Simulator를 사용하며 iPhone 14로 가장 최신 OS 환경에서 테스트를 하게 됨
        // 👾 이때 실제 Xcode의 deployment target 설정이 test와 실제 product가 일치해야 함

일단 간단하게는 이렇게 테스트를 자동화할 수 있다. 

 

여기서 주의해야 할 점은 Xcode에서 iOS Deployment Target을 설정할 때 Test와 Product를 동일하게 가져가야 한다는 점이다. 

작성한 script에선 최신 OS 환경에서 테스트를 한다고 했는데 Test와 Product의 iOS Deployment Target이 다르면 Simulator의 버전이 꼬이면서 빌드 에러가 발생한다.

 

그리고 작성한 워크플로우를 테스트하고 싶다면 지정한 Event가 발생하도록 하여 직접 확인하면 된다. 

이에 대한 성공 여부는 Actions 탭에 쌓이게 된다.

리스트의 항목들을 탭하면 어디서 실패를 했는지도 직접 확인이 가능하다. 

 

여기까진 사실 아주 간단하고 다른 블로그에서도 포스팅이 아주 잘 되어 있었다. 

문제는 여기서부터이다.

 

보안 관련 Key 값들의 경우 보통 .gitignore 파일에 넣어두고 Git이 트래킹하지 못하도록 하여 GitHub에도 해당 파일이 올라가지 못하도록 관리한다. 

 

그러다보니 가상머신에서 빌드를 할 때 해당 파일이 없어서 빌드가 되지 않는 문제가 있었다... ㅎㅎ

 

.gitignore에 있는 파일을 직접 만들어 빌드하기

방법을 고민하다가 요 블로그 포스팅을 많이 참고했다. 

 

Hiding Secrets From Git in SwiftPM

A step-by-step guide on how to prevent your 3rd party service secrets from committing to Git when using apps modularized with SwiftPM.

www.fline.dev

위에서는 WorkFlow에서 단순히 테스트하는 로직만 들어있었다면 이제는 테스트를 하기 전에 필요한 파일을 생성하고 테스트를 할 수 있도록 하는 것이다. 

 

그리고 GitHub Secrets를 사용해 Key와 Value를 저장해두고 파일을 직접 생성할 때에는 이 값을 사용하는 방식이었다. 

GitHub Secrets를 사용하는 것도 아주 간단하다. 

 

Encrypted secrets - GitHub Docs

Encrypted secrets allow you to store sensitive information in your organization, repository, or repository environments.

docs.github.com

GitHub 레포지토리에서 일단 Settings 탭에 들어간 후 Secrets and variables에서 Actions를 선택한 후 New repository secret 버튼을 눌러 Key의 이름을 적고 value값을 적은 후 저장하면 끝이다. 

사용하는 것도 아래처럼 사용하면 된다.

${{ secrets.(Key의 이름) }}

여기까진 그래도 쉽다! 

 

처음에는 GitHub Secrets에 저장한 secrets들을 어떻게 프로젝트의 소스코드에서 사용할까 했는데 GitHub Secrets에 저장한 secrets은 단순히 테스트를 위해 생성하는 파일로 이해하면 되고, 실제 프로젝트 소스코드에선 따로 생성해서 기존처럼 .gitignore에 등록한 후 사용하면 된다. 

 

 

내가 참고한 블로그 포스팅에선 JSON 형태의 파일을 생성하는 식으로 구현을 해놨다. 

하지만... 나는 plist 형태로 Key값을 관리하고 있었다. 

 

그래서 plist의 형태 그대로 추가하면 되겠지 생각했다. 

(하지만 이는 큰 오산...)

 

    steps:
    - uses: actions/checkout@v3

    - name: Setup secrets.plist
      run: |
        echo '
        {
            <?xml version="1.0" encoding="UTF-8"?>
            <plist version="1.0">
            <dict>
                <key>Key</key>
                <string>${{ secrets.KEY }}</string>
            </dict>
            </plist>
        }
        ' >> ./JTAppleCalendarExample/Secret/Key/secrets.plist

이렇게 생성하고 보니... 

unable to read input file as a property list: The operation couldn’t be completed.

요 에러가 발생하면서 생성한 plist 파일을 읽을 수 없다고 한다. 

 


참고로 빌드 관련 로그가 너무 길어지면 나중에 해당 로그를 볼 때 로그를 따로 다운로드해서 보라고 한다.

이는 로그 우측 상단 톱니 바퀴를 눌러서 Download log archive 버튼을 누르면 로그 파일을 받을 수 있다. 


관련해서 구글링을 계속 했지만 내 상황과 적합한 답을 찾진 못했다. 

그래서 ChatGPT를 이용했더니 오히려 적합한 답변을 줬다. 

 

물론 처음부터 정확한 답을 준 것은 아니고... 계속 질문을 바꿔가며 하다가 적당한 답변을 찾았다. 

 

그 방법은...! 

 

일단 JSON 형태로 파일을 생성한 뒤 plist로 변환하는 방식이었다. 

 

    - name: Make JSON file
      run: |
        echo '{
        "key": "${{ secrets.KEY }}",
        }' >> secrets.json
      
    - name: Convert JSON to .plist
      run: |
        if [ -f secrets.json ]; then
            plutil -convert xml1 -o ./JTAppleCalendarExample/Secret/Key/secrets.plist secrets.json
        else
            echo "secrets.json file not found"
            exit 1
        fi

변환할 때에는 처음 본 명령어였는데 plutil을 사용하면 된다. 

 

이렇게 하면 원하는대로 plist 파일을 생성할 수 있다. 

경로 관련해서도 잘 안됐었는데 아래처럼 작성하면 잘 동작했다. 

 

./{프로젝트 명}/{폴더 Hierarchy}/{생성하고자 하는 파일명}

참고한 블로그 포스팅에선 파일 경로를 다르게 작성하던데 이렇게 하면 가상머신에서 빌드를 할 때 생성한 파일이 원하는 위치에 들어있지 않았다. 

 

마지막으로 가상 머신에서 실행할 Step이 끝나면 생성한 파일을 제거하도록 했다. 

 

전체적인 WorkFlow는 다음과 같다. 

 

name: run_test

on:
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: macos-latest
    steps:
    - uses: actions/checkout@v3
    - name: Make JSON file
      run: |
        echo '{
        "key": "${{ secrets.KEY }}",
        }' >> secrets.json
      
    - name: Convert JSON to .plist
      run: |
        if [ -f secrets.json ]; then
            plutil -convert xml1 -o ./JTAppleCalendarExample/Secret/Key/secrets.plist secrets.json
        else
            echo "secrets.json file not found"
            exit 1
        fi

    - name: Run Tests
      run: |
        xcodebuild clean test -project JTAppleCalendarExample.xcodeproj -scheme JTAppleCalendarExample -destination 'platform=iOS Simulator,name=iPhone 14,OS=latest'

    - name: Clean Up
      run: |
        rm secrets.json ./JTAppleCalendarExample/Secret/Key/secrets.plist

 

보다보니 테스트 자동화를 비롯해서 다양한 것들을 GitHub Actions를 통해 구현이 가능한 것 같다. 

배포 자동화도 Xcode Clouds / Fastlane이랑 더불어서 고려해봐야겠다. 

Comments