Python Unittest 모듈

Beginner

This tutorial is from open-source community. Access the source code

소개

이 랩에서는 Python 의 내장 모듈인 unittest를 사용하는 방법을 배우게 됩니다. 이 모듈은 코드가 올바르게 작동하는지 확인하기 위한 소프트웨어 개발의 중요한 부분인 테스트를 구성하고 실행하기 위한 프레임워크를 제공합니다.

또한 기본적인 테스트 케이스를 생성하고 실행하며, 예상되는 오류와 예외를 테스트하는 방법도 배우게 됩니다. unittest 모듈은 테스트 스위트 (test suite) 를 생성하고, 테스트를 실행하며, 결과를 검증하는 과정을 단순화합니다. 이 랩에서는 teststock.py 파일이 생성될 것입니다.

첫 번째 유닛 테스트 생성하기

Python 의 unittest 모듈은 테스트를 구성하고 실행하는 구조화된 방법을 제공하는 강력한 도구입니다. 첫 번째 유닛 테스트를 작성하기 전에 몇 가지 핵심 개념을 이해해 보겠습니다. 테스트 픽스처 (test fixture) 는 테스트 전에 환경을 준비하고 테스트 후에 정리하는 데 도움이 되는 setUptearDown과 같은 메서드입니다. 테스트 케이스 (test case) 는 개별 테스트 단위이고, 테스트 스위트 (test suite) 는 테스트 케이스의 모음이며, 테스트 러너 (test runner) 는 이러한 테스트를 실행하고 결과를 표시하는 역할을 합니다.

이 첫 번째 단계에서는 stock.py 파일에 이미 정의된 Stock 클래스에 대한 기본 테스트 파일을 생성할 것입니다.

  1. 먼저, stock.py 파일을 엽니다. 이렇게 하면 테스트할 Stock 클래스를 이해하는 데 도움이 됩니다. stock.py의 코드를 보면 클래스가 어떻게 구성되어 있는지, 어떤 속성을 가지고 있는지, 어떤 메서드를 제공하는지 알 수 있습니다. stock.py 파일의 내용을 보려면 터미널에서 다음 명령을 실행하십시오.
cat stock.py
  1. 이제 선호하는 텍스트 편집기를 사용하여 teststock.py라는 새 파일을 생성할 차례입니다. 이 파일에는 Stock 클래스에 대한 테스트 케이스가 포함됩니다. 다음은 teststock.py 파일에 작성해야 하는 코드입니다.
## teststock.py

import unittest
import stock

class TestStock(unittest.TestCase):
    def test_create(self):
        s = stock.Stock('GOOG', 100, 490.1)
        self.assertEqual(s.name, 'GOOG')
        self.assertEqual(s.shares, 100)
        self.assertEqual(s.price, 490.1)

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

이 코드의 주요 구성 요소를 살펴보겠습니다.

  • import unittest: 이 줄은 Python 에서 테스트를 작성하고 실행하는 데 필요한 도구와 클래스를 제공하는 unittest 모듈을 가져옵니다.
  • import stock: Stock 클래스를 포함하는 모듈을 가져옵니다. 이 import 가 없으면 테스트 코드에서 Stock 클래스에 액세스할 수 없습니다.
  • class TestStock(unittest.TestCase): unittest.TestCase에서 상속받는 TestStock라는 새 클래스를 생성합니다. 이렇게 하면 TestStock 클래스가 여러 테스트 메서드를 포함할 수 있는 테스트 케이스 클래스가 됩니다.
  • def test_create(self): 이것은 테스트 메서드입니다. unittest 프레임워크에서 모든 테스트 메서드는 test_ 접두사로 시작해야 합니다. 이 메서드는 Stock 클래스의 인스턴스를 생성한 다음 assertEqual 메서드를 사용하여 Stock 인스턴스의 속성이 예상 값과 일치하는지 확인합니다.
  • assertEqual: 이것은 TestCase 클래스에서 제공하는 메서드입니다. 두 값이 같은지 확인합니다. 같지 않으면 테스트가 실패합니다.
  • unittest.main(): 이 스크립트가 직접 실행되면 unittest.main()TestStock 클래스의 모든 테스트 메서드를 실행하고 결과를 표시합니다.
  1. teststock.py 파일에 코드를 작성한 후 저장합니다. 그런 다음 터미널에서 다음 명령을 실행하여 테스트를 실행합니다.
python3 teststock.py

다음과 유사한 출력이 표시됩니다.

.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

출력의 단일 점 (.) 은 하나의 테스트가 성공적으로 통과했음을 나타냅니다. 테스트가 실패하면 점 대신 F가 표시되며 테스트에서 무엇이 잘못되었는지에 대한 자세한 정보가 함께 제공됩니다. 이 출력은 코드가 예상대로 작동하는지 또는 수정해야 할 문제가 있는지 빠르게 식별하는 데 도움이 됩니다.

테스트 케이스 확장하기

이제 기본적인 테스트 케이스를 만들었으므로 테스트 범위를 확장할 차례입니다. 더 많은 테스트를 추가하면 Stock 클래스의 나머지 기능을 다루는 데 도움이 됩니다. 이렇게 하면 클래스의 모든 측면이 예상대로 작동하는지 확인할 수 있습니다. TestStock 클래스를 수정하여 여러 메서드와 속성에 대한 테스트를 포함시킬 것입니다.

  1. teststock.py 파일을 엽니다. TestStock 클래스 내부에 몇 가지 새로운 테스트 메서드를 추가할 것입니다. 이러한 메서드는 Stock 클래스의 다른 부분을 테스트합니다. 다음은 추가해야 하는 코드입니다.
def test_create_keyword_args(self):
    s = stock.Stock(name='GOOG', shares=100, price=490.1)
    self.assertEqual(s.name, 'GOOG')
    self.assertEqual(s.shares, 100)
    self.assertEqual(s.price, 490.1)

def test_cost(self):
    s = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(s.cost, 49010.0)

def test_sell(self):
    s = stock.Stock('GOOG', 100, 490.1)
    s.sell(20)
    self.assertEqual(s.shares, 80)

def test_from_row(self):
    row = ['GOOG', '100', '490.1']
    s = stock.Stock.from_row(row)
    self.assertEqual(s.name, 'GOOG')
    self.assertEqual(s.shares, 100)
    self.assertEqual(s.price, 490.1)

def test_repr(self):
    s = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(repr(s), "Stock('GOOG', 100, 490.1)")

def test_eq(self):
    s1 = stock.Stock('GOOG', 100, 490.1)
    s2 = stock.Stock('GOOG', 100, 490.1)
    self.assertEqual(s1, s2)

각 테스트가 수행하는 작업을 자세히 살펴보겠습니다.

  • test_create_keyword_args: 이 테스트는 키워드 인수를 사용하여 Stock 객체를 생성할 수 있는지 확인합니다. 객체의 속성이 올바르게 설정되었는지 확인합니다.
  • test_cost: 이 테스트는 Stock 객체의 cost 속성이 올바른 값 (주식 수에 가격을 곱한 값) 을 반환하는지 확인합니다.
  • test_sell: 이 테스트는 Stock 객체의 sell() 메서드가 일부 주식을 판매한 후 주식 수를 올바르게 업데이트하는지 확인합니다.
  • test_from_row: 이 테스트는 from_row() 클래스 메서드가 데이터 행에서 새 Stock 인스턴스를 생성할 수 있는지 확인합니다.
  • test_repr: 이 테스트는 Stock 객체의 __repr__() 메서드가 예상 문자열 표현을 반환하는지 확인합니다.
  • test_eq: 이 테스트는 __eq__() 메서드가 두 Stock 객체를 올바르게 비교하여 동일한지 확인하는지 확인합니다.
  1. 이러한 테스트 메서드를 추가한 후 teststock.py 파일을 저장합니다. 그런 다음 터미널에서 다음 명령을 사용하여 테스트를 다시 실행합니다.
python3 teststock.py

모든 테스트가 통과하면 다음과 같은 출력이 표시됩니다.

......
----------------------------------------------------------------------
Ran 7 tests in 0.001s

OK

출력의 7 개의 점은 각 테스트를 나타냅니다. 각 점은 테스트가 성공적으로 통과했음을 나타냅니다. 따라서 7 개의 점이 보이면 7 개의 테스트가 모두 통과했음을 의미합니다.

예외 테스트하기

테스트는 소프트웨어 개발의 중요한 부분이며, 그 중요한 측면 중 하나는 코드가 오류 조건을 적절하게 처리할 수 있도록 하는 것입니다. Python 에서 unittest 모듈은 특정 예외가 예상대로 발생했는지 테스트하는 편리한 방법을 제공합니다.

  1. teststock.py 파일을 엽니다. 예외를 확인하도록 설계된 몇 가지 테스트 메서드를 추가할 것입니다. 이러한 테스트는 잘못된 입력을 만났을 때 코드가 올바르게 동작하는지 확인하는 데 도움이 됩니다.
def test_shares_type(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.shares = '50'

def test_shares_value(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(ValueError):
        s.shares = -50

def test_price_type(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(TypeError):
        s.price = '490.1'

def test_price_value(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(ValueError):
        s.price = -490.1

def test_attribute_error(self):
    s = stock.Stock('GOOG', 100, 490.1)
    with self.assertRaises(AttributeError):
        s.share = 100  ## 'share' is incorrect, should be 'shares'

이제 이러한 예외 테스트가 어떻게 작동하는지 이해해 보겠습니다.

  • with self.assertRaises(ExceptionType): 문은 컨텍스트 관리자 (context manager) 를 생성합니다. 이 컨텍스트 관리자는 with 블록 내부의 코드가 지정된 예외를 발생시키는지 확인합니다.
  • 예상된 예외가 with 블록 내에서 발생하면 테스트가 통과합니다. 이는 코드가 잘못된 입력을 올바르게 감지하고 적절한 오류를 발생시키고 있음을 의미합니다.
  • 예외가 발생하지 않거나 다른 예외가 발생하면 테스트가 실패합니다. 이는 코드가 예상대로 잘못된 입력을 처리하지 못할 수 있음을 나타냅니다.

이러한 테스트는 다음 시나리오를 확인하도록 설계되었습니다.

  • shares 속성을 문자열로 설정하면 TypeError가 발생해야 합니다. shares는 숫자여야 하기 때문입니다.
  • shares 속성을 음수로 설정하면 ValueError가 발생해야 합니다. 주식 수는 음수가 될 수 없기 때문입니다.
  • price 속성을 문자열로 설정하면 TypeError가 발생해야 합니다. price는 숫자여야 하기 때문입니다.
  • price 속성을 음수로 설정하면 ValueError가 발생해야 합니다. 가격은 음수가 될 수 없기 때문입니다.
  • 존재하지 않는 속성 share(누락된 's'에 유의) 를 설정하려고 하면 AttributeError가 발생해야 합니다. 올바른 속성 이름은 shares이기 때문입니다.
  1. 이러한 테스트 메서드를 추가한 후 teststock.py 파일을 저장합니다. 그런 다음 터미널에서 다음 명령을 사용하여 모든 테스트를 실행합니다.
python3 teststock.py

모든 것이 올바르게 작동하면 12 개의 모든 테스트가 통과했음을 나타내는 출력이 표시됩니다. 출력은 다음과 같습니다.

............
----------------------------------------------------------------------
Ran 12 tests in 0.002s

OK

12 개의 점은 지금까지 작성한 모든 테스트를 나타냅니다. 이전 단계에서 7 개의 테스트가 있었고, 방금 5 개를 더 추가했습니다. 이 출력은 코드가 예상대로 예외를 처리하고 있음을 보여주며, 이는 잘 테스트된 프로그램의 훌륭한 징후입니다.

선택된 테스트 실행 및 테스트 디스커버리 사용

Python 의 unittest 모듈은 코드를 효과적으로 테스트할 수 있는 강력한 도구입니다. 특정 테스트를 실행하거나 프로젝트의 모든 테스트를 자동으로 검색하고 실행하는 여러 가지 방법을 제공합니다. 이는 테스트 중에 코드의 특정 부분에 집중하거나 전체 프로젝트의 테스트 스위트를 빠르게 확인할 수 있으므로 매우 유용합니다.

특정 테스트 실행하기

경우에 따라 전체 테스트 스위트 대신 특정 테스트 메서드 또는 테스트 클래스만 실행하려는 경우가 있을 수 있습니다. unittest 모듈과 함께 패턴 옵션을 사용하여 이를 수행할 수 있습니다. 이렇게 하면 실행할 테스트를 더 많이 제어할 수 있으며, 코드의 특정 부분을 디버깅할 때 유용할 수 있습니다.

  1. Stock 객체 생성과 관련된 테스트만 실행하려면 다음을 수행합니다.
python3 -m unittest teststock.TestStock.test_create

이 명령에서 python3 -m unittest는 Python 에게 unittest 모듈을 실행하도록 지시합니다. teststock은 테스트 파일의 이름이고, TestStock은 테스트 클래스의 이름이며, test_create는 실행하려는 특정 테스트 메서드입니다. 이 명령을 실행하면 Stock 객체 생성과 관련된 코드가 예상대로 작동하는지 빠르게 확인할 수 있습니다.

  1. TestStock 클래스의 모든 테스트를 실행하려면 다음을 수행합니다.
python3 -m unittest teststock.TestStock

여기서는 특정 테스트 메서드 이름을 생략합니다. 따라서 이 명령은 teststock 파일의 TestStock 클래스 내의 모든 테스트 메서드를 실행합니다. 이는 Stock 객체의 테스트 케이스의 전반적인 기능을 확인하려는 경우에 유용합니다.

테스트 디스커버리 사용하기

unittest 모듈은 프로젝트의 모든 테스트 파일을 자동으로 검색하고 실행할 수 있습니다. 이렇게 하면 특히 테스트 파일이 많은 대규모 프로젝트에서 각 테스트 파일을 수동으로 지정하여 실행하는 번거로움을 덜 수 있습니다.

  1. 현재 파일의 이름을 테스트 디스커버리 명명 패턴에 따라 변경합니다.
mv teststock.py test_stock.py

unittest의 테스트 디스커버리 메커니즘은 test_*.py 명명 패턴을 따르는 파일을 찾습니다. 파일 이름을 test_stock.py로 변경하면 unittest 모듈이 이 파일의 테스트를 더 쉽게 찾고 실행할 수 있습니다.

  1. 테스트 디스커버리를 실행합니다.
python3 -m unittest discover

이 명령은 unittest 모듈에게 현재 디렉토리에서 test_*.py 패턴과 일치하는 모든 테스트 파일을 자동으로 검색하고 실행하도록 지시합니다. 디렉토리를 검색하고 일치하는 파일에서 발견된 모든 테스트 케이스를 실행합니다.

  1. 테스트를 검색할 디렉토리를 지정할 수도 있습니다.
python3 -m unittest discover -s . -p "test_*.py"

여기서:

  • -s .은 검색을 시작할 디렉토리를 지정합니다 (이 경우 현재 디렉토리). 점 (.) 은 현재 디렉토리를 나타냅니다. 다른 위치에서 테스트를 검색하려면 이 값을 다른 디렉토리 경로로 변경할 수 있습니다.
  • -p "test_*.py"는 테스트 파일과 일치하는 패턴입니다. 이렇게 하면 test_로 시작하고 .py 확장자를 가진 파일만 테스트 파일로 간주됩니다.

이전과 마찬가지로 12 개의 모든 테스트가 실행되고 통과하는 것을 볼 수 있습니다.

  1. 랩과의 일관성을 위해 파일 이름을 원래 이름으로 다시 변경합니다.
mv test_stock.py teststock.py

테스트 디스커버리를 실행한 후 랩 환경을 일관되게 유지하기 위해 파일 이름을 원래 이름으로 다시 변경합니다.

테스트 디스커버리를 사용하면 각 테스트 파일을 개별적으로 지정할 필요 없이 프로젝트의 모든 테스트를 쉽게 실행할 수 있습니다. 이렇게 하면 테스트 프로세스가 더 효율적이고 오류 발생 가능성이 줄어듭니다.

요약

이 랩에서는 Python 의 unittest 모듈을 사용하여 자동화된 테스트를 생성하고 실행하는 방법을 배웠습니다. unittest.TestCase 클래스를 확장하여 기본 테스트 케이스를 만들고, 클래스 메서드와 속성의 정상적인 기능을 확인하는 테스트를 작성했으며, 오류 조건에서 적절한 예외를 확인하는 테스트를 만들었습니다. 또한 특정 테스트를 실행하고 테스트 디스커버리를 사용하는 방법도 배웠습니다.

단위 테스트는 소프트웨어 개발의 기본적인 기술로, 코드의 신뢰성과 정확성을 보장합니다. 철저한 테스트를 작성하면 버그를 조기에 발견하고 코드의 동작에 대한 확신을 가질 수 있습니다. Python 애플리케이션을 개발할 때, 보다 강력하고 유지 관리 가능한 코드를 위해 기능 구현 전에 테스트를 작성하는 테스트 주도 개발 (TDD, Test-Driven Development) 방식을 채택하는 것을 고려해 보십시오.