Go 테스트 - 어떤 항목이 호출되었는지 확인하는 방법은 무엇입니까?

11993 단어 gotestingbeginners
안녕하세요, 고퍼들

저는 친구와 이야기를 나누며 Golang에서 여행을 시작할 때 직면했던 몇 가지 어려움을 기억하고 있었습니다. 컨텍스트를 위해 우리 둘 다 .toHaveBeenCalled() , toHaveBeenCalledTimes(number) .toHaveBeenCalledWith(...) 등과 같은 메서드를 제공하는 유명한 Jest와 같은 일부 테스트 프레임워크가 있는 땅인 NodeJS 배경에서 왔습니다.

반면에 Go 세계에서 기본testing 패키지는 (거의) 모든 단위 테스트 시나리오에 적합하지만 무언가가 호출되었는지 확인하는 것과 같은 작업을 수행하는 명확한 방법을 제공하지 않으며 플롯 트위스트는 다음과 같습니다. 그럴 필요가 없습니다. Go는 이와 같은 작업을 수행하는 훌륭한 관용적 방법을 제공하므로 프레임워크를 사용할 필요가 없습니다.

좋아, 하지만 "Go idiomatic way"에서 호출된 항목을 확인하는 방법은 무엇입니까?



대답은 간단합니다. 인터페이스와 종속성 반전을 사용하는 것입니다!

종속성 역전(Dependency Inversion)에 대해 조금 이야기해 보겠습니다.

캡슐화의 모범 사례로 코드에서 가능할 때마다 인터페이스를 사용하여 유형을 추상화하는 것이 좋습니다. 예를 들어 다음과 같이 특정 품종의 개를 나타내는 구조체를 정의한다고 가정합니다.

package dogs

type DogLhasa struct {
    Name string
} 

func (d *DogLhasa) Bark() string {
    return "woof-AUAU"
}

type DogRotweiler struct {
    Name string
}

func (d *DogRotweiler) Bark() string {
    return "woof-woof-ARHHHHHGG"
}


그리고 인수로 전달된 개의 Bark() 메서드를 호출하는 함수가 있다고 가정합니다. 문제는 이 함수를 정의하는 방법입니다. 인터페이스를 사용하지 않고 다음과 같이 이상한 작업을 수행해야 합니다.

func MakeDogLhasaBark(dog DogLhasa) string {
    return dog.Bark()
}

func MakeDogRotweilerBark(dog DogRotweiler) string {
    return dog.Bark()
}


이것이 잘 확장되지 않는 것이 분명합니다. 코드에 추가하려는 모든 개 빵에 대해 이와 같은 함수를 작성해야 한다면 얼마나 엉망이 될지 상상해 보십시오.
이 문제를 해결하려면 인터페이스로 작업해야 합니다. 우리는 개가 무엇이어야 하는지를 정의하는 인터페이스를 정의하고 개를 다루는 모든 코드는 이 인터페이스를 중계할 수 있습니다.

type Dog interface {
    Bark() string
}

func MakeDogBark(dog Dog) string {
    return dog.Bark()
}


이제 MakeDogBark 를 호출할 때 인터페이스 Dog 를 구현하는 일부 구조체 유형의 객체를 전달할 수 있습니다.

func main() {
    dog := &DogLhasa{Name: "Rex"}
    MakeDogBark(dog)
}


훨씬 간단하지 않습니까? 이를 종속성 역전이라고 합니다. 일부 특정 dog 구조체에 의존하는 대신 함수MakeDogBark는 구조체가 Dog가 되기 위해 구현해야 하는 것을 정의하는 인터페이스Dog에 의존합니다. 따라서 MakeDogBark 함수는 인터페이스Dog를 구현하는 모든 구조체의 모든 개체를 수락할 수 있습니다. 종속성 반전의 시각적 표현을 확인하세요.



그러나 이것이 toHaveBeenCalled.* 테스트와 같은 테스트를 구현하는 데 정확히 어떻게 도움이 될 수 있습니까?

함수MakeDogBark에 대한 단위 테스트를 작성해 보겠습니다. 이 테스트에서는 MakeDogBark가 호출될 때 인수로 전달된 Barkdog 메서드가 호출되는지 확인하려고 합니다. 이를 위해 메서드Bark가 호출된 횟수를 추적할 수 있는 "가짜 개"구조체를 만들 수 있습니다. 이와 같이:

type fakeDog struct {
    numberOfBarks int
}

func (d *fakeDog) Bark() string {
    d.numberOfBarks++
    return "woof"
}


이렇게 하면 numberOfBarks 유형의 개체가 짖을 때마다 변수fakeDog가 증가합니다. 따라서 Bark 메서드가 호출되었는지 테스트하려면 numberOfBarks가 0보다 큰지 확인해야 합니다.

func TestMakeDogBark(t *testing.T) {
    dog := &fakeDog{}

    MakeDogBark(dog)

    // fail if the dog didn't bark
    if dog.numberOfBarks == 0 {
        t.Errorf("Expected dog to bark once, but it barked %d times", dog.numberOfBarks)
    }
}


유사한 휴리스틱을 사용하여 toHaveBenCalled.* 테스트의 변형을 달성할 수 있습니다. 예를 들어:

toHaveBeenCalledWith

type fakeStruct struct {
    calledWith string
}

func (f *fakeStruct) SomeMethod(s string) {
    f.calledWith = s
}

func TestSomeMethod(t *testing.T) {
    f := &fakeStruct{}
    f.SomeMethod("hello")
    if f.calledWith != "hello" {
        t.Errorf("Expected SomeMethod to be called with 'hello', but was called with '%s'", f.calledWith)
    }
}


toHaveBeenCalledTimes

type fakeStruct struct {
    calledTimes int
}

func (f *fakeStruct) SomeMethod() {
    f.calledTimes++
}

func TestSomeMethod(t *testing.T) {
    f := &fakeStruct{}

    f.SomeMethod()
    f.SomeMethod()
    f.SomeMethod()

    if f.calledTimes != 3 {
        t.Errorf("Expected f.calledTimes to be 3, but it was %d", f.calledTimes)
    }
}


꽤 멋지죠? 타사 프레임워크나 라이브러리 없이 네이티브 항목만 사용하여 Golang으로 얼마나 많은 것을 달성할 수 있는지 정말 놀랍습니다.

행복한 테스트! 🧪

좋은 웹페이지 즐겨찾기