티스토리 뷰

unittest code를 작성하고 test code에서 module을 import할 때 너무나 많은 error가 발생했습니다. module을 찾을 수 없는 error와 attempted relative import with no known parent package error 등의 error가 발생했습니다. 이러한 error가 발생한 원인을 정확하게 파악하려 합니다.

__name__의 역할

python의 __name__은 모듈이 저장되는 변수이며 import로 모듈을 가져왔을 때 모듈의 이름이 들어갑니다. 파이썬 인터프리터를 통해 파이썬파일을 직접실행할 경우에는 파이썬에서 알아서 그파일의 name은 __main__이 됩니다. 파이썬 모듈을 import해서 사용할 경우에는 name은 원래 모듈 이름으로 설정됩니다. 그러므로 만약 해당 파일이 직접 실행시킬 때에만 실행되도록 설정하고 싶다면 if __name__ == '__main__':로 실행합니다.

절대경로와 상대경로

위와 같은 구조의 프로젝트가 있다고 한다고 합니다.

# add_two_numbers.py

from .lib.linked_list import LinkedList

상대경로는 내위치를 중심으로 표현한 경로를 말합니다. .은 현재위치를 ..은 상위 디렉터리를 나타냅니다.

# add_two_numbers.py

from lib.linked_list import LinkedList

절대경로는 절대경로는 변하지않는 고유경로입니다.

 

상대경로로 import하여 add_two_numbers.py을 직접 실행하는 경우 attempted relative import with no known parent package error가 발생합니다. 실행 후 상대경로를 통해 다른 모듈을 import 할때, 파이썬은 모듈의 이름 __name__에 기반을 두고 현재모듈의 위치를 찾는다. __name__이 직접 실행하는 경우 __main__으로 변경되어 위치를 찾을 수가 없게 됩니다. 직접 실행하는 python file의 경우 항상 절대경로를 사용해야한다고 적혀있습니다.

Intra-package References: Note that relative imports are based on the name of the current module. Since the name of the main module is always "main", modules intended for use as the main module of a Python application must always use absolute imports.

sys.path

sys.path는 디렉터리의 경로가 기록된 문자열 리스트입니다. 이 리스트에 경로를 추가하면 해당 경로에 있는 파이썬 파일을 import할 수 있습니다.

 

pip install <package> 명령을 실행하면 site-packages folder에 package가 설치됩니다. sys.pathsite-packages folder의 path가 등록되어 있어 다운 받은 package를 절대경로로 import하여 사용할 수 있습니다.

sys.path에는 기본적으로 몇 가지 경로가 미리 추가되어 있습니다. 직접 실행한 python file이 속한 folder의 위치가 추가되어 있습니다. 또한 위에서 언급한 site-packages의 위치, python 인터프이터의 위치 등이 추가되어 있습니다.

unittest 사용하면서 경로 설정 문제점

실행 방법에 따라 다른 경로 설정

unittest의 테스트 케이스를 작성은 아래와 같습니다.

# tests/test.py
import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

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

이 테스트 케이스를 실행하는 방법은 python -m unittest test.py로 실행합니다. 이와 같은 방법으로 실행하여 __name__의 값을 확인하면 test가 반환되는 것을 확인할 수 있습니다. __name__에서 __main__이 반환되지 않는 것으로 보아 직접실행되는 형태가 아닙니다. 직접실행하는 방법은 python test.py로 실행하는 것입니다. python test.py로 실행해야만 unittest.main()이 실행됩니다.

 

python -m의 실행은 모듈을 스크립트로 수행할 때 쓰는 옵션이라고 합니다. python -m unittest로 실행하면 unittest module이 스크립트로 실행되고 test.py는 unittest에 인자로 전달되어 import 형식으로 test.py가 실행되는 것으로 보입니다.

 

python -m unittest test.py로 실행하는 방법과 python test.py로 실행하는 방법이 import하는 경로의 차이가 발생합니다. python test.py로 직접 실행하는 경우 sys.path의 경로에 tests folder가 포함되지만 python -m unittest test.py로 실해하는 경우 shell의 현재 위치가 sys.path에 추가되어 있습니다. 따라서 경우 2가지 실행 방법에서 절대 경로를 설정한다면 다른 import 경로를 설정해야 합니다.

통합으로 test case를 실행할 때 다른 경로 설정

unittest로 test code를 작성할 때는 주로 하나의 파일만 실행합니다. test case를 작성을 완료하면 모든 test code를 한 번에 실행해야 합니다.

# test_attendace_service.py
import unittest
from unittest.mock import patch, MagicMock
from datetime import datetime
from flaskr.service import attendance_service
from flaskr.model import AttendanceModel, TopicModel, UserModel

.....

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

절대경로로 module을 import하여 test_attendace_service.py file 하나만 실행하는 경우 import 경로에 문제가 없이 동작하게 구성했습니다.

# all_tests.py
import glob
import unittest
import sys
import os
testdir = os.path.dirname(__file__)
sys.path.insert(0, os.path.abspath(
    os.path.join(testdir, "../../des_api_lambda")))

if __name__ == '__main__':
    loader = unittest.TestLoader()
    start_dir = 'tests'
    suite = loader.discover(start_dir)

    runner = unittest.TextTestRunner()
    runner.run(suite)

all_test.py에서는 sys.path에 project root folder의 위치를 추가하여 import 경로에 문제가 발생하지 않도록 구성했습니다.

Reference

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함