-
Notifications
You must be signed in to change notification settings - Fork 364
#07. 테스팅 (Testing in Software)
Sung Yun Byeon edited this page Sep 26, 2022
·
1 revision
- 소프트웨어 엔지니어링 지식을 알고 싶은 미래의 ML/AI 엔지니어
- 실제 비즈니스에 모델을 배포해서 적용할 때에는 학습 단계와는 달리 고려해야할 점들이 많습니다.
- 코드를 유지보수가능하고 변경에도 용이한 형태로 만드는 게 소프트웨어 엔지니어링에서 아주 중요한 요소입니다.
- 테스트를 만들고 테스트를 주기적으로 실행 및 유지보수 해나가는 것만으로도 위와 같은 목표를 달성해나가기 수월해집니다.
소프트웨어 테스트의 중요성은 여러 책들과 강의에서 설명되고 있습니다. 하지만 가장 소프트웨어 엔지니어 관점에서 봤을 때의 테스트의 중요성과 어떤 자세가 필요한 지에 대한 정보가 잘 정리되어 있는 글이라고 생각하는 Geeksforgeeks의 Software Testing and Debugging 섹션을 소개해드립니다.
Software Engineering | Seven Principles of software testing - GeeksforGeeks
- Testing shows the presence of defects: 소프트웨어 테스트의 목표는 소프트웨어가 실패(잘못 동작)하게 만드는 일입니다. 소프트웨어 테스팅은 결함의 존재에 대해서 이야기하지만, 결함이 없음을 보장하는 도구는 아닙니다. 테스트는 결함을 줄이기는 하지만 모든 결함을 전부 없앨 수는 없습니다.
- Exhaustive testing is not possible: 모든 입력값과 상황에 대해서 테스트를 할 순 없습니다.
- Early testing: 최대한 일찍 테스트를 시작해야합니다. SDLC의 초기 단계에서 결함을 발견할 수록 해결할 수 있는 비용이 적습니다.
- Defect clustering: 소수의 모듈이 대부분의 결함들을 가지고 있습니다. (파레토의 법칙) 테스트는 이런 결함을 일으킬 모듈들을 결함을 일으키지 않을 모듈과 격리합니다.
- Pesticide paradox: 같은 테스트를 계속 수행하는 것은 새로운 버그를 찾아주지 않습니다. 지속적으로 테스트 케이스를 리뷰하고 발전시켜 나가야합니다.
- Testing is context-dependent: 테스트 방법과 전략은 소프트웨어가 개발된 맥락에 영향을 받습니다. 각 상황과 맥락에 맞는 테스트 방법을 찾아야합니다.
- Absence of errors fallacy: 테스트를 통해 버그가 없게 되는 거보다도 더 중요한 건 유저의 요구사항입니다.
Software Engineering | Testing Guidelines - GeeksforGeeks
- 개발팀은 소프트웨어 테스팅을 피해야합니다.
- 소프트웨어는 절대 버그가 없을 수 없습니다.
- 최대한 빠르게 테스트하십시오
- 중요한 부분을 먼저 테스트하세요.
- 가용시간은 제한적입니다
- 테스트는 예상하지 못하고 negative한 입력값에 대해서 수행되어야 합니다.
- 테스트 결과를 적절하게 분석해야합니다.
- 가설을 검증해야 합니다.
- E2E(Functional) Test: 하나의 (비즈니스 로직에서의) 기능 단위의 테스트. 실제 프로덕션의 환경과 가장 비슷한 환경을 만들어서 테스트합니다.
- Integration Test: 여러 모듈들이 의도대로 협력하는 지를 확인하는 테스트.
- Unit Test: 응용 프로그램에서 테스트 가능한 가장 작은 소프트웨어를 실행하여 예상대로 동작하는지 확인하는 테스트
- 목적: 테스트 더블이란 실제 애플리케이션에서 사용되는 객체를 대신해서 테스트에서 사이드 이펙트를 최소화해서 편하게 사용할 수 있도록 하기 위함
- 유래: 스턴트맨 (stunt double)으로 부터 만들어진 용어
- 종류
- Dummy: parameter 들만 채운 객체 인스턴스. 객체가 필요하지만 기능이 필요하지는 않을 때.
- Fake: 동작의 구현은 가지고 있지만 프로덕션에는 적합하지 않은 객체
- Stub: Dummy 객체가 실제로 동작하는 것처럼 구성. 최소한으로 인터페이스를 구성. 테스트에서 호출된 요청에 대한 결과를 미리 제공
- Spy: 테스트에서 해당 객체를 호출됐는 지에 대한 정보를 기록.
- Mock: 호출에 대한 기대를 명시하고, 내용에 따라 동작하도록 프로그래밍 된 객체
파이썬의 대표적인 테스트 프레임워크
일간 1.7M, 주간 10M 다운로드 (링크)
작고 읽기 쉬운 테스트 작성부터 복잡하고 규모있는 테스트 작성에도 사용합니다.
개인적으로 다른 testing 툴을 사용하거나 기본으로 파이썬에 내장되어있는 Unittest를 사용하지 않고 pytest를 사용하는 이유는 다음과 같습니다.
- 간결함 / 파이써닉함: 테스트를 작성하기 위해 써야하는 코드량이 많아지면, 자연스럽게 테스트에 손이 덜가게 됩니다. pytest는 간결하면서도 코드를 파이써닉하게 유지하게 해줍니다.
-
Fixture: 테스팅을 하는데 있어서 필요한 부분들을 혹은 조건들을 미리 준비해놓은 리소스 혹은 코드. 테스트 로직과는 상관이 없는 의존성 및 반복되는 코드 등을 재사용가능하게 만들어줍니다.
- pytest에는 유용한 여러 fixture들을 기본으로 제공합니다. (링크)
- scope를 통해서 테스트를 위해 환경을 준비하는 setup 관련 작업에 대한 시간을 최소화할 수 있습니다.
-
다양한 plugin: pypi에서 pytest 관련 플러그인을 설치해서 다양한 기능을 이용할 수 있습니다.
-
pytest-cov
: 테스트 커버리지를 계산해서 리포트 생성 (html, markdown 등) -
pytest-xdist
: 테스트 병렬 및 분산 실행으로 테스트 실행 속도를 높일 수 있음 -
pytest-mock
: mock 패키지를 래핑한 mocker fixture를 제공해서 코드 테스팅에서 monkeypatching, mock, stub, spy등의 test double을 쉽게 사용할 수 있습니다.
-
def inc(x): # 일반 함수
return x + 1
def test_answer(): # 테스트 케이스
assert inc(3) == 5
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_sample.py F [100%]
================================= FAILURES =================================
_______________________________ test_answer ________________________________
def test_answer():
> assert inc(3) == 5
E assert 4 == 5
E + where 4 = inc(3)
test_sample.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_sample.py::test_answer - assert 4 == 5
============================ 1 failed in 0.12s =============================
- pytest cli를 통해서 실행합니다.
- How to invoke pytest — pytest documentation
- 테스트에 필요한 코드 조각들을 재사용가능한 형태로 만듭니다.
-
@pytest.fixture
decorator로 fixture를 정의합니다. - fixture의 scope를 설정해서 값이나 인스턴스 초기화하는 주기를 설정할 수 있습니다.
- 테스트 케이스들에서 공통적으로 적용되어야 하는 로직이나 setup method에 해당하는 부분들을 하나의 함수로 만들어두고, 테스트하는 함수에서 인자로 받아 사용합니다.
- fixture 간의 dependency를 설정할 수 있습니다. fixture로 설정한 함수의 인자로 다른 fixture의 이름을 정의하면, 의존하는 fixture를 실행한 후에 값을 주입받아서 fixture가 정의됩니다.
@pytest.fixture
def logic_a():
return "hello"
@pytest.fixture
def logic_b(logic_a):
return f"{logic_a} world"
def test_fixture_deps(logic_b):
assert logic_b == "hello world" # True
- 처음엔 실패하는 테스트를 먼저 작성하세요
- 처음부터 fixture를 적용하기보다, 테스트를 미리 많이 만들어두고 테스트 코드를 중복 코드들을 리팩토링하면서 배워나가세요.