Github API를 이용해서 참여자들의 참여율을 계산하는 과제였습니다. 과제 요구사항을 그대로 가져와서 한 번 살펴보겠습니다.
github.com/whiteship/live-study/issues/4
과제 1. live-study 대시 보드를 만드는 코드를 작성하세요.
- 깃헙 이슈 1번부터 18번까지 댓글을 순회하며 댓글을 남긴 사용자를 체크 할 것.
- 참여율을 계산하세요. 총 18회에 중에 몇 %를 참여했는지 소숫점 두자리가지 보여줄 것.
- Github 자바 라이브러리를 사용하면 편리합니다.
- 깃헙 API를 익명으로 호출하는데 제한이 있기 때문에 본인의 깃헙 프로젝트에 이슈를 만들고 테스트를 하시면 더 자주 테스트할 수 있습니다.
자.. 일단 저는 백기선님 레포를 대상으로 진행했습니다. 깃헙 이슈 1번부터 18번까지 댓글 순회를 해야하는데 현 live-study issue를 보시면 아시겠지만 15주차로 끝이 났습니다. 저런 부분은 유동적인 부분이라 직접 issue 개수를 세어서 계산하도록 했습니다. 또한 마지막 issue는 과제가 아니므로 해당 issue는 건너뛰도록 구현했습니다.
GitHub github = GitHubBuilder.fromPropertyFile("location/my_custom_github.properties").build();
Access 또한 github 토큰을 미리 발급받아둔 것이 있어서 토큰 방식으로 사용했습니다. 토큰의 경우 따로 설정파일에 'oauth = 토큰' 의 형태로 저장하고 불러왔습니다.
package github.dashboard;
import org.kohsuke.github.*;
import java.io.IOException;
import java.util.*;
public class GithubDashBoard {
private GitHub gitHub;
private GHRepository repo;
public GithubDashBoard(String repoName) throws IOException {
this.gitHub = GitHubBuilder.fromPropertyFile("src/main/resources/application.properties").build();
this.repo = this.gitHub.getRepository(repoName);
}
private List<GHIssue> getIssue() throws IOException {
return this.repo.getIssues(GHIssueState.ALL);
}
private List<GHIssueComment> getComment(GHIssue issue) throws IOException {
return issue.getComments();
}
private GHUser getUser(GHIssueComment comment) throws IOException {
return comment.getUser();
}
private List<GHLabel> getLable(GHIssue issue){
return (List<GHLabel>) issue.getLabels();
}
private Map<String,Double> getParticipation(List<GHIssue> issues) throws IOException {
double issueCount = issues.size();
Map<String, Set<Integer>> issueParticipation = new HashMap<>();
Map<String,Double> per = new HashMap<>();
for(GHIssue issue:issues){
//과제만 필터링 하기 위한 파트
boolean flag = false;
for(GHLabel label: getLable(issue)){
if (label.getName().equals("과제")) {
flag = true;
}
}
if (!flag){
issueCount-=1;
continue;
}
for (GHIssueComment comment:getComment(issue)){
String userName = getUser(comment).getLogin();//login은 닉네임 getName은 리얼 네임을 리턴
if(userName.equals("whiteship")){
continue;//백기선님께서 코멘트 달아주신 것은 제외
}
Integer issueNum = issue.getNumber();
if(!issueParticipation.containsKey(userName)){
issueParticipation.put(userName,new HashSet<Integer>(Arrays.asList(issueNum)));
}
else{
issueParticipation.get(userName).add(issueNum);
}
}
}
for(String name: issueParticipation.keySet()){
per.put(name,(double)((issueParticipation.get(name).size()*100)/issueCount));
}
return per;
}
public void displayDashBoard() throws IOException {
List<GHIssue> issues = getIssue();
Map<String, Double> per = getParticipation(issues);
for (String name: per.keySet()) {
System.out.println(name + " "+ String.format("%.2f", per.get(name)));
}
}
public static void main(String[] args) throws IOException {
GithubDashBoard git = new GithubDashBoard("whiteship/live-study");
git.displayDashBoard();
}
}
처음에는 그냥 main 함수에 모든 코드를 넣었습니다만 SRP 원칙 위배라고 생각해서 조금 분리했습니다. 그랬더니 이게 맞나 싶네요. 그냥 한줄짜리 코드를 저렇게 분리하는게 맞는걸지.. 흠.. 또 저 말고 다른 분께서도 그렇게 하셔서 더 혼란스럽습니다.
getParticipation 함수를 짤 때 이 분의 코드를 많이 참고했습니다. 원래 set을 쓰지않고 그냥 List로 구현했더니 중복 출석이 체크가 되더라구요. 자바에서 set을 처음 써봐서 곤란했습니다. List에서 set으로 넘어가는 과정에서 많이 참고했고 그렇게 완전 똑같아져버렸습니다. 비슷해지는 김에 main에서 토큰 발급받는 과정도 생성자로 넘겼더니 깔끔해졌습니다. (최대한 main에서 처리하는 일을 줄이려고 노력중입니다. 함수 호출만 남겨두려구요.) 베낀다는 마음가짐보다는 저보다 더 나은 것을 습득한다는 느낌으로 사용했는데 잘 모르겠네요. 좋은 코드는 어떻게 짤 수 있는지.. 노력은 하고 있으나 아직 미숙합니다.
issueParticipation.put(userName,new HashSet<Integer>(Arrays.asList(issueNum)));
//issueParticipation.put(userName,new HashSet<Integer>(issueNum)); 원래 했던 코드
나름 스스로 해본다고 주석으로 처리한 방식으로 사용했었는데 다시 생각해보니 issueNum의 크기만큼 set이 생성되더라구요. 머쓱했었습니다.
테스트코드
package github.dashboard;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kohsuke.github.GHIssue;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
class GithubDashBoardTest {
@Test
void getParticipationTest() throws IOException {
GithubDashBoard git = new GithubDashBoard("whiteship/live-study");
List<GHIssue> issues = git.getIssue();
Map<String, Double> per = git.getParticipation(issues);
Assertions.assertThat(String.format("%.2f",per.get(사용자))).isEqualTo(기대값);
}
}
README.md에 사용자별 참여율이 이미 정리되어있으니 테스트 사용자가 값이 제대로 나오는지 확인하도록 구성했습니다. 이 과정에서 문제가 있는데, 최종 출력을 할 함수빼고는 private으로 선언했더니 테스트 코드에서도 호출을 못했습니다. 저야 단순 테스트니까 잠깐은 풀어도 될듯싶어서 public으로 변경하고 작성했는데 다른 방법을 찾아봅시다.
package github.dashboard;
import com.fasterxml.jackson.databind.JsonSerializer;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.kohsuke.github.GHIssue;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
class GithubDashBoardTest {
@Test
@SuppressWarnings("unchecked")
void getParticipationTest() throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
GithubDashBoard git = new GithubDashBoard("whiteship/live-study");
Method issue = git.getClass().getDeclaredMethod("getIssue");
issue.setAccessible(true);
Method participation = git.getClass().getDeclaredMethod("getParticipation", List.class);
participation.setAccessible(true);
Map<String,Double> per = (Map<String, Double>) participation.invoke(git,issue.invoke(git));
Assertions.assertThat(String.format("%.2f",per.get(이름))).isEqualTo(기대값);
}
}
매우 많이 길어지긴 했지만 이런 식으로 작성하시면 private으로 작성이 가능해집니다. 아래 사이트를 참고해서 작성했습니다. 문제는 Map<String,Double>가 있는 라인에서 uncheckd 에러가 자꾸 발생합니다. 타입의 안정성을 보장할 수 없을때 발생하는 에러인데 무시하라고 어노테이션을 넣었네요ㅠㅠ
참고
github-api.kohsuke.org/index.html
외에 본문에서 언급중인 블로그
'Java' 카테고리의 다른 글
[JAVA] 클래스 (0) | 2021.04.19 |
---|---|
[JAVA] LinkedList,Stack.Queue 구현하기 (0) | 2021.04.17 |
[JAVA] JUnit 5 기초 (0) | 2021.04.14 |
[JAVA] 자바 제어문 - 반복문 (0) | 2021.04.13 |
[JAVA] 자바 제어문 - 조건문 (0) | 2021.04.12 |