ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 11장 테스트
    카테고리 없음 2019. 11. 25. 20:54
    The Go Programming language

    개요

    오류를 찾는 방법은 동료 검토, 테스트 방식이다.

    암묵적으로 자동화된 테스트를 의미하는 테스팅은 테스트 대상 코드(프로덕션 코드)가 특정한 입력에 대해 의도한 대로 동작하는지 확인하는 작은 프로그램을 작성하는 것으로, 보통 입력은 특정 기능을 수행하기 위해 신중하게 선택된 값이거나 넓은 범위를 테스트하기 위한 임의의 값이다.

    Go의 테스트는 go test 명령어로 실행할 수 있는 테스트 함수 작성 규칙에 의존한다.

    테스트 코드를 작성하는 것은 작업의 한 부분에 초점을 맞춘 짧은 함수를 작성하면 된다.

     

    11.1 go test 도구

    _test.go로 끝나는 파일은 go test로 빌드된다.

    _test.go 파일 안에는 Test, Benchmark, Example 함수가 특별하게 취급된다.

    Test로 시작하는 테스트 함수는 일부 프로그램 로직이 정확하게 동작하는지 실행해본다.

    Benchmark로 시작하는 벤치마크 함수는 일부 동작의 성능을 측정한다.

    Example로 시작하는 예제 함수는 기계가 확인한 문서를 제공한다.

     

    11.2 Test 함수 

    각 테스트 파일은 testing 패키지를 임포트해야 한다.

    테스트 함수는 다음과 같은 시그니처 값을 갖는다.

    func TestName(t *testing.T) { 
    	// ...
    }

    테스트 함수명은 Test로 시작해야 한다. 

    부가적인 접미사는 대문자로 시작해야 한다.

    func TestSin(t *testing.T) { /* ... */ } 
    func TestCos(t *testing.T) { /* ... */ } 
    func TestLog(t *testing.T) { /* ... */ }

    파라미터 t에는 테스트 실패를 보고하고 부가적인 정보를 기록하는 메소드가 있다.

     

    인자가 없는 go test 명령은 현재 디렉토리의 패키지를 대상으로 동작한다.

    -v 플래그는 패키지 안의 테스트 이름과 실행 시간을 각각 출력한다.

    -run 플래그는 go test에서 인자 패턴과 함수명이 일치하는 것만 실행하게 한다.

     

    테이블로 작성된 테스트 케이스가 있다.

    func TestIsPalindrome(t *testing.T) { 
    	var tests = []struct {
    	input string
    	want bool 
      }{
          {"", true},
          {"a", true},
          {"aa", true},
          {"ab", false},
          {"kayak", true},
          {"detartrated", true},
          {"A man, a plan, a canal: Panama", true}, 
          {"Evil I did dwell; lewd did I live.", true}, 
          {"Able was I ere I saw Elba", true},
          {"été", true},
          {"Et se resservir, ivresse reste.", true}, // non-palindrome 
          {"palindrome", false}, {"desserts", false}, // semi-palindrome
      }
      for _, test := range tests {
          if got := IsPalindrome(test.input); got != test.want {
              t.Errorf("IsPalindrome(%q) = %v", test.input, got) 
          }
      } 
    }

    테스트 실패 메시지는 보통 "f(x) = y, want z"의 형식이며 f(x)는 시도한 동작과 그 입력이고 y는 실제 결과이며 z는 예상된 결과다.

     

    주의점

    공통 및 중복된 정보를 피하라.

    불리언 함수를 호출할 때는 정보가 없는 want z 부분을 생략하라.

    x, y, z가 길다면 관련 부분에 간결한 요약을 출력하라.

    테스트 작성자는 테스트 실패를 파악해야 하는 개발자를 돕기 위해 노력해야 한다.

    11.2.1 무작위 테스트

    무작위 테스트는 임의의 입력을 구성해 더 넓은 범위의 입력을 조사한다.

    임의의 입력이 주어졌을 때 기댓값은 2가지 방법이 있다.

    1) 별도의 함수를 구현해 두 구현에서 같은 결과를 출력하는지 확인한다.

    2) 입력 값을 패턴에 따라 생성해 예상 결과 값을 사전에 알 수 있게 한다.

    11.2.2 명령 테스트

    go test 도구는 라이브러리 패키지를 테스트하기에 유용하지만 약간의 노력으로 명령을 테스트하기 위해서도 사용할 수 있다.

    11.2.3 화이트박스 테스트

    블랙박스 테스트는 패키지에 대해 API와 문서로 노출된 것 외에는 아무런 지식이 없다고 가정한다. 패키지 내부는 불투명하다.

    화이트박스 테스트는 패키지의 내부 함수와 데이터 구조에 대한 특별한 권한을 갖고 있어서 일반 사용자가 접근할 수 없는 부분을 관찰하고 변경할 수 있다.

    두 방법은 상호보완적이다.

    11.2.4 외부 테스트 패키지

    외부 테스트는 별도의 패키지에 있으므로 테스트 대상 패키지가 의존하는 도우미 패키지도 임포트할 수 있다.

    외부 테스트 패키지는 의존성의 순환을 피해 테스트할 수 있으며, 특히 여러 컴포넌트 간 상호작용을 테스트하는 통합 테스트에서 애플리케이션에서와 마찬가지로 다른 패키지를 자유롭게 임포트할 수 있다.

     

    go list 도구를 통해 패키지 디렉토리 내의 Go 소스 파일을 프로덕션 코드, 패키지 내 테스트, 외부 테스트로 요약할 수 있다.

    11.2.5 효율적인 테스트 작성

    테스트는 유일한 사용자가 관리자일지라도 사용자 인터페이스가 있다.

    좋은 테스트는 실패 시 망가지는 대신 문제의 증상에 대해 명확하고 간결한 설명을 출력하며 아마도 해당 문맥에 관련된 다른 내용도 같이 출력할 것이다.

    좋은 테스트는 최초의 실패에 포기하지 않고 한 번의 실행에서 여러 오류를 보고해야 하며 이는 실패의 패턴이 스스로 드러날 수 있기 때문이다.

    코드를 간결하게 할 수 있다면 부가적인 함수를 계속해서 만들어야 한다.

    좋은 테스트의 핵심은 먼저 원하는 구체적인 동작을 구현한 뒤에만 함수를 사용해 코드를 간결하게 하고 중복을 제거하는 것이다.

    11.2.6 불안정한 테스트 방지

    프로그램에 적절한 변경이 이뤄졌는데도 실패하는 테스트 프로그램을 불안정하다(brittle)고 한다.

    11.3 커버리지

    테스트를 많이 하더라도 패키지에 버그가 없다고 증명할 수는 없다.

    테스트에서 얻을 수 있는 최대 성과는 패키지가 다양한 핵심 시나리오에서 잘 동작한다는 신뢰다.

    테스트들에서 대상 패키지에 테스트하는 정도를 테스트 커버리지(coverage)라 한다.

    구문 커버리지(statement coverage)는 가장 간단하며 테스트 중 최소 한 번 이상 실행되는 소스 문장의 일부이다.

    go test에 통합돼 있는 Go의 cover 도구를 사용해 구문 커버리지를 측정하고 테스트에 있는 명백한 빈틈을 식별한다.

    11.4 Benchmark 함수

    벤치마킹은 고정된 부하가 발생할 때의 프로그램 성능을 측정하는 방법이다.

    Go의 벤치마크 함수는 테스트 함수처럼 보이지만 Benchmark 접두사 및 *testing.T에 있는 메소드의 대부분과 일부 성능 측정에 관련된 추가 메소드를 제공하는 *testing.B 파라미터를 갖는다. 이 파라미터는 측정하는 동작을 몇 번이나 수행할지 지정하는 정수 필드 N도 노출한다.

    import "testing"
    func BenchmarkIsPalindrome(b *testing.B) { 
    	for i := 0; i < b.N; i++ {
    		IsPalindrome("A man, a plan, a canal: Panama") 
        }
    }

    다음 명령어로 실행한다.

    cd $GOPATH/src/gopl.io/ch11/word2
    $ go test -bench=.
    PASS
    BenchmarkIsPalindrome-8 1000000 1035 ns/op 
    ok gopl.io/ch11/word2 2.179s

    -bench 플래그의 인자로 실행할 벤치마크를 선택한다.

    '.' 패턴은 word 패키지의 모든 벤치마크와 일치한다.

    8은 동시에 벤치마크할 때 중요한 GOMAXPROCS의 값이다.

    1,000,000회는 IsPalindrome 실행에서 각 호출에 평균 1.035ms 정도의 시간이 걸렸다고 알려준다.

     

    -benchmem 플래그는 보고서에 메모리 할당 통계를 포함한다.

    11.5 프로파일

    프로그램의 속도를 세부적으로 살펴보고자 할 때 결정적인 코드를 식별하기 위한 최선의 방법은 프로파일링이다.

    프로파일링은 실행 중 프로파일 이벤트를 일부 수집하고 후처리 과정에서 외삽(예측)해 성능을 측정하는 자동화된 기법이다.

    프로파일링의 결과인 통계적 요약본을 프로파일이라 한다.

     

    CPU 프로파일은 가장 많은 CPU 시간을 필요로 하는 함수를 식별한다.

    heap 프로파일은 가장 많은 메모리를 할당한 구문을 식별한다.

    차단 프로파일은 고루틴을 가장 오래 대기시킨 작업을 식별한다.

     

    프로파일을 수집한 후에는 pprof 도구로 분석해야 한다.

    go tool pprof를 통해 간접적으로 접근한다.

     

    go test는 일반적으로 테스트가 완료되고 나서 테스트 실행 파일을 폐기하지만 프로파일링이 활성화돼 있는 경우에는 foo가 테스트되는 패키지의 이름일 때 실행 파일을 foo.test로 저장한다.

    11.6 Example 함수

    func ExampleIsPalindrome() {
      fmt.Println(IsPalindrome("A man, a plan, a canal: Panama")) 
      fmt.Println(IsPalindrome("palindrome"))
      // Output:
      // true
      // false
    }

    Example 함수에는 3가지 목적이 있다.

    1) 문서화다. 실제 Go 코드이며, 컴파일 시 확인 대상이므로 코드 진화에 따라 최신 상태로 유지된다.

    2) go test로 실행 가능한 테스트를 제공하는 것이다. // Output: 주석이 있으면 테스트 드라이버가 이 함수를 실행하고 표준 출력의 결과가 주석 안의 문자열과 일치하는지 확인한다.

    3) 직접적인 실험이다. golang.org의 godoc 서버는 Go 플레이그라운드를 사용해 사용자가 각 Example 함수를 웹 브라우저에서 직접 수정하고 실행할 수 있게 한다.

     

     

     

     

     

Designed by Tistory.