Ce texte est destiné aux testeurs novices qui souhaitent comprendre comment faire des rapports sur l'allure avec l'historique des tests, et également expliquer où les stocker afin que n'importe quel membre de votre équipe puisse consulter le rapport.
Référentiel de travail avec le code de travail final et l'infrastructure
Lien vers les rapports après l'exécution des tests
gitlab python, allure, docker, , . , , , . allure, . , UI , .
. , . windows, .
, python, , . pytest, , , . Dog API. gitlab, docker.
, :
- API
- allure
- Gitlab Runner
- .gitlab-ci.yml
- UI
allure_pages
.
:
conftest.py
—requirements.txt
— pythontest_dog_api.py
tests
, . :
python -m venv venv
PyCharm
, PyCharm . , .
-
Add Interpreter
, PyCharm ,
:
.
pip install requests
— , Dog Apipip install pytest
— ,pip install pytest-xdist
—pip install allure-pytest
— allure
pip freeze > requirements.txt
.
requirements.txt
.
, requirements.txt
allure-pytest==2.8.18 pytest==6.0.1 pytest-xdist==2.1.0 requests==2.24.0
API
, GET POST . . , . .
@pytest.fixture
def dog_api():
return ApiClient(base_address="https://dog.ceo/api/")
. base_address, https://dog.ceo/api/
. , GET POST .
class ApiClient:
def __init__(self, base_address):
self.base_address = base_address
GET .
def get(self, path="/", params=None, headers=None):
url = f"{self.base_address}{path}"
return requests.get(url=url, params=params, headers=headers)
, requests. ApiClient path
, params
, headers
get
, requests.get(url=url, params=params, headers=headers)
. . , , , , requests.get
.
POST. , , .
def post(self, path="/", params=None, data=None, json=None, headers=None):
url = f"{self.base_address}{path}"
return requests.post(url=url, params=params, data=data, json=json, headers=headers)
conftest.py:
import pytest
import requests
class ApiClient:
def __init__(self, base_address):
self.base_address = base_address
def post(self, path="/", params=None, data=None, json=None, headers=None):
url = f"{self.base_address}{path}"
return requests.post(url=url, params=params, data=data, json=json, headers=headers)
def get(self, path="/", params=None, headers=None):
url = f"{self.base_address}{path}"
return requests.get(url=url, params=params, headers=headers)
@pytest.fixture
def dog_api():
return ApiClient(base_address="https://dog.ceo/api/")
Dog API . , . , . , . , . test_dog_api.py
import pytest
def test_get_random_dog(dog_api):
response = dog_api.get("breeds/image/random")
with allure.step(" , "):
assert response.status_code == 200, f" , {response.status_code}"
with allure.step(" . json ."):
response = response.json()
assert response["status"] == "success"
with allure.step(f" {response}"):
with allure.step(f" "):
with allure.step(f" - "):
pass
@pytest.mark.parametrize("breed", [
"afghan",
"basset",
"blood",
"english",
"ibizan",
"plott",
"walker"
])
def test_get_random_breed_image(dog_api, breed):
response = dog_api.get(f"breed/hound/{breed}/images/random")
response = response.json()
assert breed in response["message"], f" , {response}"
@pytest.mark.parametrize("file", ['.md', '.MD', '.exe', '.txt'])
def test_get_breed_images(dog_api, file):
response = dog_api.get("breed/hound/images")
response = response.json()
result = '\n'.join(response["message"])
assert file not in result, f" {file}"
@pytest.mark.parametrize("breed", [
"african",
"boxer",
"entlebucher",
"elkhound",
"shiba",
"whippet",
"spaniel",
"dvornyaga"
])
def test_get_random_breed_images(dog_api, breed):
response = dog_api.get(f"breed/{breed}/images/")
response = response.json()
assert response["status"] == "success", f" {breed}"
@pytest.mark.parametrize("number_of_images", [i for i in range(1, 10)])
def test_get_few_sub_breed_random_images(dog_api, number_of_images):
response = dog_api.get(f"breed/hound/afghan/images/random/{number_of_images}")
response = response.json()
final_len = len(response["message"])
assert final_len == number_of_images, f" {number_of_images}, {final_len}"
allure
allure. , . , .
with allure.step('step 1'):
— , .
@allure.feature('Dog Api') @allure.story('Send few requests')
— ,
allure, . allure,
API allure .
class ApiClient:
def __init__(self, base_address):
self.base_address = base_address
def post(self, path="/", params=None, data=None, json=None, headers=None):
url = f"{self.base_address}{path}"
with allure.step(f'POST request to: {url}'):
return requests.post(url=url, params=params, data=data, json=json, headers=headers)
def get(self, path="/", params=None, headers=None):
url = f"{self.base_address}{path}"
with allure.step(f'GET request to: {url}'):
return requests.get(url=url, params=params, headers=headers)
. , . . .
@allure.feature('Random dog')
@allure.story(' ')
def test_get_random_dog(dog_api):
response = dog_api.get("breeds/image/random")
with allure.step(" , "):
assert response.status_code == 200, f" , {response.status_code}"
with allure.step(" . json ."):
response = response.json()
assert response["status"] == "success"
with allure.step(f" {response}"):
with allure.step(f" "):
with allure.step(f" - "):
pass
. test_dog_api.py
import pytest
import allure
@allure.feature('Random dog')
@allure.story(' ')
def test_get_random_dog(dog_api):
response = dog_api.get("breeds/image/random")
with allure.step(" , "):
assert response.status_code == 200, f" , {response.status_code}"
with allure.step(" . json ."):
response = response.json()
assert response["status"] == "success"
with allure.step(f" {response}"):
with allure.step(f" "):
with allure.step(f" - "):
pass
@allure.feature('Random dog')
@allure.story(' ')
@pytest.mark.parametrize("breed", [
"afghan",
"basset",
"blood",
"english",
"ibizan",
"plott",
"walker"
])
def test_get_random_breed_image(dog_api, breed):
response = dog_api.get(f"breed/hound/{breed}/images/random")
with allure.step(" . json ."):
response = response.json()
assert breed in response["message"], f" , {response}"
@allure.feature('List of dog images')
@allure.story(' ')
@pytest.mark.parametrize("file", ['.md', '.MD', '.exe', '.txt'])
def test_get_breed_images(dog_api, file):
response = dog_api.get("breed/hound/images")
with allure.step(" . json ."):
response = response.json()
with allure.step(" "):
result = '\n'.join(response["message"])
assert file not in result, f" {file}"
@allure.feature('List of dog images')
@allure.story(' ')
@pytest.mark.parametrize("breed", [
"african",
"boxer",
"entlebucher",
"elkhound",
"shiba",
"whippet",
"spaniel",
"dvornyaga"
])
def test_get_random_breed_images(dog_api, breed):
response = dog_api.get(f"breed/{breed}/images/")
with allure.step(" . json ."):
response = response.json()
assert response["status"] == "success", f" {breed}"
@allure.feature('List of dog images')
@allure.story(' ')
@pytest.mark.parametrize("number_of_images", [i for i in range(1, 10)])
def test_get_few_sub_breed_random_images(dog_api, number_of_images):
response = dog_api.get(f"breed/hound/afghan/images/random/{number_of_images}")
with allure.step(" . json ."):
response = response.json()
with allure.step(" "):
final_len = len(response["message"])
assert final_len == number_of_images, f" {number_of_images}, {final_len}"
, :
.gitlab-ci.yml
— , yaml gitlab runnergitlab-runner
— , Go. . . gitlab runner, docker . "". .
devops - , . . docker desktop windows. , .gitlab-ci .
docker desktop, .
, gitlab.com. gitlab.com, . , .
Settings -> General -> Visibility, project features, permissions
, Pipelines
.
CI / CD
. Settings -> CI / CD -> Runners
Gitlab Runner
. , powershell, , . , , .
C:\gitlab_runners , gitlab-runner.exe
, :
- :
./gitlab-runner.exe register
- url, 2. , :
https://gitlab.somesubdomain.com/
- 3. , :
tJTUaJ7JxfL4yafEyF3k
- . UI . :
Runner on windows for autotests
- , , .gitlab-ci.yml, . , .
docker, windows
- , . docker
docker
- image, .
.gitlab-ci.yml
python:3.8-alpine
, .
:
.\gitlab-runner.exe status
:
.\gitlab-runner.exe run
, Settings -> CI / CD -> Runners
, - :
, . . .
, .
.gitlab-ci.yml
stages. . 4. stage — job, .
stages:
- testing #
- history_copy #
- reports #
- deploy # gitlab pages
. Testing
docker_job: # job
stage: testing # stage,
tags:
- docker # gitlab , . , , 6 .
image: python:3.8-alpine # , .
before_script:
- pip install -r requirements.txt #
script:
- pytest -n=4 --alluredir=./allure-results tests/test_dog_api.py # (-n=4 ), --alluredir=
allow_failure: true # , .
artifacts: # , , .
when: always #
paths:
- ./allure-results #
expire_in: 1 day # , . .
. history_copy
history_job: # job
stage: history_copy # stage,
tags:
- docker #
image: storytel/alpine-bash-curl # , . , ?
script:
- 'curl --location --output artifacts.zip "https://( , gitlab.example.com)/api/v4/projects/( )/jobs/artifacts/master/download?job=pages&job_token=$CI_JOB_TOKEN"' # api job, . .
- apk add unzip # , unzip,
- unzip artifacts.zip #
- chmod -R 777 public #
- cp -r ./public/history ./allure-results #
allow_failure: true # , . .
artifacts:
paths:
- ./allure-results #
expire_in: 1 day
rules:
- when: always #
. reports
allure_job: # job
stage: reports # stage,
tags:
- docker #
image: frankescobar/allure-docker-service # allure. .
script:
- allure generate -c ./allure-results -o ./allure-report # ./allure-results ./allure-report
artifacts:
paths:
- ./allure-results #
- ./allure-report
expire_in: 1 day
rules:
- when: always
. deploy
pages: # job , pages
stage: deploy # stage,
script:
- mkdir public # public. gitlab pages public
- mv ./allure-report/* public # public .
artifacts:
paths:
- public
rules:
- when: always
.gitlab-ci.yml
stages:
- testing #
- history_copy #
- reports #
- deploy # gitlab pages
docker_job: # job
stage: testing # stage,
tags:
- docker # gitlab , . , , 6 .
image: python:3.8-alpine # , .
before_script:
- pip install -r requirements.txt #
script:
- pytest -n=4 --alluredir=./allure-results tests/test_dog_api.py # (-n=4 ), --alluredir=
allow_failure: true # , .
artifacts: # , , .
when: always #
paths:
- ./allure-results #
expire_in: 1 day # , . .
history_job: # job
stage: history_copy # stage,
tags:
- docker #
image: storytel/alpine-bash-curl # , . , ?
script:
- 'curl --location --output artifacts.zip "https://( , gitlab.example.com)/api/v4/projects/( )/jobs/artifacts/master/download?job=pages&job_token=$CI_JOB_TOKEN"' # api job, . .
- apk add unzip # , unzip,
- unzip artifacts.zip #
- chmod -R 777 public #
- cp -r ./public/history ./allure-results #
allow_failure: true # , . .
artifacts:
paths:
- ./allure-results #
expire_in: 1 day
rules:
- when: always #
allure_job: # job
stage: reports # stage,
tags:
- docker #
image: frankescobar/allure-docker-service # allure. .
script:
- allure generate -c ./allure-results -o ./allure-report # ./allure-results ./allure-report
artifacts:
paths:
- ./allure-results #
- ./allure-report
expire_in: 1 day
rules:
- when: always
pages: # job , pages
stage: deploy # stage,
script:
- mkdir public # public. gitlab pages public
- mv ./allure-report/* public # public .
artifacts:
paths:
- public
rules:
- when: always
, :
conftest.py
-
tests
requirements.txt
(, ).gitlab-ci.yml
.
, . , . CI / CD -> Pipelines
, Ci , .gitlab-ci.yml.
, Settings -> Pages
. pages 30 . .
. .
, , . .
. , stage history_job . .
, . .
UI
[services](https://docs.gitlab.com/ee/ci/services/)
. , script
. UI job :
services:
- selenium/standalone-chrome:latest
, url, - url, . :
-
:
-
/
__
-
/
-
( Gitlab Runner v1.1.0 )
executor ( chromedriver , ) ui :
browser = webdriver.Remote(command_executor="http://selenium__standalone-chrome:4444/wd/hub")
, .gitlab-ci.yml:
selenium/standalone-chrome:latest
:
selenium__standalone-chrome
Pour exécuter mes tests, j'ai obtenu ce poste. Si vous ajoutez les trois travaux suivants de l'exemple avec des tests d'API, vous pouvez exécuter tous les tests et obtenir un rapport.
chrome_job:
stage: testing
services:
- selenium/standalone-chrome
image: python:3.8
tags:
- docker
before_script:
- pip install -r requirements.txt
script:
- pytest --alluredir=./allure-results tests/
allow_failure: true
artifacts:
when: always
paths:
- ./allure-results
expire_in: 1 day
Liens utiles
En plus des liens dans l'article, j'aimerais partager quelques autres qui peuvent aider Ă travailler avec allure et gitlab ci.
Un exemple de ce projet sur gitlab
Lien vers les rapports après l'exécution des tests
Un article sur allure sur habr
Introduction Ă gitlab ci
UPD: Merci aux commentateurs d'avoir signalé les inexactitudes et l'incomplétude de ce guide. J'ai corrigé et ajouté un lien vers le référentiel de travail