Intro
Github Actions는 Github의 push, pull 등의 이벤트 발생 시 해당 이벤트에 대해 미리 정해둔 동작을 자동으로 수행하는 도구다.
이를 통해 pull 하기 전 자동으로 테스트를 수행하여 Fail 시 PR을 취소시키고 Slack을 통해 알림을 준다거나, 배포용 브랜치에 push 이벤트가 발생할 경우 AWS에 자동으로 Deploy 하는 등의 CI/CD 구성이 가능하다.
이번 시간엔 간단한 예제를 통해 Github Actions를 사용하는 방법에 대해 알아보자.
테스트 프로젝트 생성
Github Actions를 사용하기에 앞서 테스트용 프로젝트를 만들어주자.
아래와 같이 4개의 Dependency만 추가했다.
그리고 간단한 웹 어플리케이션을 작성한다.
Github 링크에 소스코드를 올려뒀으니 src 폴더를 복사해도 된다.
GitHub - DevJaewoo/github-action-test
Contribute to DevJaewoo/github-action-test development by creating an account on GitHub.
github.com
User.java
@Entity
@Table(name = "users")
@Getter
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@Enumerated(EnumType.STRING)
private UserAuthority authority;
public User(String name, UserAuthority authority) {
this.name = name;
this.authority = authority;
}
public User(String name) {
this(name, UserAuthority.ROLE_CLIENT);
}
}
UserAuthority.java
public enum UserAuthority {
ROLE_ADMIN,
ROLE_CLIENT,
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByName(String name);
}
UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public Long join(User user) {
if(user.getName().isEmpty()) {
throw new IllegalArgumentException("유효한 회원명이 아닙니다.");
}
if(userRepository.findByName(user.getName()).isPresent()) {
throw new IllegalArgumentException("이미 존재하는 회원입니다.");
}
userRepository.save(user);
return user.getId();
}
}
UserRepository.java
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/join")
public String join(@RequestParam String name) {
User user = new User(name);
Long id = userService.join(user);
return "Success! ID: " + id;
}
}
application.yml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/test
driver-class-name: org.postgresql.Driver
username: postgres
password: postgres
jpa:
hibernate:
ddl-auto: create
빌드 후 정상적으로 실행되는지 확인해보자.
테스트 케이스 작성
Github Actions가 자동으로 실행해줄 간단한 테스트 케이스들을 작성한다.
UserTest.java
class UserTest {
@Test
@DisplayName("User 기본 권한이 Client로 설정되었는지")
public void User_Constructor_DefaultAuthorityTest() {
User user = new User("user");
Assertions.assertThat(user.getAuthority()).isEqualTo(UserAuthority.ROLE_CLIENT);
}
}
UserServiceTest.java
@SpringBootTest
@Transactional
class UserServiceTest {
@Autowired UserRepository userRepository;
@Autowired UserService userService;
@Test
@DisplayName("회원가입 정상적")
public void UserService_join_success() {
//given
User user = new User("user");
//when
Long id = userService.join(user);
//then
Assertions.assertThat(id).isGreaterThan(0);
}
@Test
@DisplayName("회원가입 사용자명 공백 실패")
public void UserService_join_emptyUsername() {
//given
User user = new User("");
//when
IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> userService.join(user));
//then
Assertions.assertThat(e.getMessage()).isEqualTo("유효한 회원명이 아닙니다.");
}
@Test
@DisplayName("회원가입 사용자명 공백 실패")
public void UserService_join_duplicateUsername() {
//given
User user = new User("user");
//when
userService.join(user);
IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> userService.join(user));
//then
Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
}
}
Github Action 만들기
이제 main 브랜치에 push, pull 할 때 코드를 자동으로 테스트하는 Action을 추가해보자.
아래 사진처럼 Actions 탭에 들어가서 Github에서 바로 생성할 수도 있지만, 연습 삼아 로컬에서 생성하고 push 해보자.
Github Action은 /github/workflows 폴더 안의 파일을 실행한다.
ci-test-main.yml 파일을 /github/workflows 폴더에 만들어주자. 파일명은 다르게 해도 괜찮다.
코드에 대한 설명은 주석으로 달아놨다.
ci-test-main.yml
# ci-test-main.yml
# workflow의 이름을 정의한다.
name: 'ci-test-main'
# workflow가 언제 동작할지 정의한다.
# 이 workflow의 경우 main branch에 push 또는 pull_request 이벤트가 발생할 경우 동작한다.
on:
push:
branches:
- 'main'
pull_request:
branches:
- 'main'
# job은 사용자가 정한 플랫폼을 통해 step이라는 일련의 과정을 실행할 수 있다.
# 여러 개의 job을 사용할 수도 있고, job끼리 정보 교환도 가능하다.
jobs:
# test라는 job을 정의한다.
test:
# job의 이름을 정의한다.
name: Build and test project
# job이 실행될 환경을 정의한다.
runs-on: ubuntu-latest
# job 실행 중 필요한 서비스들을 정의한다.
# Docker Container로 설정할 수 있다.
# 이 프로젝트는 PostgreSQL을 사용했기 때문에 postgres 환경으로 구성했다.
services:
postgres:
image: postgres:latest
env:
# application.yml에서 DB를 test로 지정했었다.
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
# step에선 쉘 스크립트나 이미 만들어진 action 등을 사용할 수 있다.
steps:
# Github Acitons는 프로젝트를 CI 서버로 내려받아 특정 브랜치로 checkout하여 실행한다고 한다.
# 원래는 쉘 스크립트로 CI 서버, 코드 저장소 간 인증, 절차 등을 신경써서 일일이 작성해줘야 하지만,
# 다른 사람이 만들어둔 action이 이미 있기 때문에 코드 한 줄로 갖다 쓸 수 있다.
- uses: actions/checkout@v3
# 이 프로젝트는 JAVA 환경에서 동작하기 때문에 CI 서버 또한 JAVA 환경을 구성해줘야 한다.
# checkout과 마찬가지로 다른 사람이 만들어 둔 action이 있기 때문에 갖다 쓰면 된다.
- name: Setup JAVA
uses: actions/setup-java@v3
with:
# 프로젝트에서 corretto-17 JDK를 사용했기 때문에 그에 맞게 구성해줬다.
java-version: '17'
distribution: 'corretto'
# gradle build 진행 시 실행 가능한 파일이 아니라는 에러를 띄우며 fail이 나기 때문에 실행 권한을 줘야 한다.
- name: Add executable permission to gradlew
run: chmod +x ./gradlew
# gradle build 작업 안에 test 작업도 기본적으로 들어가있기 때문에 build만 해주면 된다.
# gradle build 작업도 다른 사람이 만들어 둔 action을 사용했다.
- name: Gradle build
uses: gradle/gradle-build-action@v2
with:
arguments: build
아래의 링크에서 각 step에 대한 사용법을 확인할 수 있다.
코드를 origin/main 브랜치에 push 하면 아래와 같이 workflow가 자동으로 실행되는 것을 볼 수 있다.
원래 #1부터 시작해야 되는데 여러 가지 오류들을 해결하다 보니 #7번째에 성공했다.
테스트 Fail 되는 경우 테스트
Branch를 하나 새로 만들어서 Fail이 날 수밖에 없는 테스트 케이스를 추가하고, main에 Pull Request 시 Test가 정상적으로 Fail 되는지 확인해보자.
test/failure라는 브랜치를 새로 만들고 아래의 테스트 케이스들을 추가하자.
UserTest.java
class UserTest {
...
@Test
@DisplayName("단위테스트 실패 테스트케이스")
public void failure() {
Assertions.assertThat(1 == 2).isTrue();
}
}
UserServiceTest.java
@SpringBootTest
@Transactional
class UserServiceTest {
...
@Test
@DisplayName("통합테스트 실패 테스트케이스")
public void failure() {
//given
User user = new User("user");
//when
Long id = userService.join(user);
//then
Assertions.assertThat(id).isEqualTo(0);
}
}
Github에 Push 하고 main으로 Pull Request를 보내면, 아래와 같이 테스트가 자동으로 진행되고 Fail 된 것을 볼 수 있다.
Details로 들어가 보면 아래와 같이 이전에 추가한 2개의 테스트 케이스가 Fail 된 것을 볼 수 있다.
결론
이번 시간엔 Github Actions를 통해 테스트를 자동화하는 방법에 대해 알아봤다.
지금은 간단하게 테스트만 자동화해봤지만, 어떻게 사용하느냐에 따라 활용할 수 있는 방법이 무궁무진할 것 같다.
또한 작업을 자동화해주는 만큼 개발 시간을 많이 단축시켜 줄 것 같다.
참고자료
'Study > DevOps' 카테고리의 다른 글
[Docker-Compose] 여러 Docker 컨테이너를 한꺼번에 관리하기 (1) | 2022.10.04 |
---|---|
[Docker] 도커 간단한 명령어 모음 (0) | 2022.09.08 |
[Docker] 도커 설치하기 (0) | 2022.09.08 |
[Docker] Docker는 무엇이고, 왜 사용해야 할까? (0) | 2022.09.06 |