본문 바로가기

Programming/Golang

견고한 Go 모듈 만들기

개요

golang 은 쉽게 패키지를 가져오고 내보낼 수 있는 언어이다. 이 번 장에서는 go 모듈을 만드는 과정을 써내려가려한다. 저자는 nb-logger 라는 non-bloking logger 를 만드려고 하기 때문에 nb-logger 와 관련된 부분은 독자가 원하는 패키지 명으로 변경해서 작성하면 된다.

 

먼저 모듈 소스 코드를 작성할 디렉토리를 만들고 이동한다.

$ mkdir nb-logger
$ cd nb-logger

 

또한 라이브러리 형태의 go 모듈을 만들기 위해서는 패키지를 가져오고 내보낼 repository 가 필요하다(repo 없이하려면 매우 귀찮다). github를 이용해서 repository 를 만들고,  go 모듈을 초기화할 때 사용한다. go mod init {repository path} 이렇게 하면 go 모듈이 생성 되고, go.mod 파일이 생성된다.

$ go mod init github.com/banaconda/nb-logger
go: creating new go.mod: module github.com/banaconda/nb-logger.
$ ls
README.md  go.mod
$ cat go.mod
module github.com/banaconda/nb-logger

go 1.18

 

아래와 같이 logger.go 파일에 테스트 코드를 작성하고 repo 로 push 한다

package nblogger

import "fmt"

func Hello(name string) string {
	message := fmt.Sprintf("Hi, %v. Wellcome!", name)
	return message
}

 

그리고 다른 go 모듈을 만든다. 아까와 다르게 repo 경로가 아닌 hello 만 넣어서 로컬 모듈로만 사용한다.

$ cd
$ go mod init hello

 

hello.go 파일을 수정하기 전에 아까 만들었던 모듈을 가져오자. go get {repository path} 명령을 수행하면. 아래와 같이 모듈을 다운받고, go.mod 파일에 require 로 nb-logger 가 추가된 것을 확인 할 수 있다.

$ go get github.com/banaconda/nb-logger
go: downloading github.com/banaconda/nb-logger v0.0.0-20220702111429-9594bb378c3c
go: added github.com/banaconda/nb-logger v0.0.0-20220702111429-9594bb378c3c

$ cat go.mod 
module hello

go 1.18

require github.com/banaconda/nb-logger v0.0.0-20220702111429-9594bb378c3c // indirect

 

이제 hello.go 파일을 작성해보자. 간단하게 nblogger 의 Hello 함수를 호출해서 출력하는 프로그램이다.

package main

import (
	"fmt"

	nblogger "github.com/banaconda/nb-logger"
)

func main() {
	message := nblogger.Hello("test")
	fmt.Println(message)
}

 

이제 실행해보자. go run {filename} 로 실행하면 정상적으로 nb-logger 모듈에 있는 Hello 함수를 실행했다는 것을 알 수 있다.

$ go run hello.go 
Hi, test. Wellcome!

 

에러 처리

에러 처리는 견고한 모듈을 만들기 위한 필수적인 사항이다. 적절한 에러처리를 통해 유효성 체크를 하지 않으면 모듈 내부에서 패닉이 나거나 더 심한 경우 패닉도 없는 상태로 이상한 방향으로 프로그램이 흘러갈 수 있다. 그렇기 때문에 모듈 작성자는 최대한 예상가능한 유효성 체크를 통해서 모듈 사용자에게 적절한 응답을 할 수 있게 해야된다.

 

코드를 통해서 알아보자 아까 만들었던 logger.go 를 수정해보자.

Hello 함수를 수정하여 string 과 error 를 같이 리턴하게 변경했다. 그리고 name 파라메터에 비어있는 스트링이 왔을 경우, 빈 스트링과 error 를 리턴하고, 그렇지 않으면 기본적인 동작과 error 를 nil 로 리턴한다.

리턴시 ({type}, error) 구조는 golang 전반적으로 많이 쓰이는 구조이니 알아두면 좋다.

package nblogger

import (
	"errors"
	"fmt"
)

func Hello(name string) (string, error) {
	if name == "" {
		return "", errors.New("empty name")
	}

	message := fmt.Sprintf("Hi, %v. Wellcome!", name)
	return message, nil
}

 

모듈을 고쳤으니 repo 에 올려야되는데 아까와 다른 버전으로 올려야지 hello 프로젝트에서 변경된 모듈을 가져갈 수 있다.그래서 아까와는 다르게 tag를 붙이고 해당 태그를 통해 퍼블리시 하면된다. ($ git push origin --tags 는 추천하지 않음)

$ git tag v0.0.1
$ git push origin
$ git push origin v0.0.1

 

hello 프로젝트로 가서 새로 변경된 nblogger를 받아보자. 기존 repository path 에 @{version} 을 붙이면 해당 버전을 가져올 수 있다. 아래 보면 기존 버전이 v0.0.1 로 바뀐 것을 볼 수 있다.

$ go get github.com/banaconda/nb-logger@v0.0.1
go: downloading github.com/banaconda/nb-logger v0.0.1
go: upgraded github.com/banaconda/nb-logger v0.0.0-20220702111429-9594bb378c3c => v0.0.1
$ cat go.mod 
module hello

go 1.18

require github.com/banaconda/nb-logger v0.0.1 // indirect

 

이제 다시 코드를 보면 아래 처럼 Hello 함수에 에러가 나고 있는 것을 볼 수 있다. 기존에는 Hello 함수가 string 만 리턴했는데 이젠 error 도 같이 리턴하기 때문에 코드를 수정해주어야 한다.

hello.go 파일을 수정해주자. error 처리를 해주고, 빈 string 도 넣어보면서 테스트 해보자

package main

import (
	"fmt"

	nblogger "github.com/banaconda/nb-logger"
)

func main() {
	message, err := nblogger.Hello("test")
	if err != nil {
		fmt.Printf("%v", err)
		panic(err)
	}
	fmt.Println(message)

	message, err = nblogger.Hello("")
	if err != nil {
		fmt.Printf("%v", err)
		panic(err)
	}
	fmt.Println(message)
}

 

실행하면 아래 와 같이 첫 문장만 잘 출력하고, 두 번 째는 panic 이 생긴 것을 알 수 있다. 

$ go run hello.go 
Hi, test. Wellcome!
empty namepanic: empty name

goroutine 1 [running]:
main.main()
        /home/nem/project/hello/hello.go:20 +0x150
exit status 2

 

테스팅

에러 처리와 함께 테스트 코드는 모듈을 더욱 견고하게 만들어준다. 추후 변경 사항에 대해서 자동으로 테스트 할 수 있고, 사용법에 대한 예제가 될 수도 있기 때문이다. 그래서 모듈의 안정성을 올릴 수 있다.

테스트 파일은 {package_name}_test.go 규칙으로 만들고 go test 명령으로 테스트 할 수 있다.

 

먼저 logger_test.go 파일을 만들어보자

package nblogger

import (
	"regexp"
	"testing"
)

func TestHello(t *testing.T) {
	name := "Test"
	want := regexp.MustCompile(`\b` + name + `\b`)
	msg, err := Hello("Test")

	if !want.MatchString(msg) || err != nil {
		t.Fatalf(`Hello("Test") = %q, %v, want match for %#q, nil`, msg, err, want)
	}
}

func TestEmpty(t *testing.T) {
	msg, err := Hello("")
	if msg != "" || err == nil {
		t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
	}
}

 

이제 테스트를 수행해보면 테스트가 정상적으로 성공한 것으로 보인다.

$ go test -v
=== RUN   TestHello
--- PASS: TestHello (0.00s)
=== RUN   TestEmpty
--- PASS: TestEmpty (0.00s)
PASS
ok      github.com/banaconda/nb-logger  0.002s

 

결론

정리하면 모듈을 만들고 관리하기 위해서는 go 모듈을 위해서는 아래 항목들을 고려해야된다.

  • 모듈을 배포할 github 같은 repository
  • 버전 관리
  • 에러 처리
  • 테스트 코드

참고

https://go.dev/doc/tutorial/create-module