상세 컨텐츠

본문 제목

Pytest - python test framework

Programming/Python

by 쌩우 2020. 8. 13. 17:25

본문

Pytest - python test framework

 

-v 옵션은 verbose로 추가적인 정보를 보고 싶은경우에 추가

1. Fixture

테스트 코드들에서 공통으로 사용되는 함수 또는 코드들을 작성할 떄 유용.

테스트의 기반이 되는 기초 환경 구성에도 유용.

사용방법

#conftest.py 라는 명칭으로 작성하면, 다른 test 파일들이 알아서 참조하게 됨
import pytest

@pytest.fixture #fixture로 선언할 코드들을 아래에 작성

def supply_AA_BB_CC():
  aa=25
  bb=35
  cc=45
  return [aa,bb,cc]
#test_basic_fixture.py
#전혀 다른 파일에서도 fixture를 공유하기 때문에 위에서 작성한 함수를 바로 사용할 수 있다.

import pytest

@pytest.fixture

def test_comparewithAA(supply_AA_BB_CC):
  zz=35
  assert supply_AA_BB_CC[0] == zz, "aa and zz comparison failed"
: pytest test_basic_fixture.py
: -> aa and zz comparison failed

pytest를 실행하면 정상적으로 supply_AA_BB_CC 함수를 인식하고, 테스트 통과 실패에 따른 메시지가 나타날 것이다.

 

2. Run Tests By Markings

마킹이 된 것들은 마크별로 테스트 실행을 구분지을 수 있다.

import pytest
@pytest.mark.set1 #set1이라는 이름으로 마킹

def test_fil1_method1():
  x = 5
  y = 6
  assert x + 1 === y, "test failed"
  assert x == y, f"test failed because x={x} y={y}!"
#run pytest with marks
# -m 옵션과 마크 이름을 지정하여 실행
: pytest -m set1

 

3. Parameterized tests

다수의 argumnets 세트로 테스트를 실행할 때 사용한다.

import pytest
@pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
'''
"input1, input2, output"은 해당 위치의 변수값들과 1대1로 매치되는 변수명
1번째 argument는 list 형태로 각각의 튜플 원소를 가지는데, 각각의 원소에 대하여 순회하며 test를 실행하게 됨
ex) 첫번째 테스트 : input1 = 5, input2 = 5, output = 10
		두번째 테스트 : input1 = 3, input2 = 5, output = 12
'''
def test_add(input1, input2, output):
  assert input1+input2 == output, "failed"

테스트를 실행하면 1번째 argumnet로 실행한 테스트에서 fail 되는 것을 확인할 수 있다.

test_addition.py::test_add[5-5-10] PASSED                                                                                                                                                                                              
test_addition.py::test_add[3-5-12] FAILED                                                                                                                                                                                              
============================================== FAILURES ==============================================
__________________________________________ test_add[3-5-12] __________________________________________
input1 = 3, input2 = 5, output = 12
    @pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
    def test_add(input1, input2, output):
>   	assert input1+input2 == output,"failed"
E    AssertionError: failed
E    assert (3 + 5) == 12
test_addition.py:5: AssertionError

 

4. Fail / Skip tests

특정 상황에서는 어떤 테스트를 실행하고 싶지 않거나, 무관한 테스트를 제외하고 싶을 수 있다.

그럴 때에 xfail이나 skip을 사용한다.

xfail한 테스트는 실행은 되지만 fail 또는 pass에 집계되지 않는다.

skip한 테스트는 실행조차 되지 않는다.

import pytest
@pytest.mark.skip
def test_add_1():
    assert 100+200 == 400, "failed"
@pytest.mark.skip
def test_add_2():
    assert 100+200 == 300, "failed"
@pytest.mark.xfail
def test_add_3():
    assert 15+13 == 28, "failed"
@pytest.mark.xfail
def test_add_4():
    assert 15+13 == 100,"failed"

def test_add_5():
    assert 3+2 == 5, "failed"

def test_add_6():
    assert 3+2 == 6, "failed"

테스트를 실행하면 아래와 같은 결과로 나타난다.

test_addition.py::test_add_1 SKIPPED
test_addition.py::test_add_2 SKIPPED
test_addition.py::test_add_3 XPASS
test_addition.py::test_add_4 xfail
test_addition.py::test_add_5 PASSED
test_addition.py::test_add_6 FAILED

============================================== FAILURES ==============================================
_____________________________________________ test_add_6 _____________________________________________
    def test_add_6():
>   	assert 3+2 == 6,"failed"
E    AssertionError: failed
E    assert (3 + 2) == 6
test_addition.py:24: AssertionError

================ 1 failed, 1 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.07 seconds =================

 

5. Results XML

테스트 결과를 XML 형식으로 생성할 수도 있다.

#test1_sample1.py의 테스트 결과를 xml 파일로 뽑아보자
: pytest test_sample1.py -v --junitxml="result.xml" #파일명은 result.xml로 임의 지정했다

테스트가 끝나면 result.xml이라는 파일이 아래와 같은 내용을 생성된다.

<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite errors="0" failures="1" hostname="gimsang-us-MacBook-Pro.local" name="pytest" skipped="0" tests="2" time="0.037" timestamp="2020-08-13T16:24:09.430519"><testcase classname="test_sample1" file="test_sample1.py" line="1" name="test_file1_method1" time="0.001"><failure message="AssertionError: test failed because x=5 y=6
assert 5 == 6
  +5
  -6">@pytest.mark.set1
    def test_file1_method1():
        x=5
        y=6
        assert x+1 == y, &quot;test failed&quot;
&gt;       assert x == y, &quot;test failed because x=&quot; + str(x) + &quot; y=&quot; + str(y)
E       AssertionError: test failed because x=5 y=6
E       assert 5 == 6
E         +5
E         -6

test_sample1.py:7: AssertionError</failure></testcase><testcase classname="test_sample1" file="test_sample1.py" line="7" name="test_fil1_method2" time="0.001"></testcase></testsuite></testsuites>

 

* Testing an API *

앞서 알아본 방법들을 활용하여 API를 테스트해본다.

테스트에 사용할 샘플 API는 샘플API를 사용한다.

(단, 공통으로 사용하기 위하여 fixture단에서 선언해준다.)

#confest.py

import pytest
@pytest.fixture
def supply_url():
  return "https://reqres.in/api"

해당 API의 호출과 관련하여 실제로 테스트할 코드는 아래에서 작성한다.

#test_list.user.py
import pytest
import requests
import json
@pytest.fixture

@pytest.mark.parametrize("userid, firstname",[(1, "George"), (2, "janet")])
def test_list_valid_user(supply_url, userid, firstname):
    url = supply_url + "/users/" + str(userid)
    resp = requests.get(url)
    j = json.loads(resp.text)
    assert resp.status_code == 200, resp.text
    assert j['data']['id'] == userid, resp.text
    assert j['data']['first_name'] == firstname, resp.text
def test_list_invaliduser(supply_url):
    url = supply_url + "/users/50"
    resp = requests.get(url)
    assert resp.status_code == 404, resp.text

 

#test_login_user.py
import pytest
import requests
import json

def test_login_valid(supply_url):
    url = supply_url + "/login"
    data = {'email': 'test@test.com', 'password': 'something'}
    resp = requests.post(url, data=data)
    j = json.loads(resp.text)
    assert resp.status_code == 400, resp.text
    assert j['error'] == 'Missing password', resp.text

def test_login_no_email(supply_url):
    url = supply_url + "/login"
    data = {}
    resp = requests.post(url, data=data)
    j = json.loads(resp.text)
    assert resp.status_code == 400, resp.text
    assert j['error'] == 'Missing email or username', resp.text

requests 모듈을 사용하여 실제로 호출 및 응답받은 값을 통하여 테스트를 실행하게 된다.

 

관련글 더보기

댓글 영역