# GitHub Copilot을 사용하여 테스트 작성

Copilot을 사용하여 단위 및 통합 테스트를 생성하고 코드 품질을 개선합니다.

## 소개

GitHub Copilot은 테스트를 빠르게 개발하고 생산성을 향상시키는 데 도움이 될 수 있습니다. 이 문서에서는 Copilot을 사용하여 단위 및 통합 테스트를 모두 작성하는 방법을 설명합니다. Copilot은 기본 함수에 대한 테스트를 생성할 때 잘 수행되지만 복잡한 시나리오에는 좀 더 자세한 프롬프트와 전략이 필요합니다. 이 문서에서는 Copilot을 사용하여 작업을 분석하고 코드 정확성을 확인하는 실제 예제를 안내합니다.

## 필수 조건

시작하기 전에 다음을 확인해야 합니다.

* [GitHub Copilot 구독 플랜](/ko/copilot/about-github-copilot/subscription-plans-for-github-copilot).
* Visual Studio, Visual Studio Code 또는 JetBrains IDE입니다.
* IDE에 [GitHub Copilot 확장 프로그램](/ko/copilot/managing-copilot/configure-personal-settings/installing-the-github-copilot-extension-in-your-environment)이 설치되어 있어야 합니다.

## 공동 파일럿 채팅를 사용하여 단위 테스트 작성

이 섹션에서는 깃허브 코파일럿 채팅을 사용하여 Python 클래스에 대한 단위 테스트를 생성하는 방법을 살펴봅니다. 이 예제에서는 Copilot을 사용하여 클래스 `BankAccount`에 대한 단위 테스트를 만드는 방법을 보여 줍니다. Copilot을 프롬프트하여 테스트를 생성하고, 실행하고, 결과를 확인하는 방법을 보여 줍니다.

### 예제 클래스: `BankAccount`

계정의 잔액을 입금, 인출 및 가져오는 방법을 포함하는 클래스 `BankAccount`부터 시작해 보겠습니다. GitHub 리포지토리에 새 파일 `bank_account.py` 만들고 Python에서 다음 `BankAccount` 클래스를 추가합니다.

```python
class BankAccount:
    def __init__(self, initial_balance=0):
        if initial_balance < 0:
            raise ValueError("Initial balance cannot be negative.")
        self.balance = initial_balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit amount must be positive.")
        self.balance += amount

    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive.")
        if amount > self.balance:
            raise ValueError("Cannot withdraw more than the current balance.")
        self.balance -= amount

    def get_balance(self):
        return self.balance
```

### 공동 파일럿 채팅를 사용하여 단위 테스트 생성

Copilot에 다양한 시나리오를 포괄하는 테스트를 작성하라는 요청을 보낼 수 있습니다. 최상의 결과를 얻으려면 테스트 요구 사항을 구체적으로 설명합니다. 다음은 `BankAccount` 클래스에 대한 단위 테스트를 생성하는 데 사용할 수 있는 프롬프트 예제입니다.

```
          **프롬프트:**_Python에서 BankAccount() 함수에 대한 포괄적인 단위 테스트 제품군을 개발합니다. 에지 사례, 예외 처리 및 데이터 유효성 검사를 포함하여 광범위한 시나리오를 다루는 여러 테스트 메서드를 작성합니다._
```

이 프롬프트는 다음과 같은 몇 가지 중요한 작업을 수행합니다.

* **범위**: Copilot이 다양한 잠재적 사용 사례를 다루는 기본 테스트 이상을 생성하도록 포괄적인 단위 테스트 제품군을 명시적으로 요청합니다.
* **시나리오**: 프롬프트는 테스트에 다음을 포함하도록 지정합니다.
  * **에지 사례**: 경계 조건 테스트(예: 예금 또는 인출에 대한 음수 값, 사용 가능한 잔액보다 더 많이 인출).
  * **예외 처리**: 특정 잘못된 작업이 발생할 때 발생해야 하는 오류에 대한 테스트(예: 사용 가능한 잔액보다 더 많은 금액을 인출하고 음수 금액을 입금).
  * **데이터 유효성 검사**: 입력의 유효성을 올바르게 검사하고 메서드에서 처리하도록 합니다.

Copilot은 다음과 같은 응답을 제공합니다.

```python
import unittest
from bank_account import BankAccount

class TestBankAccount(unittest.TestCase):
    def setUp(self):
        self.account = BankAccount()

    def test_initial_balance(self):
        self.assertEqual(self.account.get_balance(), 0)

    def test_deposit_positive_amount(self):
        self.account.deposit(100)
        self.assertEqual(self.account.get_balance(), 100)

    def test_withdraw_within_balance(self):
        self.account.deposit(100)
        self.account.withdraw(50)
        self.assertEqual(self.account.get_balance(), 50)

    def test_deposit_negative_amount_raises_error(self):
        with self.assertRaises(ValueError):
            self.account.deposit(-100)

    def test_withdraw_negative_amount_raises_error(self):
        with self.assertRaises(ValueError):
            self.account.withdraw(-50)

    def test_withdraw_more_than_balance_raises_error(self):
        self.account.deposit(100)
        with self.assertRaises(ValueError):
            self.account.withdraw(200)

    def test_initial_balance_negative_raises_error(self):
        with self.assertRaises(ValueError):
            BankAccount(-100)

if __name__ == '__main__':
    unittest.main()
```

### 테스트 사례 실행

Copilot에서 생성된 테스트 도구 모음에는 프롬프트에 지정된 각 시나리오에 대한 테스트 사례가 포함됩니다.
`BankAccount` 클래스에서 각 함수는 예금 및 인출과 같은 유효한 작업뿐만 아니라 음수 금액 및 사용 가능한 잔액보다 더 많은 인출 시도와 같은 에지 사례를 포함하여 다양한 조건에 대해 테스트됩니다.

Copilot이 테스트 도구 모음 생성을 완료하면 새 파일 `test_bank_account.py`에 코드를 추가하십시오. 테스트를 실행하는 방법을 물어볼 수 있습니다.

```
          **Prompt:**_"unittest 프레임워크를 사용하여 Python에서 이러한 단위 테스트를 어떻게 실행하나요?"_
```

Copilot은 다음 bash 명령을 제공합니다.

```bash
python -m unittest test_bank_account.py
```

테스트를 실행하면 터미널 또는 IDE에 출력이 표시됩니다. 모든 테스트가 통과하면 `BankAccount` 클래스가 예상대로 작동하는지 확신할 수 있습니다.

#### 슬래시 명령

또한 Copilot에게 `/tests` 슬래시 명령을 사용하여 전체 단위 테스트 모음을 작성하라는 메시지를 표시할 수 있습니다. IDE의 현재 탭에 파일이 열려 있는지 확인하고 Copilot에서 해당 파일에 대한 단위 테스트를 생성합니다. Copilot에서 생성하는 테스트는 모든 시나리오를 다루지 않을 수 있으므로 생성된 코드를 항상 검토하고 필요할 수 있는 추가 테스트를 추가해야 합니다.

> \[!TIP] Copilot에 단위 테스트에서 아직 다루지 않은 코드 파일에 대한 테스트를 작성하도록 요청하는 경우 편집기에서 인접한 탭에서 하나 이상의 기존 테스트 파일을 열어 유용한 컨텍스트와 함께 Copilot을 제공할 수 있습니다. Copilot은 사용하는 테스트 프레임워크를 볼 수 있으며 기존 테스트와 일치하는 테스트를 작성할 가능성이 높습니다.

Copilot은 다음과 같은 단위 테스트 제품군을 생성합니다.

```python
import unittest
from bank_account import BankAccount

class TestBankAccount(unittest.TestCase):
    def setUp(self):
        self.account = BankAccount()

    def test_initial_balance(self):
        self.assertEqual(self.account.get_balance(), 0)
```

## Copilot을 사용하여 통합 테스트 작성

통합 테스트는 결합 시 시스템의 다양한 구성 요소가 올바르게 작동하는지 확인하는 데 필수적입니다. 이 섹션에서는 외부 서비스 `BankAccount`와의 상호 작용을 포함하고 모의 항목을 사용하여 실제 연결 없이도 시스템 동작을 테스트하도록 `NotificationSystem` 클래스를 확장합니다. 통합 테스트의 목표는 `BankAccount` 클래스와 `NotificationSystem` 서비스 간의 상호 작용을 확인하여 올바르게 함께 작동하는지 확인하는 것입니다.

### 예제 클래스: Notification Services가 있는 `BankAccount`

사용자에게 알림을 보내는 `BankAccount`와(과) 같은 외부 서비스와의 상호 작용을 포함하도록 `NotificationSystem` 클래스를 업데이트 해보겠습니다.
`NotificationSystem`은 테스트해야 하는 통합을 나타냅니다.

다음 코드 조각을 사용하여 `BankAccount` 파일의 `bank_account.py` 클래스를 업데이트합니다.

```python
class BankAccount:
    def __init__(self, initial_balance=0, notification_system=None):
        if initial_balance < 0:
            raise ValueError("Initial balance cannot be negative.")
        self.balance = initial_balance
        self.notification_system = notification_system

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit amount must be positive.")
        self.balance += amount
        if self.notification_system:
            self.notification_system.notify(f"Deposited {amount}, new balance: {self.balance}")

    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive.")
        if amount > self.balance:
            raise ValueError("Cannot withdraw more than the current balance.")
        self.balance -= amount

        if self.notification_system:
            self.notification_system.notify(f"Withdrew {amount}, new balance: {self.balance}")

    def get_balance(self):
        return self.balance
```

여기서는 Copilot에 대한 요청을 세분화하여 `BankAccount` 클래스에 대한 통합 테스트를 더 작고 관리하기 쉬운 조각으로 작성합니다. 이렇게 하면 Copilot에서 좀 더 정확하고 관련 있는 테스트를 생성할 수 있습니다.

```
          **프롬프트:**_ "`deposit` 클래스의 `BankAccount` 함수에 대한 통합 테스트를 작성합니다. 모의 개체를 사용하여 `NotificationSystem`을(를) 시뮬레이션하고 입금 후에 올바르게 호출되는지 확인합니다."_
```

이 프롬프트는 다음과 같은 몇 가지 중요한 작업을 수행합니다.

* **범위**: 단위 테스트가 아니라 `deposit` 함수와 `NotificationSystem` 간의 상호 작용에 중점을 두고 통합 테스트를 지정합니다.
* **모의 객체:** 외부 시스템과의 상호 작용을 실제 구현에 의존하지 않고 테스트하기 위해 `NotificationSystem`을(를) 모의 객체로 시뮬레이션하도록 명시적으로 요청합니다.
* **확인**: 프롬프트는 `NotificationSystem`이 입금 후 올바르게 호출되는지 확인하여 구성 요소 간의 통합이 예상대로 작동하는지 확인합니다.
* **특이성**: 프롬프트는 테스트할 메서드(`deposit`) 및 클래스(`BankAccount`)를 명확하게 표시합니다.

> \[!TIP] Copilot이 잘못된 테스트를 생성하는 경우 테스트하려는 함수에 대한 입력 및 출력의 예를 제공합니다. 이렇게 하면 Copilot이 함수의 예상 동작을 평가하는 데 도움이 됩니다.

Copilot은 다음과 같은 테스트 제품군을 생성합니다.

```python
import unittest
from unittest.mock import Mock
from bank_account import BankAccount

class TestBankAccountIntegration(unittest.TestCase):
    def setUp(self):
        self.notification_system = Mock()

    def test_deposit_with_notification(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        account.deposit(50)
        self.assertEqual(account.get_balance(), 150)
        self.notification_system.notify.assert_called_once_with("Deposited 50, new balance: 150")

if __name__ == '__main__':
    unittest.main()
```

생성된 코드를 새 파일 `test_bank_account_integration.py`에 추가합니다.

### 테스트 사례 개선

위의 프롬프트는 `NotificationSystem`이(가) 유효한 입금이 이루어질 때 호출되는 것을 확인하는 단일 테스트 사례를 생성했습니다. 그러나 입금 중에 오류가 발생하는 경우는 다루지 않습니다. 이러한 시나리오에서는 `NotificationSystem`을(를) 호출하면 안 됩니다. 잘못된 예금을 처리하는 테스트 사례를 추가하고 알림 시스템이 트리거되지 않도록 해야 합니다.

```
          **프롬프트:**_"함수가 올바른 예외를 발생시키고 `NotificationService`이(가) 호출되지 않는지 확인하기 위해 잘못된 입금 금액에 대한 테스트 사례를 추가합니다."_
```

Copilot은 다음과 같은 테스트 사례를 생성합니다.

```python
    def test_deposit_negative_amount_raises_error(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        with self.assertRaises(ValueError):
            account.deposit(0)
        self.notification_system.notify.assert_not_called()
```

### 개선 영역에 대한 질문

이제 예금에 대한 통합 기능의 유효성을 검사하기 위해 테스트 사례를 작성했으므로 테스트 도구 모음 내에서 향상된 기능을 검색할 수 있는 좋은 기회입니다. 현재 테스트가 작동하는 동안 Copilot에 코드 검사를 평가하라는 메시지를 표시하고 개선 영역을 제안할 수 있습니다.

```
          **프롬프트:**_"`BankAccount` 클래스와 `NotificationSystem` 간의 통합에 대한 전체 검사를 보장하기 위해 어떤 추가 테스트를 포함해야 하나요?"_
```

이 질문으로 Copilot에 메시지를 표시하면 간과되었을 수 있는 누락된 테스트 사례를 식별하는 데 도움이 될 수 있습니다.
이 경우 유효하고 잘못된 예금을 테스트하는 동안 아직 인출 기능을 다루지 않았습니다.

Copilot은 다음과 같이 업데이트된 테스트 제품군을 생성합니다.

<details>
  <summary>전체 생성된 코드 예제를 확장하려면 클릭</summary>

```python
import unittest
from unittest.mock import Mock
from bank_account import BankAccount

class TestBankAccountIntegration(unittest.TestCase):
    def setUp(self):
        self.notification_system = Mock()

    def test_deposit_with_notification(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        account.deposit(50)
        self.assertEqual(account.get_balance(), 150)
        self.notification_system.notify.assert_called_once_with("Deposited 50, new balance: 150")

    def test_deposit_negative_amount_raises_error(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        with self.assertRaises(ValueError):
            account.deposit(-50)
        self.notification_system.notify.assert_not_called()

    def test_deposit_zero_amount_raises_error(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        with self.assertRaises(ValueError):
            account.deposit(0)
        self.notification_system.notify.assert_not_called()

    def test_withdraw_with_notification(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        account.withdraw(30)
        self.assertEqual(account.get_balance(), 70)
        self.notification_system.notify.assert_called_once_with("Withdrew 30, new balance: 70")

    def test_withdraw_exceeding_balance_raises_error(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        with self.assertRaises(ValueError):
            account.withdraw(150)
        self.notification_system.notify.assert_not_called()

    def test_withdraw_negative_amount_raises_error(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        with self.assertRaises(ValueError):
            account.withdraw(-30)
        self.notification_system.notify.assert_not_called()

    def test_withdraw_zero_amount_raises_error(self):
        account = BankAccount(initial_balance=100, notification_system=self.notification_system)
        with self.assertRaises(ValueError):
            account.withdraw(0)
        self.notification_system.notify.assert_not_called()

    def test_initial_negative_balance_raises_error(self):
        with self.assertRaises(ValueError):
            BankAccount(initial_balance=-100, notification_system=self.notification_system)

if __name__ == '__main__':
    unittest.main()
```

</details>

Copilot이 만족도에 맞게 테스트 제품군을 생성한 후에는 아래 명령을 사용하여 테스트를 실행하여 결과를 확인합니다.

```bash
python -m unittest test_bank_account_integration.py
```

## Copilot 스페이스를 사용하여 테스트 제안 개선

Copilot 스페이스는 Copilot를 사용하여 작업별 컨텍스트를 구성하고 공유할 수 있는 기능입니다. 이렇게 하면 사용자가 받는 제안의 관련성을 개선하는 데 도움이 됩니다. 프로젝트에 대한 더 많은 컨텍스트를 Copilot에 제공하면 더 나은 테스트 제안을 받을 수 있습니다.

예를 들어, 다음과 같은 항목을 포함하는 Space를 만들 수 있습니다.

* 테스트 중인 모듈(예: `payments.js`)
* 현재 테스트 도구 모음(예: `payments.test.js`)
* 누락된 항목에 대한 테스트 검사 보고서 또는 메모

이 Space에서 Copilot에게 다음과 같은 질문할 수 있습니다.

>

```
          `payments.test.js`의 논리를 기반으로 `payments.js`에서 어떤 테스트 사례가 누락되었어?
```

또는

> 기존 테스트 도구 모음의 구조를 따르면서 `refund.js`의 환불 논리에 대한 단위 테스트를 작성해줘.

Copilot 스페이스 사용에 대한 자세한 내용은 [GitHub Copilot 스페이스 정보](/ko/copilot/using-github-copilot/copilot-spaces/about-organizing-and-sharing-context-with-copilot-spaces)을 참조하세요.