본문 바로가기

컨퍼런스 정리

GopherCon Korea 2023 정리 4편(최종)

728x90

 

3편에서 이어진다. 텍스트 추출은 Voice2Text를 사용했으며, 텍스트의 정리는 LLM을 사용했다.

 

Go 테스트의 거의 모든 것

https://youtu.be/8BDGRsdUtpc?si=W_Bk7VJ46qSk_V9j

발표 개요

이 발표에서는 Go 테스트의 기본부터 고급 기법까지 폭넓게 다루면서, 효과적인 테스트 코드 작성법을 소개함. 단순한 단위 테스트뿐만 아니라 성능 테스트, 퍼즈 테스트, 예제 테스트까지 포함하며, 실무에서 활용할 수 있는 다양한 기법을 공유함.


테스트 개념과 필요성

  • 테스트의 목적:
    • 코드의 안정성 보장
    • 리팩토링 시 예상치 못한 버그 방지
    • 협업 시 신뢰성 확보
  • Go의 테스트 특징:
    • 표준 라이브러리인 testing 패키지를 활용
    • 기본적으로 *_test.go 파일을 사용
    • go test 명령어로 간편하게 실행 가능

기본적인 단위 테스트

1. Go의 testing 패키지

  • 테스트 함수는 func TestXxx(t *testing.T) 형태로 정의
  • t.Errorf 또는 t.Fatal 등을 사용해 실패 조건 정의
  • go test -v 옵션으로 상세 로그 확인 가능

2. 서브 테스트 (Subtests)

  • t.Run을 사용하여 작은 단위로 테스트를 분리 가능
  • 반복문을 활용해 여러 입력 값을 테스트할 때 유용
func TestAddition(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 2, 3},
        {2, 3, 5},
        {10, 5, 15},
    }

    for _, tc := range tests {
        t.Run(fmt.Sprintf("%d+%d", tc.a, tc.b), func(t *testing.T) {
            result := Add(tc.a, tc.b)
            if result != tc.expected {
                t.Errorf("expected %d, got %d", tc.expected, result)
            }
        })
    }
}

3. 테이블 기반 테스트

  • 다양한 입력 값과 예상 결과를 한 테이블로 관리하며 테스트
  • 서브 테스트와 함께 사용하면 효율적

Mock과 testify 활용

1. testify 라이브러리 소개

  • Go에서 테스트를 간편하게 만들 수 있는 라이브러리
  • assert와 require를 활용해 코드 가독성을 높임
import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestSubtraction(t *testing.T) {
    assert.Equal(t, 5, Subtract(10, 5))
}

2. Mocking 기법

  • testify/mock 패키지를 활용해 의존성을 분리
  • 데이터베이스, API 등 실제 환경을 테스트할 수 없는 경우 Mock 객체를 활용

성능 테스트 (Benchmarking)

1. 벤치마크 테스트의 기본

  • func BenchmarkXxx(b *testing.B) 형태로 작성
  • b.N을 사용해 반복 실행하며 평균 성능 측정
func BenchmarkLoop(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = math.Sqrt(144)
    }
}

2. 벤치마크 실행 방법

  • go test -bench . 명령어로 실행

퍼즈 테스트 (Fuzzing)

  • testing.F를 활용해 예상치 못한 입력값을 자동 생성해 테스트
  • 보안 취약점이나 경계값 오류를 찾는 데 유용
func FuzzExample(f *testing.F) {
    f.Add("hello")
    f.Fuzz(func(t *testing.T, input string) {
        _ = process(input)
    })
}
  • go test -fuzz=FuzzExample 명령어로 실행

예제 기반 테스트 (Example Tests)

  • 문서화 + 테스트를 동시에 수행할 수 있는 기능
  • ExampleXxx 형태로 함수 작성
func ExampleAdd() {
    fmt.Println(Add(2, 3))
    // Output: 5
}
  • go test 실행 시 자동으로 출력 값과 비교

테스트 커버리지 확인

  • go test -cover 옵션으로 커버리지 확인
  • go test -coverprofile=coverage.out && go tool cover -html=coverage.out
    • HTML로 상세 커버리지 분석 가능

결론 및 정리

✅ Go에서는 표준 testing 패키지를 활용해 간단한 테스트를 작성할 수 있음
✅ testify 같은 라이브러리를 사용하면 테스트 코드 가독성을 높일 수 있음
✅ 벤치마킹과 퍼즈 테스트를 활용해 성능 및 보안 이슈를 사전에 방지 가능
✅ 커버리지 도구를 활용해 테스트 품질을 지속적으로 개선해야 함

버그 없는 프로그램 만들기: 테스팅의 관점으로

https://youtu.be/1GP_M5w7vtU?si=rZ7V5O6OlTTrD8_0

1. 왜 버그 없는 프로그램이 중요한가?

  • 블록체인은 사용자의 자산과 직접적으로 연결되어 있음.
  • 버그 발생 시 롤백 불가능 → 사용자 자산이 영구적으로 손실될 수 있음.
  • 법률적 책임이 클 수 있으며, 해외에서는 개발자가 직접 책임을 지는 사례도 존재.
  • 분산된 네트워크 환경 때문에 버그 수정이 더욱 어렵고 복잡함.
  • 실제 사례: Osmosis에서 63억 원 상당의 피해 발생 후, 테스팅의 중요성을 더욱 깊이 고민.

2. 블록체인과 테스팅의 차별점

  • 블록체인에서는 전통적인 유닛 테스트 개념이 존재하지 않음.
  • 모든 테스트는 인테그레이션 테스트처럼 진행됨.
  • 특정 함수만 테스트하는 것이 아니라 관련 API, 모듈까지 함께 테스트해야 함.

테스트 기법 소개

1. 유닛 테스팅

  • Go의 testing.T를 활용하여 개별 함수 검증.
  • 블록체인에서는 유닛 테스트도 인테그레이션 방식으로 진행됨.
  • Testify 라이브러리의 Suite 기능 활용:
    • 테스트 환경을 종합적으로 셋업할 수 있음.
    • SetupSuite()를 이용해 모든 테스트 전에 공통 설정 수행.

2. 뮤테이션 테스팅

  • 코드를 자동으로 변형하여 테스트가 이를 감지하는지 확인하는 기법.
  • 예시:
    • return user.Age >= 18 → return user.Age > 18 등으로 변형.
    • 변형 후에도 테스트가 실패하지 않으면, 기존 테스트가 불완전함을 의미.
  • 1980년대 논문에서 제안되었으나, 당시 컴퓨터 성능 문제로 활용 어려움.
  • 현대에는 go-mutesting 같은 도구를 활용하여 자동화 가능.

3. 엔드 투 엔드 (E2E) 테스팅

  • 프로그램 전체 흐름을 시뮬레이션하여 검증하는 방식.
  • 블록체인의 특성상, 분산된 여러 개의 노드에서 동작 검증이 필요함.
  • 테스트 방법:
    • 도커 컨테이너를 활용해 체인 A, B를 띄움.
    • 체인 A에서 체인 B로 트랜잭션을 전송하고, 정상 동작 여부 확인.

4. 퍼징(Fuzz) 테스팅

  • 랜덤한 입력값을 주입하여 예상치 못한 오류를 찾아내는 기법.
  • Go 1.18부터 공식적으로 지원.
  • 블록체인에서는 전체 프로그램을 대상으로 퍼징 수행:
    • API 요청을 랜덤하게 보내고, 전체적인 상태 변화가 정상적으로 유지되는지 검증.
    • 예시: 암호화폐 총 공급량이 변하지 않는지 테스트.

결론: 버그 없는 프로그램을 만들기 위한 노력

  • 블록체인은 롤백이 불가능하므로 철저한 테스팅이 필수.
  • 전통적인 테스팅 방법뿐만 아니라 뮤테이션, 퍼징, E2E 테스팅까지 적극 활용.
  • 선언적인 방식(Declarative Test Generation)으로 공리적 조건을 유지하며 테스트 수행.
  • NASA의 사례처럼 완벽한 프로그램을 목표로 지속적인 개선 필요.

"버그 없는 프로그램은 없다"는 말이 있지만, 우리는 완벽을 목표로 해야 한다.

Scenario Tester : 인수 테스트 자동화로 자신감, 생산성 높이기

https://youtu.be/gTw7gTJ4r2Y?si=x4Pim4FzMynN7vI2


1. 배경: AB180과 데이터 파이프라인

  • AB180은 마케팅 성과 분석 솔루션인 에어브리지를 운영.
  • 데일리 20억 건 이상의 사용자 행동 데이터를 수집하고 분석.
  • 핵심 파이프라인 구성:
    • WAAS: 사용자 데이터를 수집하는 서버.
    • Kafka: 데이터를 저장하고 전달.
    • Worker: 데이터를 처리하고 분석.

비용 절감 목표

  • 서버 비용 절감이 필요했고, 이를 위해 4가지 액션 아이템을 설정:
    1. WAAS를 파이썬에서 Go로 마이그레이션.
    2. Worker를 파이썬에서 Rust로 변경.
    3. DB 변경.
    4. 네트워크 비용 최적화.
  • 본 발표에서는 Worker의 Rust 마이그레이션 과정과 인수 테스트 자동화에 초점을 맞춤.

2. 마이그레이션 과정에서 테스트 전략

  • 기존의 파이썬과 Rust를 병행 운영해야 하는 상황.
  • 단위 테스트(Unit Test)와 통합 테스트(Integration Test)는 최대한 재사용.
  • 인수 테스트(Acceptance Test)는 완벽하게 옮기고, 추가적인 케이스도 생성.

인수 테스트란?

  • 요구사항을 검증하는 테스트.
  • 구조:
    • Given: 초기 환경 설정 (컨텍스트)
    • When: 특정 요청이 들어옴 (입력)
    • Then: 예상 결과를 검증 (출력)

3. 기존 인수 테스트의 한계

  1. 테스트 코드가 많아지면서 유지보수가 어려움.
  2. 현재 서비스가 어떤 기능을 지원하는지 한눈에 파악하기 어려움.
  3. 언어를 변경할 경우 기존 테스트를 옮기는 공수가 큼.

4. 해결책: 시나리오 테스트 자동화

  • 인수 테스트를 JSON 기반의 "시나리오"로 관리.
  • 테스트 자동화를 위해 두 가지 핵심 컴포넌트 추가:
    1. Scenario Generator
      • 실제 서비스와 상호작용하면서 자동으로 테스트 데이터(시나리오)를 생성.
    2. Scenario Tester
      • 저장된 시나리오를 실행해 결과가 일치하는지 검증.

시나리오 구조

{
  "context": { "db": "초기 DB 상태", "cache": "초기 캐시 값" },
  "input": { "request": "API 요청 데이터" },
  "output": { "response": "기대 결과", "db": "최종 DB 상태" }
}
  • 컨텍스트(Context): DB, 캐시 등의 초기 상태.
  • 입력(Input): 실제 서비스에 전달되는 요청 데이터.
  • 출력(Output): 기대하는 응답 및 데이터 변경 사항.

자동화 과정

  1. Scenario Generator
    • 로컬에서 서비스를 실행하고, 입력을 넣어 예상 결과를 저장.
  2. Scenario Tester
    • 저장된 시나리오를 실행하고 실제 서비스의 응답과 비교.
    • 차이가 있을 경우 상세 diff 제공.

5. 시나리오 테스트 도입 효과

  • 100억 개 이상의 테스트 케이스 생성 및 검증.
  • 파이썬과 Rust 코드의 일관된 검증이 가능.
  • 언어 변경 시에도 시나리오 파일만 공유하면 검증 가능.

6. 기존 단위 테스트로는 잡지 못했던 문제

  1. JSON Encoding 이슈
    • <, > 문자가 유니코드로 변환되는 문제.
    • \u003c, \u003e로 변환되어 클라이언트에서 예상과 다른 응답 발생.
  2. JSON Unmarshaling 대소문자 문제
    • lowerCaseKey vs LowerCaseKey → 예상과 다르게 언마샬링 성공.
    • 해결책: 맵(Map) 기반의 수동 언마샬링.
  3. 타임스탬프 처리 오류
    • 13자리 밀리초(millis) vs 10자리 초(seconds).
    • 테스트에서는 정상 작동했지만, 실제 배포 후 오류 발생.
  4. 해시(Hash) 알고리즘 불일치
    • MD5 vs SHA-256 → 테스트에서는 통과했지만, 실제 해싱 방식 차이로 문제 발생.

7. 도구 및 팁

  1. Go JSON Diff (github.com/wI2L/jsondiff)
    • JSON diff를 시각적으로 확인 가능.
  2. GORM After Query Hook
    • SQL 쿼리 실행 후 자동으로 결과 저장.
  3. Makefile 활용
    • cat scenario.json | scenario-tester 형태로 CLI 자동화.

8. 단점과 개선점

단점

  1. 개발 공수가 큼
    • 서비스 하나를 개발하는 수준의 비용 발생.
    • Rust, Go, Python 각각 Scenario Tester를 만들어야 했음.
  2. 잘못된 시나리오 생성 시 문제 발생
    • 실제 인풋이 바뀌어도 테스트가 실패하지 않는 문제 발생.
    • 인풋 데이터 변경 감지 로직 추가로 해결.

개선 목표

  1. 업스트림/다운스트림 강제화
    • A 서비스의 아웃풋을 B 서비스의 인풋으로 활용하도록 테스트 강제.
  2. 공통 시나리오 테스트 프레임워크 개발
    • Python, Go, Rust에서 동일한 시나리오 파일 사용.
  3. 비기능적 요구사항 검증 추가
    • 성능 테스트까지 포함하는 방향으로 확장.

9. 마이그레이션 결과

  • 월 1,000만 원 이상의 비용 절감.
  • 더 견고한 프로덕트 개발 가능 (강타입 언어 덕분에 숨겨진 버그 발견).
  • Rust + Go 도입 후 성능 대폭 향상.
  • 코드 리팩토링이 더 쉬워짐 (시나리오 기반 자동 검증 덕분).

10. 결론

시나리오 테스트 자동화를 통해 안정성과 생산성을 모두 확보
파이썬 → Rust & Go로의 마이그레이션을 안전하게 수행
단위 테스트로는 잡을 수 없는 문제까지 포괄적으로 해결
팀 전체의 코드 품질과 리팩토링 가능성을 크게 향상

"시나리오 테스트 자동화를 통해 자신감을 얻고, 더 빠르게 개발할 수 있다!" 🚀

총정리

GopherCon Korea 2023을 통해 알 수 있는 기술 동향은 다음과 같다.

 

1. Go가 집중적으로 사용되는 영역

현재 Go는 서버 개발, 클라우드 인프라, 데이터 파이프라인, 모니터링, 테스트 자동화 등 다양한 분야에서 강력한 역할을 수행하고 있습니다.

📌 주요 활용 분야:
클라우드 인프라 자동화 (Kubernetes + Go)
고성능 데이터 처리 (Kafka, Rust와 결합한 워커 시스템)
API 서버 및 마이크로서비스 개발
대규모 서버 모니터링 및 운영 자동화
테스트 자동화 및 품질 개선 (시나리오 테스트, 버그 탐지)


2. Go를 선택하는 이유

각 기업들이 발표에서 공통적으로 강조한 **"왜 Go인가?"**에 대한 이유는 다음과 같습니다.

🛠 Go를 선택한 이유:
1️⃣ 고성능 & 경량성: Rust보다 쉽고 Python보다 빠름.
2️⃣ Go의 단일 바이너리 배포: 크로스 플랫폼 지원, 서버리스 환경에서 특히 강점.
3️⃣ 쿠버네티스와의 강력한 시너지: K8S 오퍼레이터 패턴을 쉽게 구현 가능.
4️⃣ 자동화 및 병렬 처리에 적합: Goroutine과 채널을 이용한 효율적인 비동기 프로그래밍.
5️⃣ 메모리 관리 최적화: GC(가비지 컬렉션)가 있지만 성능 저하 없이 운영 가능.
6️⃣ 테스트 및 유지보수 용이: 강타입 언어로 코드 안정성이 뛰어나고, 테스트 코드 작성이 쉬움.

💡 Rust와의 비교:

  • 성능이 중요한 특정 워커 로직은 Rust,
  • 유지보수성이 중요한 비즈니스 로직은 Go를 선택하는 패턴이 많음.

3. Go와 함께하는 핵심 기술 스택

📡 주요 기업들이 Go와 함께 활용하는 기술 스택

분야 주요 기술
클라우드 인프라 Kubernetes (K8S), AWS Lambda, Kafka, MSK
데이터 파이프라인 Kafka, Redis, InfluxDB (TSDB), PostgreSQL
모니터링 & DevOps Prometheus, Grafana, OpenTelemetry
테스트 & 품질 개선 Ginkgo, Scenario Tester, Fuzz Testing
자동화 & 서버리스 AWS Lambda (Go), Kubernetes Operator
API & 백엔드 gRPC, REST API, GraphQL

Go가 클라우드 환경과 잘 맞는다는 점이 다시 한 번 강조됨.
특히 쿠버네티스 오퍼레이터 패턴과의 결합이 매우 활발하게 이루어지고 있음.


4. 기업들이 Go를 활용하는 방식 & 주요 사례

🔷 현대자동차

  • 프라이빗 클라우드 H 클라우드 개발
  • K8S 오퍼레이터 패턴을 사용해 데이터센터 자동화
  • 자동차 → SDV(Software Defined Vehicle) → 클라우드 연동

🔷 삼성전자

  • 대규모 AI 학습 인프라 구축
  • 데이터센터 및 클라우드 인프라 자동화

🔷 AB180 (에어브리지)

  • 파이썬 → Go & Rust로 마이그레이션
  • Scenario Tester 개발로 인수 테스트 자동화
  • Kafka & Go 활용한 대규모 데이터 파이프라인 운영

🔷 모니터링 & DevOps

  • 서버리스 기반의 경량 모니터링 툴 개발 (Go + InfluxDB)
  • Kafka를 활용한 실시간 로깅 시스템 구축

🔷 백엔드 API & 서버 개발

  • AWS Lambda + Go로 웹훅 처리 시스템 구축 (Go의 경량성 활용)
  • 대규모 API 요청을 처리하는 고성능 REST & gRPC 서버 개발

5. 최신 트렌드 및 향후 전망

🚀 Go를 활용한 최신 트렌드:
서버리스(Serverless)와 Go

  • AWS Lambda + Go로 가벼운 웹훅 처리
  • Go의 단일 바이너리 배포가 서버리스 환경에서 강점

Go + Kubernetes (K8S) → 클라우드 인프라 자동화

  • Kubernetes 오퍼레이터 패턴으로 데이터센터 자동화 (현대자동차)
  • 클라우드 네이티브 개발에 최적화된 언어로 자리 잡음

Rust & Go의 공존

  • Rust는 성능 최적화가 필요한 부분에서 선택
  • Go는 비즈니스 로직과 유지보수성이 중요한 부분에서 선택

테스트 자동화와 품질 개선

  • Scenario Tester 같은 프레임워크 도입 증가
  • 단순 유닛 테스트를 넘어 비기능적 요구사항(성능, 부하 테스트)까지 포함

Go의 테스팅 & QA 혁신

  • Fuzz Testing & Property-Based Testing 도입
  • 단순한 단위 테스트를 넘어 실제 운영 환경에 가까운 자동화 테스트 증가

Go의 데이터 처리 확장

  • Kafka, Redis, TSDB(InfluxDB)와의 조합이 많아짐
  • 실시간 데이터 처리대규모 이벤트 스트리밍에서 적극 활용

6. 결론: GopherCon Korea 2023에서 본 Go의 미래

🛠 Go는 이제 단순한 백엔드 개발 언어를 넘어 클라우드 인프라, 서버리스, 데이터 파이프라인, 모니터링, 테스트 자동화 등 다양한 분야에서 핵심 기술로 자리 잡음.

🏆 기업들이 Go를 선택하는 이유는 성능, 배포 편의성, 유지보수성, 자동화 가능성 때문이며, 특히 쿠버네티스와의 강력한 시너지가 Go의 성장을 더욱 가속화하고 있음.

📈 향후 전망

  • 서버리스 및 클라우드 인프라 자동화에서 더 강력한 역할 수행
  • 테스트 자동화 및 품질 개선에 집중 (Scenario Tester 같은 도구 증가)
  • Rust & Go의 조합이 더욱 많아질 것 (각각의 장점을 활용하는 방식)
  • Go 기반 데이터 처리 시스템의 증가 (Kafka, TSDB, Redis 활용 확대)

🔍 결론적으로, Go는 클라우드 네이티브 개발과 서버 자동화에서 가장 강력한 언어 중 하나로 자리 잡았으며, 앞으로도 지속적인 성장과 확장이 기대됨! 🚀

 

728x90

'컨퍼런스 정리' 카테고리의 다른 글