[Dart & Flutter] 개발자를 위한 필수 개념 정리 (Git, 파일 입출력, TDD 등)

2025. 5. 20. 21:22Dart & Flutter

더보기

📖 목차

1. 협업을 위한 필수 도구 Git

2. 리눅스 환경의 디렉토리, 파일 경로

3. 해시 알고리즘

4. Flutter에서 파일 입출력 라이브러리

5. TDD
6. Dart 언어의 test 라이브러리를 통해 테스트 작성하는 방법


📌 1. 협업 도구 Git 기본 개념

 

Git이란?

 
Git은 버전 관리 도구입니다.
코드를 수정하거나 기능을 추가하다 보면 이전 상태로 되돌리고 싶을 때가 있습니다.
또한 여러 사람이 함께 개발할 때는 누가, 어떤 코드를, 왜 수정했는지를 명확하게 관리할 필요가 있죠.
 
Git은 이러한 문제를 해결해주는 분산 버전 관리 시스템(DVCS)입니다.


Git의 핵심 개념
 

저장소(Repository): Git이 코드를 추적하고 관리하는 공간
로컬 저장소(Local Repo): 내 컴퓨터에 있는 Git 저장소
원격 저장소(Remote Repo): GitHub, GitLab 같은 서버에 있는 저장소
커밋(Commit): 코드 변경 내용을 저장하는 스냅샷
브랜치(Branch): 독립적으로 개발을 진행할 수 있는 작업 공간
병합(Merge): 브랜치를 하나로 합치는 작업


Git 설치 및 설정
 

1. Git 설치
공식 다운로드 링크

 

Git - Downloads

Downloads macOS Windows Linux/Unix Older releases are available and the Git source repository is on GitHub. Latest source Release 2.49.0 Release Notes (2025-03-14) Download Source Code GUI Clients Git comes with built-in GUI tools (git-gui, gitk), but ther

git-scm.com

 
2. 사용자 정보 설정

git config --global user.name "조성은"
git config --global user.email "you@example.com"

 
이 정보는 커밋할 때 ‘누가 수정했는지’ 기록되므로 꼭 설정해 주세요!


Git 기본 사용 흐름

# 1. 저장소 초기화
git init

# 2. 파일을 스테이징
git add main.dart

# 3. 커밋 (스냅샷 저장)
git commit -m "처음으로 main.dart 파일 추가"

# 4. 원격 저장소 연결 (GitHub와 연동할 때)
git remote add origin https://github.com/username/repo.git

# 5. 원격 저장소로 업로드
git push -u origin main

브랜치 (Branch)란?

 
브랜치는 기능 개발이나 실험을 기존 코드에 영향 없이 진행할 수 있게 해주는 도구입니다.

# 브랜치 생성
git branch feature/login

# 브랜치 전환
git checkout feature/login

# 또는 한 줄로
git checkout -b feature/login

 
예시 시나리오
• main 브랜치는 배포 가능한 안정된 코드
• feature/login 브랜치는 로그인 기능을 개발하는 브랜치
• 기능 개발 완료 후 main 브랜치로 merge!


변경 이력 보기

git status       # 현재 변경사항 확인
git log          # 커밋 히스토리 보기
git diff         # 변경된 코드 내용 확인

실습 예제

 
1. Git으로 첫 저장소 만들기

mkdir my_project
cd my_project
git init
touch main.dart
git add main.dart
git commit -m "첫 커밋"

 
2. GitHub에 연동하기
• GitHub에서 새 저장소 생성 → URL 복사

git remote add origin https://github.com/yourname/my_project.git
git push -u origin main

협업할 때 Git을 사용하는 방법

 
브랜치 전략
main: 배포용
develop: 개발 중인 코드
feature/: 기능 단위 브랜치 (예: feature/calendar-ui)
hotfix/: 긴급 수정 브랜치
 
협업 시 꿀팁
• 자주 pull해서 팀원 코드 반영
• 커밋 메시지는 명확하고 간결하게

feat: 로그인 기능 추가
fix: 회원가입 오류 수정
refactor: 불필요한 코드 정리

정리

 
• Git은 코드의 타임머신입니다. 잘 활용하면 언제든 과거로 돌아갈 수 있어요!
• 초보일수록 작은 단위로 자주 커밋하는 습관을 들이세요.
• GitHub, GitLab 같은 플랫폼을 활용하면 협업도, 이력 관리도 편리해집니다.


📌 2. Linux 환경에서 디렉토리와 파일, 경로

 

Linux는 왜 중요한가?

 
개발자라면 터미널과 친해져야 해요.
 
iOS, Android, 웹, 서버 등 어떤 분야의 개발자든,
Linux나 Unix 기반의 환경에서 터미널 명령어를 사용하는 상황은 반드시 생깁니다.
 
Flutter나 Dart로 개발할 때도, CLI 명령어를 입력하는 경우가 많죠.
이 글에서는 디렉토리/파일 관리 명령어와 경로 개념을 쉽고 빠르게 정리해볼게요.


디렉토리와 파일 관리 기본 명령어

1. 디렉토리 관련 명령어
ls: 현재 디렉토리 안의 파일/폴더 목록 보기
pwd: 현재 작업 중인 디렉토리 경로 출력
cd: 디렉토리명 디렉토리 이동
mkdir: 폴더명 새 디렉토리 만들기
rmdir: 폴더명 비어있는 디렉토리 삭제
 
2. 파일 관련 명령어
touch: 파일명 새 파일 생성
rm: 파일명 파일 삭제
cp: 원본 복사본 파일 복사
mv: 원본 새이름 파일 이름 변경 / 이동
cat: 파일명 파일 내용 출력
 
제 실습

mkdir my_folder
cd my_folder
touch hello.txt
ls         # hello.txt 확인
cat hello.txt

경로(Path)란 무엇인가?

 
경로는 파일 또는 폴더의 ‘위치’입니다.
 
Linux는 트리 구조로 모든 파일과 폴더를 관리합니다.
경로에는 두 가지 종류가 있습니다:

경로 종류 설명  예시
절대 경로  루트 /부터 시작하는 전체 경로  /home/username/project/main.dart
상대 경로 
현재 위치 기준의 경로  ../main.dart, ./lib/utils.dart

 
- 특수 기호

기호 의미
현재 디렉토리
현재  디렉토리
사용자 홈 디렉토리 (/home/사용자명)

 
실전 예시

cd ~/Desktop/my_project        # 내 바탕화면 프로젝트 폴더로 이동
cd ..                          # 한 단계 위로 이동
ls ./lib                       # lib 폴더 안의 파일 목록 보기

실습 예제: 간단한 디렉토리 구조 만들기

mkdir flutter_app
cd flutter_app
mkdir lib assets
touch main.dart lib/home.dart assets/logo.png
ls -R

 
-R 옵션은 재귀적으로 폴더 안까지 모두 보여줍니다.


CLI 명령어 단축키 & 팁
 

↑ / ↓: 이전 명령어 불러오기
Tab: 자동완성 (파일/디렉토리 이름)
Ctrl + L: 터미널 화면 지우기 (clear와 동일)
Ctrl + C: 실행 중인 명령 중지


정리
 

• Linux 환경은 파일과 디렉토리 중심으로 움직입니다.
• GUI 환경과 달리, CLI에서는 경로를 정확히 이해해야 원하는 파일을 다룰 수 있어요.
• 개발자는 터미널을 능숙하게 다룰수록 빠르고 강력하게 작업할 수 있습니다.


📌 3. 해시 알고리즘(Hash Algorithm)

해시(Hash)란?

 
해시는 아무 길이의 데이터를 고정된 길이의 문자열로 바꿔주는 기술입니다.
이렇게 변환된 결과를 해시값(hash value) 또는 다이제스트(digest)라고 부릅니다.
 
예시

입력: "hello"
출력: 5d41402abc4b2a76b9719d911017c592 (MD5 해시)

 
한 글자만 바꿔도 결과가 완전히 달라져요!


해시 알고리즘의 특징

 
고정 길이 출력: 입력 길이에 관계없이 항상 일정한 길이의 해시값을 생성
결정적(Deterministic): 같은 입력이면 항상 같은 출력
빠른 계산 속도: 빠르게 변환 가능
충돌 회피: 서로 다른 입력이 같은 해시값을 갖기 어렵도록 설계됨
비가역성(역으로 복원 불가능): 해시값만 보고 원래 데이터를 알 수 없음


해시 알고리즘의 주요 용도
 

1. 무결성 검사 (Integrity Check)
파일이 변경되었는지 확인할 때 사용
→ 파일을 열지 않고도 “이게 원본과 같은지” 확인 가능!
 
2. 비밀번호 저장
비밀번호를 그대로 저장하지 않고 해시값으로 저장
→ 해커가 DB를 털어도 원래 비밀번호는 모름!
 
3. 디지털 서명 & 인증
전자서명, 인증서 등에서도 해시를 사용하여 문서 위조 여부 확인
 
4. Git의 버전 관리
Git은 커밋 내용을 해시값으로 식별함 → 정확하고 빠르게 변경 이력을 추적 가능


Git에서 해시 알고리즘은 어떻게 쓰일까?

 
Git은 내부적으로 SHA-1 해시 알고리즘을 사용하여 커밋, 트리, 블롭 등 모든 객체에 고유한 ID(해시값)를 부여합니다.

$ git log

commit 9a1f35b7a8c342718f994dcf849e88be89e7c6e7
Author: 조성은 <you@example.com>
Date:   Mon May 20 14:00:00 2025 +0900

    🎉 초기 커밋: 프로젝트 구조 세팅

 
위의 긴 문자열이 바로 SHA-1 해시값!
이 값은 커밋한 파일, 작성자, 시간, 메시지 등 모든 정보의 조합을 바탕으로 생성되며,
1바이트라도 달라지면 전혀 다른 해시값이 생성됩니다.
 
즉, Git은 해시값을 기반으로 변경 이력을 추적하고, 무결성을 보장합니다.


SHA-1 외에 다른 해시 알고리즘

알고리즘  해시 길이 보안성 설명
MD5  128비트  낮음  빠르지만 충돌이 쉽게 발생
SHA-1  160비트  낮음 (Git 기본값)  Git에서 주로 사용하지만 보안 취약점 있음
SHA-256  256비트  높음  보안성이 높아 최근 대체로 사용
SHA-3  256/512비트 등  매우 높음  새로운 구조로 설계된 안전한 해시

해시 충돌(Collision)이란?
 

서로 다른 입력인데 같은 해시값이 나오는 현상을 충돌(Collision)이라고 합니다.
 
해시 충돌이 발생하면:
• 무결성 보장에 문제가 생길 수 있음
• 보안적으로 치명적일 수 있음 (예: 위조된 문서를 원본처럼 보이게 할 수 있음)
 
그래서 보안 분야에서는 SHA-256 이상을 권장하는 추세예요.


직접 해시값 구해보기 (CLI 실습)
 

- Linux/macOS에서

echo -n "hello" | sha1sum
# 결과: aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d

echo -n "hello!" | sha1sum
# 결과: 561f9a0786684aeb7a962ca7ee40c8a6d490d6c5

 
느낌표 하나만 달라도 전혀 다른 해시값이 생성되는 걸 확인할 수 있습니다.


정리
 

• 해시 알고리즘은 데이터를 고정된 길이의 문자열로 바꾸는 변환 함수입니다.
• Git은 커밋의 고유 ID로 SHA-1 해시값을 사용해 변경 이력을 추적합니다.
• 보안과 무결성 보장을 위해 해시 알고리즘은 필수적인 기술입니다.


📌 4. Flutter의 파일 입출력 라이브러리

– dart:io 라이브러리로 데이터 저장하고 불러오기
 

파일 입출력이란?

 
입력(Input): 파일에서 내용을 읽어오기
출력(Output): 파일에 내용을 저장하기
 
예를 들어 앱에서 유저가 쓴 글을 저장하거나, 로컬 설정 값을 기록하고 불러오는 기능이 필요할 때 파일 입출력(File I/O)이 사용돼요.


Flutter에서 사용하는 주요 라이브러리
 

Flutter에서는 Dart 기본 라이브러리인 dart:io를 통해 파일을 다룰 수 있어요.
단, 파일 시스템은 웹 환경에서는 지원되지 않고, 모바일, 데스크탑 등 로컬 파일 시스템이 존재하는 플랫폼에서만 동작해요.
 
- import 방법

import 'dart:io';
import 'package:path_provider/path_provider.dart';

1) 저장할 위치 얻기 – path_provider
 

Flutter에서 앱 전용 저장 경로를 얻으려면 path_provider 패키지를 사용해야 해요.

 

pubspec.yaml에 추가

dependencies:
  path_provider: ^2.1.2

 
경로 불러오기 예시

Future<String> getLocalPath() async {
  final directory = await getApplicationDocumentsDirectory();
  return directory.path;
}

2) 파일에 문자열 저장하기
 

- 예제 코드: 텍스트 저장

Future<File> writeToFile(String content) async {
  final path = await getLocalPath();
  final file = File('$path/data.txt');

  // writeAsString은 문자열을 파일에 씀
  return file.writeAsString(content);
}

3) 파일에서 문자열 읽어오기

 
- 예제 코드: 텍스트 읽기

Future<String> readFromFile() async {
  try {
    final path = await getLocalPath();
    final file = File('$path/data.txt');

    // 파일 내용 읽어오기
    return await file.readAsString();
  } catch (e) {
    return '파일을 읽을 수 없습니다: $e';
  }
}

전체 흐름 실습

void main() async {
  await writeToFile('Flutter는 짱이야!');
  final content = await readFromFile();
  print('파일 내용: $content');
}

 
출력 결과:

파일 내용: Flutter는 짱이야!

유용한 파일 관련 메서드들
 

exists() : 파일이 존재하는지 확인
delete() : 파일 삭제
create() : 파일 생성
readAsString() : 문자열로 읽기
writeAsString('내용') : 문자열로 쓰기


주의사항: 비동기 처리

dart:io는 대부분의 함수가 Future 기반으로 동작하므로 await, async를 꼭 사용해야 해요!


추가: 여러 줄 쓰기 & 리스트 저장하기

Future<void> writeList(List<String> lines) async {
  final path = await getLocalPath();
  final file = File('$path/log.txt');

  final sink = file.openWrite(); // Stream 방식으로 쓰기 시작
  for (final line in lines) {
    sink.writeln(line);
  }
  await sink.close(); // 꼭 닫아줘야 저장됨!
}

정리

 
• Flutter에서 파일 입출력은 dart:io와 path_provider를 조합해서 사용해요.
• 파일 경로는 getApplicationDocumentsDirectory()를 통해 확보해요.
• 파일은 비동기적으로 읽고/쓸 수 있으며, 앱 데이터 저장에 활용할 수 있어요.


📌 5. TDD와 Dart 테스트 라이브러리로 단위 테스트 완전 정복!

– 테스트 주도 개발로 Flutter 앱의 품질을 높이자
 

TDD란?

“테스트 코드를 먼저 작성하고, 그 테스트를 통과하는 코드를 구현하는 개발 방식”이에요.
 
3단계 순환 구조
1. 실패하는 테스트 작성 (Red)
2. 테스트를 통과시키는 최소한의 코드 작성 (Green)
3. 코드 리팩토링 (Refactor)

Red → Green → Refactor → (반복!)

 
이 과정을 반복하면서 점점 신뢰할 수 있는 코드를 만들어나가는 게 바로 TDD!


왜 TDD를 써야 할까?

 
버그 예방: 사전에 테스트를 짜서 버그 발생률 ↓
유지보수 용이: 코드 수정 시 테스트가 보호막 역할
문서화 효과: 테스트 코드 자체가 “어떻게 동작해야 하는지” 보여줌
심리적 안정감: 뭔가 고쳐도 기존 기능이 잘 돌아가는지 즉시 확인 가능


Dart에서 테스트하려면?

 
Flutter 또는 Dart 프로젝트에서 test 라이브러리를 사용하면 단위 테스트를 손쉽게 작성할 수 있어요!


1) test 라이브러리 설치
 
✅ pubspec.yaml에 추가

dev_dependencies:
  test: ^1.24.0

 
dev_dependencies에 추가해야 해! 테스트는 개발할 때만 사용하니까 😎


2) 테스트 파일 구조 만들기

 
예시 구조

/lib
  calculator.dart  
/test
  calculator_test.dart  ← 여기에 테스트 코드 작성!

3) 예제 – 간단한 계산기 테스트

 
- 먼저 로직부터 구현: lib/calculator.dart

class Calculator {
  int add(int a, int b) => a + b;

  int subtract(int a, int b) => a - b;
}

 
- 테스트 코드 작성: test/calculator_test.dart

import 'package:test/test.dart';
import '../lib/calculator.dart';

void main() {
  group('Calculator 테스트', () {
    test('덧셈 테스트', () {
      final calc = Calculator();
      expect(calc.add(3, 2), 5);
    });

    test('뺄셈 테스트', () {
      final calc = Calculator();
      expect(calc.subtract(5, 2), 3);
    });
  });
}

4) 테스트 실행하기

 
- 터미널에서 실행

dart test

 
- 성공 시 출력

00:00 +2: All tests passed!

 
두 개의 테스트가 모두 통과했습니다.


테스트 작성 시 자주 쓰는 함수들

 
test() : 개별 테스트를 정의
group() : 여러 테스트를 묶어서 그룹화
expect(actual, matcher) : 예상 결과 비교
setUp() : 각 테스트 전에 실행되는 코드
tearDown() : 각 테스트 후 정리 코드


테스트를 반복 작성하는 팁
 

• 기능 하나 만들면 → 바로 테스트도 하나 추가!
• 에러가 나면 → 테스트 먼저 만들고 수정!
• 테스트 실패는 “망했다”가 아니라 “이제 고치면 된다!”는 신호 🔔


정리

 
• TDD는 테스트 → 구현 → 리팩터링의 반복을 통해 안정적인 코드를 만드는 개발 방식이에요.
• Dart의 test 라이브러리를 사용하면, 간단하게 단위 테스트를 작성할 수 있어요.
• Flutter 개발에서도 상태관리, 유틸 함수, 모델 로직 등 비 UI 영역에서 적극 활용돼요.