> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-ko/contributor-guides/testing-standards/writing-tests.md).

# 테스트 작성

테스트는 다양한 방식으로 작성할 수 있습니다. 이 섹션에서는 테스트를 작성하는 방식에 대한 표준과 그 근거를 제시하려고 합니다.

테스트는 반드시 다음을 사용하여 작성해야 합니다 **describe** 그리고 **테스트** jest가 제공하는 메서드

## 컨텍스트 설명 및 구성

이 **describe** 메서드는 가능하다면 테스트할 코드가 실행될 컨텍스트를 설명하는 데 사용해야 하며, 그 컨텍스트를 자신의 범위 안에서 구성해야 합니다.

```tsx
describe('플래그가 빨간색일 때', () => {
	...
})

describe('플래그가 파란색일 때', () => {
	...
})
```

컨텍스트를 설명하기 위해, 다음 단어 중 하나를 사용해야 합니다: **일 때** 최상위 describe와 **가지고 있는** 또는 **및** 그 이전에 정의된 다른 컨텍스트에 의존하는 컨텍스트가 있음을 나타내기 위해 사용합니다. 이에 대한 근거는 describe 문장을 결합하여 쉽게 이해하고 확인할 수 있는 자기 설명적인 컨텍스트를 갖기 위함입니다. 아이디어는 컨텍스트 설명을 연결하는 것이므로, **describe 구문은** 반드시 소문자로 작성해야 합니다.

```tsx
describe('플래그가 빨간색일 때', () => {
	...
	describe('바람이 강할 때', () => {
     // 여기서 컨텍스트는 describe들의 결합으로 설명됩니다:
     // "플래그가 빨간색이고 바람이 강할 때"
     ...
  })
})
```

여러 describe는 같은 수준과 더 낮은 수준에서 중첩될 수 있습니다. 같은 수준의 describe는 서로 다른 컨텍스트를 나타내고, 더 낮은 수준의 describe(중첩된 describe)는 더 구체적인 컨텍스트를 나타냅니다.

```tsx
describe('플래그가 빨간색일 때', () => {
	...
	describe('바람이 강할 때', () => {
     ...
  })

	describe('바람이 약할 때', () => {
     ...
  })
})
```

앞서 언급했듯이, 각 describe에는 컨텍스트를 구성하는 메서드나 컨텍스트를 파괴하거나 변경하는 메서드가 함께 있어야 합니다. 반드시 사용해야 하는 메서드는 다음과 같습니다: **beforeEach** 그리고 **afterEach**.

{% hint style="warning" %}
Jest 프레임워크에는 두 가지 메서드가 있습니다, **beforeEach** 및 **beforeAll**, 하지만 둘을 함께 사용하면 중첩된 컨텍스트가 있을 때 혼란과 실행 문제가 발생합니다. 왜냐하면 [실행되는 순서가](https://jestjs.io/docs/setup-teardown#scoping) 우리가 예상하는 바와 다르기 때문입니다.
{% endhint %}

다음으로 생성된 컨텍스트는 **beforeEach** 메서드를 사용할수록 describe 트리를 더 깊게 내려가며 점점 더 구체적으로 정의됩니다. 각 **beforeEach** 는 자신의 텍스트가 말하는 바에 따라 컨텍스트를 더 구체적으로 지정합니다.

### 변수 범위

이 **변수들** 컨텍스트에서 설정된 값은 사용될 범위에 맞게 정의되어야 합니다. 즉, 변수 하나가 describe의 중첩된 수준에서 특정 컨텍스트에만 사용된다면, 그 변수는 해당 특정 컨텍스트에만 정의되어야 합니다. 이에 대한 근거는 변수를 사용되는 코드 가까이에 두어 코드를 더 이해하기 쉽게 만들기 위함입니다.

```tsx
let flag = 'blue'
describe('플래그가 빨간색일 때', () => {
	let wind = 'mild'
	beforeEach(() => {
    flag = 'red'
  })
  ...

	describe('바람이 강할 때', () => {
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('바람이 약할 때', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
```

의 **원시 타입**, 즉 **변경되지 않을** 컨텍스트나 이후 컨텍스트에서 사용될 경우 **const** 를 사용하여 한 번, **beforeEach** 범위 내 정의보다.

의 **원시 타입들** 컨텍스트나 이후 컨텍스트에서 변경될 것은 다음과 같이 정의되어야 합니다: **let**이는 TypeScript의 특성상 필수입니다.

의 **비원시 타입** (객체, 배열 등)은 다음과 같이 정의되어야 합니다: **let** 그리고 그 값은 다음에서 설정되어야 합니다 **beforeEach.** 이에 대한 근거는 JS의 객체가 변경 가능(mutable)하다는 점입니다. 즉, 해당 객체를 사용하는 코드의 실행은 변경의 영향을 받을 수 있고, 의도치 않게 다른 테스트에 영향을 줄 수 있습니다.

```tsx
let flag
describe('플래그가 빨간색일 때', () => {
  // 여러 범위에서 수정될 원시 값입니다.
	let wind
  // 테스트 코드에서 사용될 객체 값입니다.
  let someObject
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1 }
  })
  ...

	describe('바람이 강할 때', () => {
		// 이 범위에서만 사용될 상수 원시 값입니다.
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('바람이 약할 때', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
...
```

변수는 가능하다면 올바른 타입으로 지정해야 합니다. 반복되는 타입은 타입으로 추상화해야 합니다.

```tsx
let flag: string
describe('플래그가 빨간색일 때', () => {
  // 여러 범위에서 수정될 원시 값입니다.
	let wind: string
  // 테스트 코드에서 사용될 객체 값입니다.
  let someObject: { id: string }
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1 }
  })
  ...

	describe('바람이 강할 때', () => {
		// 이 범위에서만 사용될 상수 원시 값입니다.
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('바람이 약할 때', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
...
```

## 기대 사항 설명과 코드 실행

이 **테스트** 메서드는 항상 하나의 **describe** 안에 배치되어야 하며, 우리가 테스트할 코드에서 무엇을 기대하는지 설명하는 데 사용되어야 하고, 가능하다면 오직 **하나의** 어설션만 포함해야 합니다. 각 **테스트** 당 여러 개의 어설션이 있을 수 있는 경우는 성능 문제가 있을 때입니다. 테스트는 각 **테스트** (때문에 **beforeEach**), 또는 기대되는 내용을 기대 설명에서 명확하게 설명할 수 있을 때입니다.

```tsx
let flag: string
describe('플래그가 빨간색일 때', () => {
	let wind: string
  let someObject: { id: string }
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1, swim: jest.fn() }
  })
  ...

  // 컨텍스트에서 테스트 실행에 대한 단일 기대(one it)
	describe('바람이 강할 때', () => {
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    
		it('수영하지 않아야 한다', () => {
      expect(goSwimming(flag, wind, someObject)).toBe(false)
    })
  })

  // 컨텍스트에서 테스트 실행에 대한 여러 기대(two its)
	describe('바람이 약할 때', () => {
    let result: boolean
		beforeEach(() => {
	    wind = 'weak'
      result = goSwimming(flag, wind, someObject)
	  })

		it('수영해야 한다', () => {
      expect(goSwimming(flag, wind, someObject)).toBe(true)
    })

    it('swim 메서드가 호출되어야 한다', () => {
      expect(someObject.swim).toHaveBeenCalled()
    })
  })
})
...
```

이 구조에 대한 근거는 테스트를 검토하는 사람과 코드를 유지보수하고 수정할 개발자에게 명확성을 제공하기 위함입니다. 각 컨텍스트와 기대가 명확하게 열거되므로, 무엇을 테스트하는지, 어떻게 테스트하는지, 그리고 무엇이 아직 테스트되지 않았는지 이해하기 쉬워집니다.

다음의 **describe** 설명을 따라 **테스트**가면 개발자는 문장을 연결하여 무엇이 테스트되는지 쉽게 이해할 수 있습니다.

```tsx
// 이는 다음과 같이 읽습니다: "플래그가 빨간색이고 바람이 강할 때, 
// 수영해야 한다"
describe('플래그가 빨간색일 때', () => {
  ...
  describe('바람이 강할 때', () => {
		...
		it('수영해야 한다', () => {
			...
		})
  })
})
```

### 명확한 기대 작성

다음에 작성된 기대 설명은 **테스트**는 테스트 실행에서 무엇을 기대하는지에 대해 가능한 한 가장 구체적이어야 합니다. 개발자는 기대 내용을 정의할 때 추상적이거나 일반적인 표현을 사용해서는 안 되며, 이는 코드에서 무엇이 기대되는지에 대한 명확성을 떨어뜨립니다.

{% hint style="danger" %}
**개발자는 다음과 같은 표현을 사용해서는 안 됩니다:**

* "기대한 대로 작동해야 한다" ⇒ 무엇이 기대한 대로 작동해야 하나요?
* "올바른 값을 반환해야 한다" ⇒ 올바른 값은 무엇인가요?
* "올바르게 resolve/return/work해야 한다" ⇒ 무언가가 어떻게 올바르게 작동하나요?
* "실패해야 한다" ⇒ 어떻게 실패해야 하나요? 어떤 메시지를 제공해야 하나요?
  {% endhint %}

무엇이 **테스트**에서 기대되는지 또는 컨텍스트가 무엇인지 지정할 때 **describe**에서 개발자는 의도를 더 잘 이해하기 위해 필요하다면 함수 이름이나 정확한 오류 메시지를 사용할 수 있지만, 테스트를 더 쉽게 유지보수할 수 있도록 가능한 경우 텍스트 표현을 사용해야 합니다.

```tsx
// math.ts
export function div(a: number, b: number): number {
	if(b === 0)	{
		throw new Error('분모 b가 0과 같아서 나눗셈을 수행할 수 없습니다')
	}
}

// test.spec.ts
import { div } from './math.ts'

describe('0으로 나눌 때', () => {
  // it의 설명은 예외 메시지의 의도를 설명합니다
	it('0으로 나눌 수 없음을 알리는 예외를 던져야 한다', () => {
		expect(() => div(12, 0)).toThrowError('분모 b가 0과 같아서 나눗셈을 수행할 수 없습니다')
	})
})
```

## 무엇을 테스트할 것인가

무엇을 테스트할지는 실행되는 코드에 따라 달라질 수 있습니다. 성능이나 입력 도메인의 크기와 같은 다양한 요인이 테스트에서 무엇을 테스트해야 하거나 하지 말아야 하는지를 분명히 바꿀 수 있습니다. 여기서는 가능하다면 개발자가 테스트해야 하는 하나의 케이스 집합을 제시합니다.

```tsx
function run(kilometers: number): number {
	if(kilometers > 1000) {
    throw new Error('주자는 1000킬로미터를 초과하여 달릴 수 없습니다')
  }

  if(kilometers <= 10) {
		return kilometers
  } else if(kilometers > 10) {
		doSomething(kilometers)
    return beLazy(kilometers)
  }
}
```

여기의 run 함수는 몇 가지 서로 다른 실행 흐름을 가지고 있습니다. 함수나 코드의 일부는 가능한 다양한 실행 흐름만을 기준으로 테스트되어서는 안 되며, 함수에서 기대되는 바를 기준으로 테스트되어야 합니다. 함수가 원래 목적대로 동작하지 않을 수도 있고, 테스트가 코드와 지나치게 결합되어 다른 문제를 잡아내지 못할 수도 있기 때문입니다. 이는 개발자가 항상 가능한 모든 실행 경로를 테스트해야 하며, 함수의 의미에 따라 다른 가능한 경로도 테스트해야 함을 뜻합니다.

이 특정 경우에는 개발자가 **적어도** 다음 사례들을 테스트해야 합니다:

1. 1000킬로미터를 초과하는 양의 킬로미터로 run 함수를 실행하는 경우
2. 10킬로미터와 같은 양으로 run 함수를 실행하는 경우
3. 10킬로미터보다 크지만 1000킬로미터보다 작은 양으로 run 함수를 실행하는 경우

첫 번째 경우에는 개발자가 run 함수가 예외를 던지는지 테스트해야 합니다. **예외는 반드시 오류 메시지로 확인해야 합니다**, 다른 예외도 던져질 수 있어 테스트의 목적을 무효화할 수 있기 때문입니다. 예외가 사용자 정의 예외라면, 개발자는 해당 예외의 인스턴스를 확인해도 됩니다.

두 번째 경우에는 함수가 전달받은 킬로미터 수와 동일한 값을 반환하는지 테스트해야 합니다.

세 번째 경우에는 함수가 외부 함수인 **beLazy** 가 반환한 값을 반환하는지 테스트해야 하며, **doSomething** (메서드에서도 실행됨)를 함수의 반환값을 통해 확인할 수 없으므로, 개발자는 **올바른 인수로 호출되었는지 테스트해야 합니다**.

```tsx
import { doSomething, beLazy } from '../runningUtils'
jest.mock('../runningUtils')

// main describe가 없다는 점에 주목하세요
// 유일한 전역 컨텍스트는 jest로 mock하고자 하는 함수들입니다

const mockDoSomething = doSomething as jest.MockedFunction<typeof doSomething>
const mockBeLazy = beLazy as jest.MockedFunction<typeof beLazy>

describe('1000킬로미터를 초과하여 달릴 때', () => {
	it('주자가 1000킬로미터를 초과하여 달릴 수 없음을 알리는 오류를 던져야 한다', () => {
		expect(() => run(1001)).toThrowError("주자는 1000킬로미터를 초과하여 달릴 수 없습니다")
	})
})

describe('10 이하로 달릴 때', () => {
	it('주어진 것과 같은 킬로미터 수를 반환해야 한다', () => {
		expect(run(2)).toEqual(2)
	})
})

describe('10킬로미터보다 크지만 1000킬로미터보다 작게 달릴 때', () => {
	const kilometers = 50
	let result: number
	beforeEach(() => {
		mockDoSomething.mockReturnValueOnce(undefined)
		mockBeLazy.mockImplementationOnce(value => value)
		result = run(kilometers)
	})
	
	it('지친 뒤의 킬로미터 수를 반환해야 한다', () => {
		expect(result).toEqual(kilometers)
	})

	it('주어진 킬로미터와 함께 doSomething 함수가 호출되어야 한다', () => {
		expect(mockDoSomething).toHaveBeenCalledWith(kilometers)
	})
})
```

## 무엇을 mock할 것인가

무엇을 mock할지는 주로 개발자가 작성하는 테스트의 유형에 따라 달라집니다.

* 단위 테스트는 문자열 포맷팅 같은 단순한 작업을 수행하는 함수 등을 제외하고 모든 외부 함수를 mock해야 합니다.
* API 테스트는 외부 서비스와 통신하는 mock만 있어야 합니다. 즉 DB나 다른 API입니다. 테스트에서 수행되는 작업 중 하나가 테스트 스위트의 성능에 영향을 준다면, 이 문제를 완화하기 위해 mock을 구현할 수 있습니다.

모든 mock은 Jest가 제공하는 도구를 사용해 만들어야 합니다. 단, `redux-saga-test-plan` mock들.

### Mock과 유틸리티 함수

* 모든 Jest mock은 가능하다면 다음의 **once** 메서드를 사용해 mock해야 합니다. 즉, `mockReturnValueOnce` 또는 `mockResolvedValueOnce` 는 권장되지 않습니다 `mockReturnValue` 또는 `mockResolvedValue`. 이에 대한 근거는 원치 않는 mock이 실행되어 테스트의 실행을 바꾸는 것을 방지하기 위함입니다.
* Jest는 다양한 종류의 mock에 사용할 수 있는 여러 타입을 제공합니다. 예를 들어 객체를 mock할 때는 `jest.Mocked<typeof someObject>`, 클래스를 mock할 때는 `jest.MockedClass<typeof SomeClass>`, 함수를 mock할 때는 `jest.MockedFunction<typeof someFunction>`.
* 하나의 `afterEach` 는 각 테스트가 끝난 뒤 실행되도록 작성해야 하며, `jest.resetAllMocks` 을 사용해 전역적으로 mock된 모듈의 mock 구현이 다른 테스트로 새어 나갈 수 있는 것을 지워야 합니다. 다음을 사용하고 있다면 이 초기화는 무시해도 됩니다. `mockReturnValueOnce` 또는 `mockResolvedValueOnce` 자동으로 처리되기 때문입니다.
* 다음과 같은 디렉터리를 만들 수 있습니다: `mocks` mock을 저장하기 위해 만들 수 있으며, 이들은 `test` 또는 `spec` 디렉터리에 배치해야 합니다. 큰 mock은 확장된 테스트 파일을 피하기 위해 테스트와 다른 파일에 저장해야 합니다. 작은 mock은 많지 않다면 테스트 안에 두는 것이 좋습니다.
  * Mock 파일은 mock하는 대상의 이름으로 이름 짓는 것이 좋습니다. 예를 들어 프로필을 mock한다면, mock은 다음 아래에 두어야 합니다. `/test/mocks/profile.ts` 파일.
  * 여러 개의 큰 mock이 필요하다면, mock할 대상의 이름을 딴 디렉터리 안에 각각 다른 파일로 두어야 합니다. 예를 들어 프로필 mock이 두 개 있다면 다음과 같이 저장합니다: `/test/mocks/profile/profile-with-wearables.ts` 및 `/test/mocks/profile/profile-without-wearables.ts`. 쉽게 접근할 수 있도록, `index.ts` 파일을 다음 안에 만들고 `/test/mocks/profile/` 디렉터리에서 내보내야 합니다.
  * mock된 객체의 변이를 피하기 위해, mock은 **함수로 내보내야 하며** 그 함수가 그것을 반환하도록 해야 합니다.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/contributor/contributor-ko/contributor-guides/testing-standards/writing-tests.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
