APT: 4. Tests
안녕하세요! 이번 자료에서는 코드를 위한 테스트들을 짜는 방법에 대해 알아보도록 하겠습니다. 이번 자료 역시 파이썬 위주로 설명드리겠습니다.
코드 테스트
코드 테스트는 빠르고 정확한 개발을 위해 필수적인 측면입니다. 만약 코드를 테스트하지 않을 경우, 새로운 코드를 작성하거나 어떤 이유로든 코드를 바꿨을 때, 그 코드가 새로운 버그를 탄생시킬 확률이 무척이나 높아집니다. 코드 테스트가 있다면 테스트가 실패한 부분을 면밀히 검토해서 빠르게 코드를 수정할 수 있습니다.
코드 테스트 원칙
좋은 코드가 있듯이, 좋은 코드 테스트 역시 존재합니다. 좋은 테스트에 대해 사람들의 견해의 차이가 존재하긴 하나, 대다수의 개발자들이 동의하는 원칙들을 몇 가지 모아보았습니다.
- 코드 테스트의 테스트 유닛은 가장 작은 단위의 기능에 집중해서, 그 기능이 정확히 동작함을 증명해야 합니다.
- 모든 테스트 유닛은 반드시 독립적이야 합니다. 순서에 무관해야 하고, 다른 테스트가 실행되거나 실행되지 않는 경우에도 똑같이 작동해야 합니다.
- 테스트에 쓰이는 데이터는 실제 사용될 데이터와 비슷해야 합니다. 실제로 쓰이지 않을 변수들로만 함수를 테스트한다면, 그 테스트를 통과해도 함수가 정확히 작동하는지 확신할 수 없기 때문입니다.
- 하지만, 테스트는 특별한 경우들 (edge cases) 역시 테스트해봐야 합니다. (예를 들어, array / list가 비어 있는 경우)
- 테스트는 되도록 빨라야 합니다. 만약 어쩔 수 없이 느린 테스트가 존재한다면, 느린 테스트와 빠른 테스트를 분별해서 따로 실행할 수 있게 정리해야 합니다.
- 테스트 함수의 이름은 길어도 좋습니다. 직접 호출되는 개발한 코드의 함수들과는 달리, 테스트 함수들은 오로지 테스트 프레임워크로부터만 호출됩니다. 그리고 함수명 또한 그 테스트가 실패할 때만 나타납니다. 즉, 테스트가 실패할 때 어떤 부분에서 버그가 생겼는지 쉽게 알기 위해 테스트의 목적을 이름에 서술해야 합니다.
- 개발을 시작하기 전에 코드에 의미 문제가 있는지 확인하기 위해 우선 테스트를 돌려야 합니다. 더 좋은 방법은 아예 자동화시키는 것입니다. (다음 자료 참조)
- 만약 코드 테스트가 통과하는 상태에서 버그가 존재한다면, 버그를 잡기 위해 우선 새로운 테스트를 만들면 좋습니다. 이 테스트는 버그가 해결되었을 때 곧바로 알 수 있게 해줄 것입니다. 또, 버그를 잡을 때 다른 기능을 망가뜨리는 것이 아닌지 확인하기 위해 코드를 자주 테스트해야 합니다.
pytest
파이썬에는 여러 가지 테스트 프레임워크가 존재합니다. 기본적으로 제공되는 unittest
부터, pytest
, nose
, doctest
등이 존재합니다. 그 중에서 개인적으로 출력 정보가 가장 유용하고 가장 테스트 코드가 깔끔하다고 생각하는 pytest에 대해서 배워보도록 하겠습니다.
pytest 설치하기
pytest는 PyPI 에 등록되어 있는 패키지라서 pip 커맨드로 간단하게 설치하실 수 있습니다.
pip install pytest
pytest 사용하기
pytest 를 사용하려면 확인하려는 코드와 그 코드를 확인할 테스트가 필요합니다.
def decrement(x):
print('Inside decrement()')
return x + 1
def test_decrement():
print('Inside test_decrement()')
assert decrement(3) == 2
예를 들어, 우리는 받은 값에 1을 뺀 값을 반환하는 decrement
라는 함수를 만들고 싶습니다. 보시다시피 decrement
함수는 1을 빼지 않고 더하고 있기 때문에 잘못 구현된 함수입니다. test_decrement
는 decrement
를 확인하는 테스트로, assert
뒤의 statement가 참값을 가지지 않는 경우, 테스트가 실패합니다. 이 경우, decrement(3)
은 4의 값을 반환할 것이기 때문에 테스트가 실패할 것입니다.
테스트를 실행하기 위해선 터미널에서 pytest
라고 입력하면 됩니다. pytest
는 자동으로 test
가 앞에 들어간 함수와 파일들을 찾아서 실행시킵니다. 또 다른 방법은 테스트가 존재하는 파일 명을 명시하는 것입니다.
pytest
pytest temp.py
위처럼 테스트가 실패할 경우, 실패한 테스트가 빨간색으로 출력되며, 실패한 assert
가 있는 줄 역시 빨간색으로 출력되고 어떻게 실패했는지 알려줍니다. 즉 assert decrement(3) == 2
줄이 틀렸다는 걸 표시하고, 이 줄이 assert 4 == 2
로 인식되었다는 것을 알려주고, 4라는 숫자가 decrement(3)
에서 나왔다고 알려줍니다.
만약 코드가 복잡해서 실패한 테스트에 대해 더 많은 정보가 필요한 경우, pytest -v
나 pytest -vv
를 써서 더 많은 정보를 알아낼 수 있습니다. (여기서 v 는 verbose의 약자입니다)
def decrement(x):
print('Inside decrement()')
return x - 1
def test_decrement():
print('Inside test_decrement()')
assert decrement(3) == 2
decrement()
를 수정하고 다시 pytest
를 실행해봅시다. 이번에는 테스트가 통과했습니다. 테스트가 통과했다면, 초록색으로 표시되고 테스트에 대해 특별한 출력을 하지 않습니다. 보시다시피 print()
문들 역시 출력을 하지 않았습니다. pytest
는 테스트가 통과할 경우 그 테스트의 출력을 감추고, 실패한 테스트에 한해서만 출력들을 보여줍니다. 즉, 테스트 안에 디버깅용 출력문을 넣어놔도 대부분의 테스트가 통과한다면 긴 출력을 보지 않을 것입니다.
결론
이 자료에서는 코드 테스트의 기초에 대해서 배워봤습니다. 다음 자료는 지금까지 배운 Source Control, Linting, Testing을 연결하는 Continuous Integration 에 대해서 배워보도록 하겠습니다. 그 이후에는 지금까지 배운 내용들을 다시 재방문해서 더 자세히 알아보도록 하겠습니다.
추가 자료
파이썬
영문
The Hitchhiker’s Guide to Python: Testing Your Code
한글
The Hitchhiker’s Guide to Python: 코드 테스트하기
자바스크립트
Mocha: 테스트 프레임워크
Chai: BDD/TDD 테스트 프레임워크