> 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** 그리고 **it** jest가 제공하는 메서드들

## 컨텍스트 설명 및 구성

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

```tsx
describe('when the flag is red', () => {
	...
})

describe('when the flag is blue', () => {
	...
})
```

컨텍스트를 설명하기 위해 다음 단어들 중 하나를 사용해야 합니다: **when** 최상위 describe에는 그리고 **having** 또는 **및** 앞서 정의된 다른 컨텍스트에 의존하는 컨텍스트가 있음을 나타내기 위해 사용합니다. 그 근거는 describe 문들을 조합함으로써 쉽게 이해하고 확인할 수 있는 자기 설명적인 컨텍스트를 갖기 위함입니다. 아이디어는 컨텍스트 설명들을 이어 붙이는 것이므로, **describe는** 소문자로 작성해야 합니다.

```tsx
describe('when the flag is red', () => {
	...
	describe('and the wind is strong', () => {
     // 여기서의 컨텍스트는 describe들의 결합으로 설명됩니다:
     // "when the red flag is red and the wind is strong"
     ...
  })
})
```

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

```tsx
describe('when the flag is red', () => {
	...
	describe('and the wind is strong', () => {
     ...
  })

	describe('and the wind is weak', () => {
     ...
  })
})
```

앞서 언급했듯이, 각 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('when the flag is red', () => {
	let wind = 'mild'
	beforeEach(() => {
    flag = 'red'
  })
  ...

	describe('and the wind is strong', () => {
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('and the wind is weak', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
```

다음의 변수들: **기본 타입**는 **변경되지 않을** 컨텍스트나 이후의 컨텍스트에서 정의되어야 하며 **const** 한 번만, 스코프 내의 **beforeEach** 정의보다 우선하여 사용되어야 합니다.

다음의 변수들: **기본 타입들** 컨텍스트에서 변경되거나 이후의 컨텍스트에서 변경될 변수는 **let**으로 정의되어야 합니다. 이는 TypeScript의 특성상 그렇게 해야 하기 때문입니다.

다음의 변수들: **비기본 타입들** (객체, 배열 등)은 **let** 으로 정의되어야 하며 그 값은 **beforeEach에서 설정되어야 합니다.** 그 근거는 JS의 객체가 변경 가능하다는 점입니다. 즉, 그 객체를 사용하는 코드의 모든 실행은 변경의 영향을 받을 수 있으며 다른 테스트에도 의도치 않게 영향을 줄 수 있습니다.

```tsx
let flag
describe('when the flag is red', () => {
  // 여러 스코프에서 수정될 기본 값.
	let wind
  // 테스트 코드에서 사용될 객체 값.
  let someObject
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1 }
  })
  ...

	describe('and the wind is strong', () => {
		// 이 스코프에서만 사용될 상수 기본 값.
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('and the wind is weak', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
...
```

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

```tsx
let flag: string
describe('when the flag is red', () => {
  // 여러 스코프에서 수정될 기본 값.
	let wind: string
  // 테스트 코드에서 사용될 객체 값.
  let someObject: { id: string }
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1 }
  })
  ...

	describe('and the wind is strong', () => {
		// 이 스코프에서만 사용될 상수 기본 값.
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('and the wind is weak', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
...
```

## 기대 사항 설명하기와 코드 실행하기

그 **it** 메서드는 항상 **describe** 의 범위 안에 있어야 하며, 우리가 테스트할 코드에서 무엇을 기대하는지 설명하는 데 사용되어야 합니다. 가능하다면 **하나의** 어설션만 포함해야 합니다. 각 **it** 마다 여러 어설션이 있을 수 있는 경우는 성능 문제가 있을 때뿐입니다. 테스트는 각 **it** (의 이유로 한 번 실행되기 때문입니다. **beforeEach**), 또는 기대되는 바를 기대 설명에서 명확하게 설명할 수 있을 때입니다.

```tsx
let flag: string
describe('when the flag is red', () => {
	let wind: string
  let someObject: { id: string }
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1, swim: jest.fn() }
  })
  ...

  // 컨텍스트에서 테스트 실행에 대한 단일 기대(하나의 it)
	describe('and the wind is strong', () => {
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    
		it('should not go swimming', () => {
      expect(goSwimming(flag, wind, someObject)).toBe(false)
    })
  })

  // 컨텍스트에서 테스트 실행에 대한 여러 기대(두 개의 it)
	describe('and the wind is weak', () => {
    let result: boolean
		beforeEach(() => {
	    wind = 'weak'
      result = goSwimming(flag, wind, someObject)
	  })

		it('should go swimming', () => {
      expect(goSwimming(flag, wind, someObject)).toBe(true)
    })

    it('should have called the swim method', () => {
      expect(someObject.swim).toHaveBeenCalled()
    })
  })
})
...
```

이 구조의 근거는 각 컨텍스트와 기대 사항이 명확하게 열거되므로, 테스트를 검토하는 사람과 코드를 유지보수하고 변경할 개발자에게 명확성을 제공하기 위함입니다. 이렇게 하면 무엇이 테스트되는지, 어떻게 테스트되는지, 그리고 무엇이 아직 테스트되지 않았는지를 더 쉽게 이해할 수 있습니다.

따라서 **describe** 설명을 **it**s까지 따라가면 개발자들은 문장을 이어 붙여 무엇이 테스트되고 있는지 쉽게 이해할 수 있습니다.

```tsx
// 이는 다음과 같이 읽습니다: "when the flag is red and the wind is strong, 
// it should go swimming"
describe('when the flag is red', () => {
  ...
  describe('and the wind is strong', () => {
		...
		it('should go swimming', () => {
			...
		})
  })
})
```

### 명확한 기대 사항 작성하기

작성된 기대 사항의 설명은 **it**s에서 테스트 실행으로부터 무엇을 기대하는지에 대해 가능한 한 설명적이어야 합니다. 개발자는 기대 사항을 정의할 때 추상적이거나 일반적인 표현을 사용해서는 안 됩니다. 그러한 표현은 코드에서 무엇을 기대하는지에 대한 명확성을 떨어뜨리기 때문입니다.

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

* "should work as expected" ⇒ 무엇이 기대대로 동작해야 하나요?
* "should return the correct value" ⇒ 올바른 값은 무엇인가요?
* "should resolve/return/work correctly" ⇒ 어떻게 올바르게 동작하나요?
* "should fail" ⇒ 어떻게 실패해야 하나요? 제공해야 하는 메시지는 무엇인가요?
  {% endhint %}

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

```tsx
// math.ts
export function div(a: number, b: number): number {
	if(b === 0)	{
		throw new Error('The divisor b equals 0, the division can\'t be performed')
	}
}

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

describe('when dividing by zero', () => {
  // it의 설명은 예외 메시지의 의도를 설명합니다
	it('should throw an exception signaling that a division by 0 is not possible', () => {
		expect(() => div(12, 0)).toThrowError('The divisor b equals 0, the division can\'t be performed')
	})
})
```

## 무엇을 테스트할지

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

```tsx
function run(kilometers: number): number {
	if(kilometers > 1000) {
    throw new Error('The runner can\'t run more than 1000 kilometers')
  }

  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 함수가 예외를 던지는지 테스트해야 합니다. **예외는 반드시 오류 메시지로 확인해야 합니다.**&#xB2E4;른 예외도 던져질 수 있어 테스트의 목적이 무효화될 수 있기 때문입니다. 예외가 커스텀 예외라면 개발자는 그 예외의 인스턴스를 확인해도 됩니다.

두 번째 사례에서는 함수가 입력받은 것과 동일한 킬로미터 수를 반환하는지 테스트해야 합니다.

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

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

// main describe가 없다는 점에 주의하세요
// 유일한 전역 컨텍스트는 jest로 모킹하려는 함수들입니다

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

describe('when running more than 1000 kilometers', () => {
	it('should throw an error signaling that the runner can\'t run more than 1000 kilometers', () => {
		expect(() => run(1001)).toThrowError("The runner can't run more than 1000 kilometers")
	})
})

describe('when running less or equal than 10', () => {
	it('should return the same amount of kilometers as the ones given', () => {
		expect(run(2)).toEqual(2)
	})
})

describe('when running more than 10 kilometers but less than 1000', () => {
	const kilometers = 50
	let result: number
	beforeEach(() => {
		mockDoSomething.mockReturnValueOnce(undefined)
		mockBeLazy.mockImplementationOnce(value => value)
		result = run(kilometers)
	})
	
	it('should return the kilometers after being lazy', () => {
		expect(result).toEqual(kilometers)
	})

	it('should call the doSomething function with the given kilometers', () => {
		expect(mockDoSomething).toHaveBeenCalledWith(kilometers)
	})
})
```

## 언제 모킹할 것인가

모킹은 주로 개발자가 작성하는 테스트의 유형에 따라 달라집니다.

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

모든 모킹은 Jest가 제공하는 도구를 사용해 수행해야 하며, 다음은 예외입니다. `redux-saga-test-plan` 모킹.

### 모킹과 유틸리티 함수

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


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
