{"id":361244,"date":"2024-05-21T01:09:42","date_gmt":"2024-05-21T01:09:42","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=361244"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-21T01:09:42","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=361244","title":{"rendered":"<span>\u0421\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u00abpowered by pytest\u00bb: \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0432\u043e\u0437\u0438\u0442\u044c\u0441\u044f, \u043d\u043e \u043e\u043d\u043e \u0442\u043e\u0433\u043e \u0441\u0442\u043e\u0438\u0442<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0442\u0438\u043f\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 Excel. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b, \u043a\u0430\u043a \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d\u0430 (\u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446, \u043a\u043e\u043b\u043e\u043d\u043e\u043a \u0442\u0430\u0431\u043b\u0438\u0446) \u0438 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0445 (\u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0430\u043c, \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438, \u0438\u0442\u0434). <\/p>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043d\u0443\u0436\u043d\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e &#171;\u0447\u0442\u043e \u043d\u0435 \u0442\u0430\u043a \u0441 \u0444\u0430\u0439\u043b\u043e\u043c&#187;: \u043a\u0430\u043a\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043d\u0435 \u043f\u0440\u043e\u0448\u0435\u043b \u0444\u0430\u0439\u043b \u0438 \u0433\u0434\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u0432 \u0444\u0430\u0439\u043b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435. <\/p>\n<p>\u042d\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043e\u0447\u0435\u043d\u044c \u043d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 &#8212; \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0434\u0430\u043d\u043d\u044b\u0445. \u0422\u0430\u043a \u043f\u043e\u0447\u0435\u043c\u0443 \u0431\u044b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f pytest, \u0438 \u043d\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u044b\u0439 \u0430\u0441\u043f\u0435\u043a\u0442 \u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u0444\u0430\u0439\u043b\u043e\u0432? \u041e\u0434\u043d\u0430\u043a\u043e, \u0435\u0441\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0435 &#171;\u043d\u043e&#187;: \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u0447\u0442\u043e\u0431\u044b \u0432\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0431\u043e\u043b\u0435\u0435 \u0448\u0438\u0440\u043e\u043a\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432. <\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c <code>pytest<\/code> \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u042d\u0442\u043e \u043d\u0435 \u0442\u0430\u043a \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u043e, \u043a\u0430\u043a \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0437\u0433\u043b\u044f\u0434.<\/p>\n<h2>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e &#8212; \u043d\u0435\u043c\u043d\u043e\u0433\u043e &#171;\u043f\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u0439\u043b&#187;<\/h2>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f, \u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 <code>pytest<\/code> \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 \u0432\u043e\u043e\u0431\u0449\u0435 \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b. \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u0442\u0435\u0441\u0442\u0430 \u0438 \u043f\u043e\u0433\u043b\u044f\u0434\u0438\u043c, \u043a\u0430\u043a \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c \u0444\u0430\u0439\u043b \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 &#171;Config&#187;. \u041d\u0430 \u043d\u0435\u0439 \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0438\u0437 \u0442\u0440\u0435\u0445 \u043a\u043e\u043b\u043e\u043d\u043e\u043a: &#171;Code&#187;, &#171;TextData&#187;, &#171;Numbers&#187; (<a href=\"https:\/\/github.com\/serhit\/pytest_service\/blob\/main\/examples\/example_type_1.xlsx\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u043b\u0435\u0436\u0438\u0442 \u0437\u0434\u0435\u0441\u044c<\/a>).<\/p>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0447\u0442\u043e:<\/p>\n<ol>\n<li>\n<p>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c \u0438\u043c\u0435\u043d\u0435\u043c \u0435\u0441\u0442\u044c<\/p>\n<\/li>\n<li>\n<p>\u0412 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u043d\u0430 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u0435\u0441\u043b\u0438 \u0435\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>pandas<\/code> \u0435\u0441\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043b\u043e\u043d\u043a\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u043e\u0436\u0438\u0434\u0430\u0435\u043c.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043a\u043e\u043b\u043e\u043d\u043a\u0430\u0445 &#171;Code&#187;, &#171;TextData&#187; \u043a\u0430\u0436\u0434\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0435. \u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u0440 &#171;\u043f\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438&#187;. \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u0435\u0435 &#8212; \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0432 \u0441\u0435\u0442 \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u041d\u043e \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043c\u043d\u0438\u0442\u044c, \u0447\u0442\u043e \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0441\u043b\u043e\u0436\u043d\u0435\u0435 &#8212; \u0437\u0430\u0442\u0440\u0430\u0433\u0438\u0432\u0430\u044e \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0441\u0442\u043e\u043b\u0431\u0446\u0430\u043c\u0438, \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0442\u043e\u043b\u0431\u0446\u044b, \u0438\u0442\u0434.<\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043a\u043e\u0434 \u0442\u0435\u0441\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043a\u0430\u043a \u043d\u0430 \u0432\u0441\u0442\u0430\u0432\u043a\u0435. \u0414\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u043a\u0430 \u0443\u043a\u0430\u0436\u0435\u043c \u043f\u0440\u044f\u043c\u0443\u044e \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0444\u0430\u0439\u043b.<\/p>\n<pre><code class=\"python\">import pytest import pandas   SHEET_NAME = 'Config' TABLE_OFFSET = 0 KEY_FIELD = 'Code'   def get_config_data():     data = pandas.read_excel(\"&lt;static path to file>\",                               sheet_name=SHEET_NAME,                               skiprows=TABLE_OFFSET)     return data  # Generate array of rows - to parametrize tests try:     config_data_records = [rec for rec in get_config_data().iterrows() if str(rec[1][KEY_FIELD]) != 'nan']     config_indexes = [rec[0] for rec in config_data_records] except:     config_data_records = None     config_indexes = None    @pytest.fixture() def config_dataset():     return get_config_data()  @pytest.fixture(params=config_data_records, ids=config_indexes) def config_record(request):     return request.param[1], request.param[0] + TABLE_OFFSET + 2  # Excel rows start with 1, plus caption, plus offset     @pytest.mark.tryfirst def test_data_exists():     get_config_data()  # Test applies for whole rowset @pytest.mark.parametrize(\"column_name\", [KEY_FIELD, 'TextData', 'Numbers']) def test_data_has_column(config_dataset, column_name):     assert column_name in config_dataset.columns  # Test applies for each row of the file @pytest.mark.parametrize(\"column_name\", [KEY_FIELD, 'TextData']) def test_mandatory_value_is_missing(config_record, column_name):     rec, line_no = config_record     assert not any(rec[[column_name]].isna())<\/code><\/pre>\n<p>\u0422\u0435\u0441\u0442\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0438 \u043b\u043e\u0432\u044f\u0442 \u043e\u0448\u0438\u0431\u043a\u0438, \u0433\u0434\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e. \u041f\u043e\u043a\u0430 \u0432\u0441\u0435 \u0445\u043e\u0440\u043e\u0448\u043e.<\/p>\n<h2>\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 pytest<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0442\u0435\u0441\u0442\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e. \u0412 <a href=\"https:\/\/happytest.readthedocs.io\/en\/latest\/reference\/#pytest-main\" rel=\"noopener noreferrer nofollow\">\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0443 pytest \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043e<\/a> &#8212; \u043c\u043e\u0436\u043d\u043e \u0434\u0435\u043b\u0430\u0442\u044c.<\/p>\n<pre><code class=\"python\">files = ['&lt;static testfile.py>']  pytest.main(files)<\/code><\/pre>\n<h3>\u0421\u0431\u043e\u0440 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a, <a href=\"https:\/\/stackoverflow.com\/questions\/72277284\/retrieve-pytest-results-programmatically-when-run-via-pytest-main\" rel=\"noopener noreferrer nofollow\">\u043a\u0430\u043a \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u043d\u043e \u0437\u0434\u0435\u0441\u044c \u043d\u0430 stackoverflow<\/a>. \u041f\u0440\u0438\u0434\u0443\u043c\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u0447\u0435\u0433\u043e \u043d\u043e\u0432\u043e\u0433\u043e \u043d\u0435 \u0431\u0443\u0434\u0435\u043c &#8212; \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0443.<\/p>\n<pre><code class=\"python\"># Definition of ResultsCollector goes here  collector = ResultsCollector() files = ['&lt;static testfile.py>']  pytest.main(files, plugins=[collector])  # Process collected results<\/code><\/pre>\n<h3>\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u0438\u043c\u0435\u043d\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430<\/h3>\n<p>\u0412\u0440\u043e\u0434\u0435 \u0432\u0441\u0435 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u041e\u0434\u043d\u0430\u043a\u043e, \u043f\u043e\u0440\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c &#171;\u0434\u0438\u043d\u0430\u043c\u0438\u043a\u0438&#187; &#8212; \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0438\u043c\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0432\u043d\u0443\u0442\u0440\u044c \u0442\u0435\u0441\u0442\u0430.<\/p>\n<p>\u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0432 \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043f\u043b\u0430\u0433\u0438\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 \u0441 \u0438\u043c\u0435\u043d\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0438 \u043f\u043e\u0442\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0435\u0433\u043e \u0432 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e &#8212; \u0432 \u043f\u0430\u043a\u0435\u0442 <code>pytest<\/code><\/p>\n<pre><code class=\"python\">class FileRef:     def __init__(self):         self.file_name = None      @pytest.hookimpl     def pytest_addoption(self, parser):         parser.addoption(\"--file\", action=\"store\")      @pytest.hookimpl     def pytest_configure(self, config):         self.file_name = config.option.file         pytest.data_file_name = config.option.file<\/code><\/pre>\n<p>\u0422\u043e\u0433\u0434\u0430 \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">file_to_test = \"&lt;path to excel file>\"  collector = ResultsCollector() file_ref = FileRef() plugins = [collector, file_ref]  files = ['&lt;static testfile.py>', '--file', file_to_test]  pytest.main(files, plugins=plugins)  # Process collected results<\/code><\/pre>\n<p>\u0430 \u0432 \u0441\u0430\u043c\u043e\u043c \u0442\u0435\u0441\u0442\u0435 \u043e\u0441\u0442\u0430\u043d\u0435\u0442\u0441\u044f \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043d\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e:<\/p>\n<pre><code class=\"python\">...  def get_config_data():     data = pandas.read_excel(pytest.data_file_name, # Now this will come from the parameter                              sheet_name=SHEET_NAME,                               skiprows=TABLE_OFFSET)     return data  ...<\/code><\/pre>\n<h4>\u041f\u0440\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445<\/h4>\n<p>\u041a\u0441\u0442\u0430\u0442\u0438, \u0435\u0441\u0442\u044c \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432, \u0433\u0434\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0435, \u043a\u0430\u043a \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 <a href=\"https:\/\/stackoverflow.com\/questions\/40880259\/how-to-pass-arguments-in-pytest-by-command-line\" rel=\"noopener noreferrer nofollow\">\u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0437\u0434\u0435\u0441\u044c<\/a>. \u041e\u0434\u043d\u0430\u043a\u043e, \u0434\u043b\u044f \u043d\u0430\u0448\u0438\u0445 \u0446\u0435\u043b\u0435\u0439 \u044d\u0442\u043e\u0442 \u0441\u043f\u043e\u0441\u043e\u0431 \u043d\u0435 \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442.<\/p>\n<p>\u041f\u043e \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0445\u043e\u0434\u0438\u0442 &#171;\u0434\u0435\u0440\u0435\u0432\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0439&#187; <code>pytest<\/code>, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u0435\u0440\u0435\u043f\u0435\u0447\u0430\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u0441\u0442\u0430\u0442\u044c\u044e (<a href=\"https:\/\/github.com\/pytest-dev\/pytest\/issues\/3261#issuecomment-755461808\" rel=\"noopener noreferrer nofollow\">\u043e\u0434\u043d\u043e \u0438\u0437 \u043f\u0435\u0440\u0432\u044b\u0445 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u0439<\/a>, <a href=\"https:\/\/stackoverflow.com\/questions\/65617105\/need-order-and-action-of-pytest-hooks-execution\" rel=\"noopener noreferrer nofollow\">\u0441\u0441\u044b\u043b\u043a\u0438<\/a>, <a href=\"https:\/\/paragkamble.medium.com\/understanding-hooks-in-pytest-892e91edbdb7\" rel=\"noopener noreferrer nofollow\">\u043f\u0435\u0440\u0435\u043f\u0435\u0447\u0430\u0442\u043a\u0438<\/a>) . \u042d\u0442\u043e &#171;\u0434\u0435\u0440\u0435\u0432\u043e&#187; \u0445\u043e\u0440\u043e\u0448\u043e \u0438\u043c\u0435\u0442\u044c \u043f\u043e\u0434 \u0440\u0443\u043a\u043e\u0439 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432.<\/p>\n<figure class=\"bordered\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ce8\/87b\/6fb\/ce887b6fbefe8746a68de0e73a0b5263.png\" width=\"404\" height=\"443\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ce8\/87b\/6fb\/ce887b6fbefe8746a68de0e73a0b5263.png\"\/><\/figure>\n<p>\u0424\u0438\u043a\u0441\u0442\u0443\u0440\u044b &#171;\u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f&#187; \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0444\u0430\u0437\u0435 &#171;collection&#187; (2) &#8212; \u043d\u0430 \u0442\u043e\u0439 \u0436\u0435 \u0444\u0430\u0437\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u0431\u043e\u0440 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u0438\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u044f (\u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0447\u0435\u0433\u043e \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f &#171;\u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u0442\u0435\u0441\u0442\u043e\u0432&#187;. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0444\u0430\u0439\u043b \u0447\u0442\u043e\u0431\u044b \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0435\u0433\u043e \u0441\u0442\u0440\u043e\u043a\u0438 \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043c\u0430\u0441\u0441\u0438\u0432 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u043e\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f.<\/p>\n<p>\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0444\u0430\u0439\u043b\u0443 \u043d\u0443\u0436\u0435\u043d \u0443\u0436\u0435 \u043d\u0430 \u0441\u0442\u0430\u0434\u0438\u0438 &#171;configure&#187; (1).<\/p>\n<h2>\u0412\u0441\u0435 &#8212; \u0432 \u0441\u0435\u0440\u0432\u0438\u0441<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0447\u0430\u0442\u044c \u0443\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0438\u0445 \u0437\u0430\u043f\u0443\u0441\u043a\u0430. \u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (\u0430 \u0442\u043e\u0447\u043d\u0435\u0435 &#8212; &#171;\u0443\u043f\u0430\u0432\u0448\u0438\u0445&#187; \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a), \u0443\u0434\u043e\u0431\u043d\u043e \u043e\u0431\u044a\u044f\u0432\u0438\u0442\u044c \u043e\u0431\u0449\u0438\u0435 \u0442\u0438\u043f\u044b. <\/p>\n<pre><code class=\"python\"># common_types.py  from enum import Enum from pydantic import BaseModel from typing import List, Optional   class ConfigType(str, Enum):     file_type_1 = 'file-type-1'     file_type_2 = 'file-type-2'   class TestResult(BaseModel):     id: str     outcome: str     rule: str     line_no: Optional[int] = None     field_name: Optional[str] = None     ref: Optional[str] = None   class FileValidationResult(BaseModel):     file_name: str     result: int     passed_cnt: int     failed_cnt: int     xfailed_cnt: int     skipped_cnt: int     total_duration_sec: float     failed_tests: List[TestResult]     version: str = None<\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0442\u0438\u043f\u044b \u043f\u0440\u0438\u0433\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u0418\u0445 \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0432\u044b\u0437\u043e\u0432\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u0430.<\/p>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0444\u0430\u0439\u043b \u0438 \u0435\u0433\u043e \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u0439 \u0442\u0438\u043f, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0435\u0433\u043e \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u043c\u044f \u044d\u0442\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0442\u0438\u043f\u043e\u043c \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<pre><code class=\"python\"># main.py  ...  @app.post('\/validate_file', response_model=FileValidationResult) async def validate_file(config_type: ConfigType,                         file: UploadFile = File()                         ):      _, file_extension = os.path.splitext(file.filename)      with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as _file:         _content = await file.read()         _file.write(_content)         _tmp_file_name = _file.name      res = validator.run_test(_tmp_file_name, config_type)     res.file_name = file.filename     res.version = __version__      os.remove(_tmp_file_name)      return res<\/code><\/pre>\n<p>\u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0431\u0438\u0440\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f. \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u0431\u043e\u043b\u0435\u0435 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u0432 \u043e\u0431\u0449\u0443\u044e \u043f\u0430\u043f\u043a\u0443 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0432  <code>pytest<\/code> \u0435\u0435 \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u0410 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0432\u044f\u0437\u043a\u0443 \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430 \u0438 \u0442\u0438\u043f\u0430. <\/p>\n<pre><code class=\"python\"># validator.py  ...  def run_test(file_name: str, file_type: ConfigType):          ...        _folder = file_type.value     if os.path.isdir(_folder):         # If we have a folder with the name matching file type - we take it         files = [_folder]     else:         # If no folder defined - search files with names related to file type         _ft = file_type.value.replace('-', '_')         files = glob.glob(f'test_{_ft}*.py')      # Add parameter for name of the file being tested     files += ['--file', file_name]      # and run found tests against the given file. Plugins will help.     pytest.main(files, plugins=plugins)      # Process results of tests based on the internal state of `collector` instance     ...     <\/code><\/pre>\n<p>\u041e\u0447\u0435\u0432\u0438\u0434\u043d\u043e\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435 \u043f\u0440\u043e \u043f\u0430\u043f\u043a\u0443 \u0441 \u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u0440\u043e\u0442\u0438\u0432 \u043f\u0440\u043e\u0441\u0442\u043e \u0444\u0430\u0439\u043b\u043e\u0432 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u043f\u0430\u043f\u043a\u0438, \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043d\u043e\u0432\u044b\u0445 \u0442\u0438\u043f\u043e\u0432 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438 Dockerfile. \u041f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0442\u0430\u043a\u043e\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u0442 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<pre><code>FROM python:3.11-slim  COPY requirements.txt \/app\/ WORKDIR \/app\/ RUN pip install --no-cache-dir -r requirements.txt  # Install application COPY *.py \/app\/  RUN mkdir {file-type-1, file-type-2}  COPY file-type-1\/*.py \/app\/file-type-1\/ COPY file-type-2\/*.py \/app\/file-type-2\/  EXPOSE 8000  ENTRYPOINT [\"uvicorn\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\", \"--workers\", \"1\", \"main:app\"]<\/code><\/pre>\n<h2>\u0418 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0431\u044b \u0432\u0441\u0435 \u0433\u043e\u0442\u043e\u0432\u043e, \u043d\u043e &#8230;<\/h2>\n<p>\u0421\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0444\u0430\u0439\u043b, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0432\u044b\u0434\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0432 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u043e\u043a\u0430\u0436\u0435\u0442, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u043e\u0434\u0441\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0435\u043c\u0443 \u0440\u0430\u0437\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b &#8212; \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u043e\u0440\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430. <\/p>\n<p>\u041f\u0440\u0438\u0447\u0438\u043d\u0430 &#171;\u043f\u0440\u043e\u0441\u0442\u0430\u044f&#187;. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f <code>pytest<\/code> \u043d\u0430\u0441\u0442\u043e\u043b\u044c\u043a\u043e \u0433\u043b\u0443\u0431\u043e\u043a\u043e \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0438 \u0444\u0438\u043a\u0441\u0442\u0443\u0440, \u0447\u0442\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0432 \u043e\u0434\u043d\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u0435\u043d. \u041e\u0431 \u044d\u0442\u043e\u043c <a href=\"https:\/\/docs.pytest.org\/en\/latest\/how-to\/usage.html\" rel=\"noopener noreferrer nofollow\">\u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f<\/a> \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442 &#8212; \u043d\u043e \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043e\u0431\u043e\u0439\u0442\u0438 \u044d\u0442\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u0435\u0441\u0441\u0438\u044e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435. \u042d\u0442\u043e\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438.<\/p>\n<p>\u041f\u0435\u0440\u0435\u0443\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u0443\u044e \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0438 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0435\u0435 \u0432\u044b\u0437\u043e\u0432 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435. \u0414\u043b\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0432 \u0432\u0438\u0434\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 <code>collector<\/code> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u043d\u0443\u044e \u0432 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0435\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435.<\/p>\n<pre><code class=\"python\"># validator.py  ...  import multiprocessing as mp  ...  def _run(file_name: str, file_type: ConfigType, q):     collector = ResultsCollector()     file_ref = FileRef()     plugins = [collector, file_ref]      _folder = file_type.value     if os.path.isdir(_folder):         files = [_folder]     else:         _ft = file_type.value.replace('-', '_')         files = glob.glob(f'test_{_ft}*.py')      files += ['--file', file_name]      pytest.main(files, plugins=plugins)      q.put(collector)   def run_test(file_name: str, file_type: ConfigType):     # Need to run tests into totally different process to make it not to \"stuck\"     # with the first file      q = mp.Queue()     p = mp.Process(target=_run, args=(file_name, file_type, q))     p.start()     collector = q.get()     p.join()      # Process results of tests based on the internal state of `collector` instance     ...<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u0432\u044b\u0437\u043e\u0432\u0430\u0445 \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u0430\u043a \u043e\u0436\u0438\u0434\u0430\u043b\u043e\u0441\u044c &#8212; \u0442\u0435\u0441\u0442\u044b \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u044e\u0442 \u0438\u0437\u043c\u0435\u043d\u044f\u044e\u0449\u0438\u0439\u0441\u044f \u0432\u0445\u043e\u0434, \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u044b \u043f\u0435\u0440\u0435\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u044e\u0442\u0441\u044f, \u0442\u0435\u0441\u0442\u044b \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f.<\/p>\n<p>\u041d\u0430\u0434\u043e \u0443\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c, \u043e\u0434\u043d\u0430\u043a\u043e, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u0432\u0434\u0440\u0443\u0433 \u0432 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u0431\u0435\u0440\u0443\u0442\u0441\u044f \u043d\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 (\u0443 \u043c\u0435\u043d\u044f \u0442\u0430\u043a\u043e\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0441\u043b\u0443\u0447\u0430\u0439\u043d\u043e), \u0442\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0437\u0430\u0432\u0438\u0441\u043d\u0435\u0442: \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u043f\u043e\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c, \u0430 \u0432\u044b\u0437\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0431\u0443\u0434\u0435\u0442 \u0436\u0434\u0430\u0442\u044c &#171;\u043f\u043e\u0441\u044b\u043b\u043a\u0438&#187; \u0438 \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0437\u0430\u043a\u043e\u043d\u0447\u0438\u0442\u0441\u044f.<\/p>\n<h3>\u0414\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043e\u0431 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0441\u0442\u0438<\/h3>\n<p>\u0412 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445 \u0440\u0435\u0437\u043e\u043d\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u043a\u043e\u0434 \u043c\u043e\u0436\u0435\u0442 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c event loop \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f. <\/p>\n<p>\u0418\u0441\u043f\u0440\u0430\u0432\u043b\u044f\u044e:<\/p>\n<pre><code class=\"python\"># validator.py  ...  import asyncio import concurrent.futures  ...  def _run(file_name: str,          file_type: ConfigType          ):     collector = ResultsCollector()     file_ref = FileRef()     plugins = [collector, file_ref]      _folder = file_type.value     if os.path.isdir(_folder):         files = [_folder]     else:         _ft = file_type.value.replace('-', '_')         files = glob.glob(f'test_{_ft}*.py')      files += ['--file', file_name]      pytest.main(files, plugins=plugins)      # Simply return collector     return collector   async def run_test(file_name: str, file_type: ConfigType):     loop = asyncio.get_running_loop()      with concurrent.futures.ProcessPoolExecutor() as pool:         collector = await loop.run_in_executor(pool, _run, file_name, file_type)      ...<\/code><\/pre>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0434\u0430\u0436\u0435 \u0431\u043e\u043b\u0435\u0435 \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u043e \u0438 \u043a\u0440\u0430\u0441\u0438\u0432\u043e: \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0441 \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u043e\u043c \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u043f\u043e\u0434\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430  \u0441\u043a\u0440\u044b\u0442\u044b, \u0430 \u0440\u0430\u043d\u044c\u0448\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b\u043e\u0441\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c. <\/p>\n<h2>\u041d\u0435\u043c\u043d\u043e\u0433\u043e \u043a\u0440\u0430\u0441\u043e\u0442\u044b pytest<\/h2>\n<p>\u041d\u0435\u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0440\u0430\u0431\u043e\u0442\u0443, \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0438 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u043b\u0430\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u043c\u043e\u0449\u044c\u044e \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u044b pytest.<\/p>\n<h3>\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0435 \u0442\u0435\u0441\u0442\u044b<\/h3>\n<p>\u0415\u0441\u043b\u0438 \u0432 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u043e\u043c \u0444\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 &#8212; \u0437\u0430\u0447\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0447\u0442\u043e \u0442\u043e \u0434\u0430\u043b\u044c\u0448\u0435 \u0438 \u0442\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f? <code>pytest<\/code> \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f <code>pytest-dependency<\/code>. <\/p>\n<p>\u0422\u0430\u043a \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0439 \u0442\u0435\u0441\u0442, \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0447\u0442\u043e \u0435\u0433\u043e \u043d\u0430\u0434\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c \u043f\u0435\u0440\u0432\u044b\u043c, \u0438 \u043f\u043e\u0442\u043e\u043c \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0442\u0435\u0441\u0442\u0430\u0445 \u0447\u0442\u043e \u043e\u043d\u0438 \u0437\u0430\u0432\u0438\u0441\u044f\u0442 \u043e\u0442 \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0430. \u0415\u0441\u043b\u0438 \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0439 \u0442\u0435\u0441\u0442 \u0443\u043f\u0430\u0434\u0435\u0442 &#8212; \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u044b \u043a\u0430\u043a \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0435.<\/p>\n<pre><code class=\"python\"># test_file.py  ...  @pytest.mark.tryfirst @pytest.mark.dependency() def test_data_exists():     get_config_data()   @pytest.mark.parametrize(\"column_name\", [KEY_FIELD, 'TextData', 'Numbers']) @pytest.mark.dependency(depends=[\"test_data_exists\"]) def test_data_has_column(config_dataset, column_name):     assert column_name in config_dataset.columns  ...<\/code><\/pre>\n<h3>\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u0430<\/h3>\n<p>\u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043f\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0442\u043e\u0447\u043d\u043e\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435 \u0442\u043e\u0433\u043e, \u0433\u0434\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0438 \u043f\u043e\u0447\u0435\u043c\u0443, \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0441\u043f\u043e\u0441\u043e\u0431 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u044d\u0442\u0443 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u0430.<\/p>\n<p>\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432 <code>pytest<\/code> \u043f\u0440\u0435\u0434\u0443\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u0430 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0430 <code>record_property<\/code>. \u041e\u043d\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0442\u0435\u0441\u0442\u0430 \u043f\u0430\u0440\u044b \u043a\u043b\u044e\u0447-\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435. <\/p>\n<pre><code class=\"python\"># test_file.py ...  RE_KEY = re.compile('data\\d+')   @pytest.mark.dependency(depends=[\"test_data_exists\"]) def test_key_value_is_correct(config_record, record_property):     rec, line_no = config_record     column_name = KEY_FIELD      record_property(\"line_no\", line_no)     record_property(\"field_name\", column_name)      val = rec[column_name]     record_property(\"ref\", f\"{val}\")      # validation goes here     assert RE_KEY.fullmatch(val) <\/code><\/pre>\n<p>\u0412 \u043f\u043e\u0441\u043b\u0435\u0434\u0441\u0442\u0432\u0438\u0435 \u0438\u0445 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c.<\/p>\n<pre><code class=\"python\"># validator.py  ...  def run_test(file_name: str, file_type: ConfigType):     ...     # Test runn happens here. collector object holds results.     ...        failed_tests = []     for report in collector.reports:         if 'failed' in report.outcome:                          # Properties from record_property             props = {r[0]: r[1] for r in report.user_properties}                          tst = TestResult(id=report.nodeid,                              outcome=report.outcome,                              rule=\"\".join(report.head_line.split('test_')[-1].split('[')[0]),                                                            # Use properties to enrich response on error                              line_no=props.get('line_no', -1),                              field_name=str(props.get('field_name', '')),                              ref=str(props.get('ref', '')))             failed_tests.append(tst) <\/code><\/pre>\n<p>\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u044b\u0434\u0430\u0432\u0430\u0435\u043c\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u043c, \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043c\u043e\u0436\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043f\u0440\u0435\u0434\u0435\u043b\u044c\u043d\u043e \u0442\u043e\u0447\u043d\u044b\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u044f &#8212; \u0447\u0442\u043e \u0438 \u0433\u0434\u0435 \u0432 \u0444\u0430\u0439\u043b\u0435 \u043d\u0435 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"json\">{   \"file_name\": \"example_type_1.xlsx\",   \"result\": 1,   \"passed_cnt\": 12,   \"failed_cnt\": 1,   \"xfailed_cnt\": 0,   \"skipped_cnt\": 0,   \"total_duration_sec\": 1.0997262001037598,   \"failed_tests\": [     {       \"id\": \"file-type-1\/test_file.py::test_mandatory_value_is_missing[2-TextData]\",       \"outcome\": \"failed\",       \"rule\": \"mandatory_value_is_missing\",       \"line_no\": 4,       \"field_name\": \"TextData\",       \"ref\": \"\"     }   ],   \"version\": \"demo\" }<\/code><\/pre>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0417\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c <code>pytest<\/code> \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432 &#171;\u043d\u0435\u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445&#187; \u0443\u0441\u043b\u043e\u0432\u0438\u044f\u0445 \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0445\u043e\u0442\u044f \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u043b\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0438\u0442\u0435\u0440\u0430\u0446\u0438\u0439. <\/p>\n<p>\u041f\u043b\u044e\u0441\u044b \u0440\u0435\u0448\u0435\u043d\u0438\u044f: <\/p>\n<ul>\n<li>\n<p>\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u043e\u0449\u043d\u0443\u044e \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b pytest \u0438 \u043e\u043f\u044b\u0442 \u0438\u043d\u0436\u0435\u043d\u0435\u0440\u043e\u0432-\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u043e\u0432 \u0434\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0444\u0430\u0439\u043b\u0430\u0445. <\/p>\n<\/li>\n<li>\n<p>\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0442\u0435\u0441\u0442\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0438 \u0437\u0430\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0442\u043e \u0435\u0449\u0435 \u043c\u043e\u0436\u043d\u043e \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c<\/p>\n<ul>\n<li>\n<p>\u0411\u043e\u043b\u044c\u0448\u0438\u0435 \u043d\u0430\u043a\u043b\u0430\u0434\u043d\u044b\u0435 \u0440\u0430\u0441\u0445\u043e\u0434\u044b \u043d\u0430 \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u0430 \u0432 \u043f\u043e\u0434\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0438 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u0431\u043e\u0440\u043a\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u0440\u0438\u0432\u043e\u0434\u044f\u0442 \u043a \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0435 \u0444\u0430\u0439\u043b\u0430 &#8212; \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0438 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043c\u043e\u0436\u0435\u0442 \u0434\u043b\u0438\u0442\u0441\u044f \u043c\u0438\u043d\u0443\u0442\u044b. \u042d\u0442\u043e \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0430\u043a\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 &#8212; \u043d\u043e \u043d\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u0438\u044f\u0442\u043d\u043e.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u0441\u043e\u043a\u0440\u0430\u0442\u0438\u0442\u044c \u043e\u0431\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0440\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>pytest-xdist<\/code> \u0434\u043b\u044f \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432, \u043d\u043e, \u043a \u0441\u043e\u0436\u0430\u043b\u0435\u043d\u0438\u044e \u043d\u0435 \u043f\u043e\u043a\u0430 \u0431\u0435\u0437 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 &#8212; \u0438\u043c\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u043d\u0435 \u0432\u0438\u0434\u043d\u043e \u0432 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0434\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430\u0445.<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u043e\u043b\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u043e\u0433\u043e \u0432 \u0441\u0442\u0430\u0442\u044c\u0435, \u0441 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u043c\u0438 \u0444\u0430\u043b\u043e\u0432 \u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432\u044b\u043b\u043e\u0436\u0435\u043d\u0430 \u0432 <a href=\"https:\/\/github.com\/serhit\/pytest_service\" rel=\"noopener noreferrer nofollow\">\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u043d\u0430 GitHub<\/a>. <br \/>\u041e\u0442 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u044d\u0442\u0443 \u0432\u0435\u0440\u0441\u0438\u044e \u043e\u0442\u0434\u0435\u043b\u044f\u044e\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. <\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/776546\/\"> https:\/\/habr.com\/ru\/articles\/776546\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0442\u0438\u043f\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432 Excel. \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u043f\u043e\u043a\u0440\u044b\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b, \u043a\u0430\u043a \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d\u0430 (\u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446, \u043a\u043e\u043b\u043e\u043d\u043e\u043a \u0442\u0430\u0431\u043b\u0438\u0446) \u0438 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0441\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0445 (\u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439, \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0438\u0435 \u0444\u043e\u0440\u043c\u0430\u0442\u0430\u043c, \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0434\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438, \u0438\u0442\u0434). <\/p>\n<p>\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043d\u0443\u0436\u043d\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e &#171;\u0447\u0442\u043e \u043d\u0435 \u0442\u0430\u043a \u0441 \u0444\u0430\u0439\u043b\u043e\u043c&#187;: \u043a\u0430\u043a\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u043d\u0435 \u043f\u0440\u043e\u0448\u0435\u043b \u0444\u0430\u0439\u043b \u0438 \u0433\u0434\u0435 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u0432 \u0444\u0430\u0439\u043b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435. <\/p>\n<p>\u042d\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043e\u0447\u0435\u043d\u044c \u043d\u0430\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 &#8212; \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0430 \u0434\u0430\u043d\u043d\u044b\u0445. \u0422\u0430\u043a \u043f\u043e\u0447\u0435\u043c\u0443 \u0431\u044b \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f pytest, \u0438 \u043d\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u044b\u0439 \u0430\u0441\u043f\u0435\u043a\u0442 \u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u0444\u0430\u0439\u043b\u043e\u0432? \u041e\u0434\u043d\u0430\u043a\u043e, \u0435\u0441\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0435 &#171;\u043d\u043e&#187;: \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0434\u043e\u043b\u0436\u043d\u0430 \u0431\u044b\u0442\u044c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u0430 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u0447\u0442\u043e\u0431\u044b \u0432\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0431\u043e\u043b\u0435\u0435 \u0448\u0438\u0440\u043e\u043a\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432. <\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c <code>pytest<\/code> \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u042d\u0442\u043e \u043d\u0435 \u0442\u0430\u043a \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u043e, \u043a\u0430\u043a \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u043f\u0435\u0440\u0432\u044b\u0439 \u0432\u0437\u0433\u043b\u044f\u0434.<\/p>\n<h2>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e &#8212; \u043d\u0435\u043c\u043d\u043e\u0433\u043e &#171;\u043f\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u0444\u0430\u0439\u043b&#187;<\/h2>\n<p>\u0414\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f, \u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 <code>pytest<\/code> \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u0444\u0430\u0439\u043b\u0430 \u0432\u043e\u043e\u0431\u0449\u0435 \u0438\u043c\u0435\u0435\u0442 \u0441\u043c\u044b\u0441\u043b. \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u0444\u0430\u0439\u043b \u0442\u0435\u0441\u0442\u0430 \u0438 \u043f\u043e\u0433\u043b\u044f\u0434\u0438\u043c, \u043a\u0430\u043a \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442.<\/p>\n<p>\u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u043c \u0444\u0430\u0439\u043b \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 &#171;Config&#187;. \u041d\u0430 \u043d\u0435\u0439 \u043f\u0440\u043e\u0441\u0442\u0430\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0438\u0437 \u0442\u0440\u0435\u0445 \u043a\u043e\u043b\u043e\u043d\u043e\u043a: &#171;Code&#187;, &#171;TextData&#187;, &#171;Numbers&#187; (<a href=\"https:\/\/github.com\/serhit\/pytest_service\/blob\/main\/examples\/example_type_1.xlsx\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u043e\u0442 \u0444\u0430\u0439\u043b \u043b\u0435\u0436\u0438\u0442 \u0437\u0434\u0435\u0441\u044c<\/a>).<\/p>\n<p>\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0447\u0442\u043e:<\/p>\n<ol>\n<li>\n<p>\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c \u0438\u043c\u0435\u043d\u0435\u043c \u0435\u0441\u0442\u044c<\/p>\n<\/li>\n<li>\n<p>\u0412 \u0442\u0430\u0431\u043b\u0438\u0446\u0435 \u043d\u0430 \u044d\u0442\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u0435\u0441\u043b\u0438 \u0435\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e <code>pandas<\/code> \u0435\u0441\u0442\u044c \u0432\u0441\u0435 \u043a\u043e\u043b\u043e\u043d\u043a\u0438 \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u043e\u0436\u0438\u0434\u0430\u0435\u043c.<\/p>\n<\/li>\n<li>\n<p>\u0412 \u043a\u043e\u043b\u043e\u043d\u043a\u0430\u0445 &#171;Code&#187;, &#171;TextData&#187; \u043a\u0430\u0436\u0434\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0434\u0430\u043d\u043d\u044b\u0435. \u042d\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043c\u0435\u0440 &#171;\u043f\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u043e\u0439 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438&#187;. \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u0435\u0435 &#8212; \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0432 \u0441\u0435\u0442 \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u041d\u043e \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043c\u043d\u0438\u0442\u044c, \u0447\u0442\u043e \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0441\u043b\u043e\u0436\u043d\u0435\u0435 &#8212; \u0437\u0430\u0442\u0440\u0430\u0433\u0438\u0432\u0430\u044e \u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u044f \u043c\u0435\u0436\u0434\u0443 \u0441\u0442\u043e\u043b\u0431\u0446\u0430\u043c\u0438, \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u0442\u043e\u043b\u0431\u0446\u044b, \u0438\u0442\u0434.<\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u043a\u043e\u0434 \u0442\u0435\u0441\u0442\u0430 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043a\u0430\u043a \u043d\u0430 \u0432\u0441\u0442\u0430\u0432\u043a\u0435. \u0414\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u043a\u0430 \u0443\u043a\u0430\u0436\u0435\u043c \u043f\u0440\u044f\u043c\u0443\u044e \u0441\u0441\u044b\u043b\u043a\u0443 \u043d\u0430 \u0444\u0430\u0439\u043b.<\/p>\n<pre><code class=\"python\">import pytest import pandas   SHEET_NAME = 'Config' TABLE_OFFSET = 0 KEY_FIELD = 'Code'   def get_config_data():     data = pandas.read_excel(\"&lt;static path to file>\",                               sheet_name=SHEET_NAME,                               skiprows=TABLE_OFFSET)     return data  # Generate array of rows - to parametrize tests try:     config_data_records = [rec for rec in get_config_data().iterrows() if str(rec[1][KEY_FIELD]) != 'nan']     config_indexes = [rec[0] for rec in config_data_records] except:     config_data_records = None     config_indexes = None    @pytest.fixture() def config_dataset():     return get_config_data()  @pytest.fixture(params=config_data_records, ids=config_indexes) def config_record(request):     return request.param[1], request.param[0] + TABLE_OFFSET + 2  # Excel rows start with 1, plus caption, plus offset     @pytest.mark.tryfirst def test_data_exists():     get_config_data()  # Test applies for whole rowset @pytest.mark.parametrize(\"column_name\", [KEY_FIELD, 'TextData', 'Numbers']) def test_data_has_column(config_dataset, column_name):     assert column_name in config_dataset.columns  # Test applies for each row of the file @pytest.mark.parametrize(\"column_name\", [KEY_FIELD, 'TextData']) def test_mandatory_value_is_missing(config_record, column_name):     rec, line_no = config_record     assert not any(rec[[column_name]].isna())<\/code><\/pre>\n<p>\u0422\u0435\u0441\u0442\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u0438 \u043b\u043e\u0432\u044f\u0442 \u043e\u0448\u0438\u0431\u043a\u0438, \u0433\u0434\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e. \u041f\u043e\u043a\u0430 \u0432\u0441\u0435 \u0445\u043e\u0440\u043e\u0448\u043e.<\/p>\n<h2>\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0439 \u0432\u044b\u0437\u043e\u0432 pytest<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0442\u0435\u0441\u0442\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e. \u0412 <a href=\"https:\/\/happytest.readthedocs.io\/en\/latest\/reference\/#pytest-main\" rel=\"noopener noreferrer nofollow\">\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u0443 pytest \u044d\u0442\u043e \u043e\u043f\u0438\u0441\u0430\u043d\u043e<\/a> &#8212; \u043c\u043e\u0436\u043d\u043e \u0434\u0435\u043b\u0430\u0442\u044c.<\/p>\n<pre><code class=\"python\">files = ['&lt;static testfile.py>']  pytest.main(files)<\/code><\/pre>\n<h3>\u0421\u0431\u043e\u0440 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f<\/h3>\n<p>\u0427\u0442\u043e\u0431\u044b \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0430\u043a, <a href=\"https:\/\/stackoverflow.com\/questions\/72277284\/retrieve-pytest-results-programmatically-when-run-via-pytest-main\" rel=\"noopener noreferrer nofollow\">\u043a\u0430\u043a \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u043d\u043e \u0437\u0434\u0435\u0441\u044c \u043d\u0430 stackoverflow<\/a>. \u041f\u0440\u0438\u0434\u0443\u043c\u044b\u0432\u0430\u0442\u044c \u043d\u0435\u0447\u0435\u0433\u043e \u043d\u043e\u0432\u043e\u0433\u043e \u043d\u0435 \u0431\u0443\u0434\u0435\u043c &#8212; \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0443.<\/p>\n<pre><code class=\"python\"># Definition of ResultsCollector goes here  collector = ResultsCollector() files = ['&lt;static testfile.py>']  pytest.main(files, plugins=[collector])  # Process collected results<\/code><\/pre>\n<h3>\u041f\u0435\u0440\u0435\u0434\u0430\u0447\u0430 \u0438\u043c\u0435\u043d\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430<\/h3>\n<p>\u0412\u0440\u043e\u0434\u0435 \u0432\u0441\u0435 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435. \u041e\u0434\u043d\u0430\u043a\u043e, \u043f\u043e\u0440\u0430 \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c &#171;\u0434\u0438\u043d\u0430\u043c\u0438\u043a\u0438&#187; &#8212; \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0438\u043c\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0432\u043d\u0443\u0442\u0440\u044c \u0442\u0435\u0441\u0442\u0430.<\/p>\n<p>\u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0432 \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u043f\u043b\u0430\u0433\u0438\u043d, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u043d\u043e\u0432\u044b\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438 \u0441 \u0438\u043c\u0435\u043d\u0435\u043c \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0438 \u043f\u043e\u0442\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0435\u0433\u043e \u0432 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e &#8212; \u0432 \u043f\u0430\u043a\u0435\u0442 <code>pytest<\/code><\/p>\n<pre><code class=\"python\">class FileRef:     def __init__(self):         self.file_name = None      @pytest.hookimpl     def pytest_addoption(self, parser):         parser.addoption(\"--file\", action=\"store\")      @pytest.hookimpl     def pytest_configure(self, config):         self.file_name = config.option.file         pytest.data_file_name = config.option.file<\/code><\/pre>\n<p>\u0422\u043e\u0433\u0434\u0430 \u0437\u0430\u043f\u0443\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432 \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u0442\u0430\u043a:<\/p>\n<pre><code class=\"python\">file_to_test = \"&lt;path to excel file>\"  collector = ResultsCollector() file_ref = FileRef() plugins = [collector, file_ref]  files = ['&lt;static testfile.py>', '--file', file_to_test]  pytest.main(files, plugins=plugins)  # Process collected results<\/code><\/pre>\n<p>\u0430 \u0432 \u0441\u0430\u043c\u043e\u043c \u0442\u0435\u0441\u0442\u0435 \u043e\u0441\u0442\u0430\u043d\u0435\u0442\u0441\u044f \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043d\u0430 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e:<\/p>\n<pre><code class=\"python\">...  def get_config_data():     data = pandas.read_excel(pytest.data_file_name, # Now this will come from the parameter                              sheet_name=SHEET_NAME,                               skiprows=TABLE_OFFSET)     return data  ...<\/code><\/pre>\n<h4>\u041f\u0440\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445<\/h4>\n<p>\u041a\u0441\u0442\u0430\u0442\u0438, \u0435\u0441\u0442\u044c \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432, \u0433\u0434\u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u0435, \u043a\u0430\u043a \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 <a href=\"https:\/\/stackoverflow.com\/questions\/40880259\/how-to-pass-arguments-in-pytest-by-command-line\" rel=\"noopener noreferrer nofollow\">\u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0437\u0434\u0435\u0441\u044c<\/a>. \u041e\u0434\u043d\u0430\u043a\u043e, \u0434\u043b\u044f \u043d\u0430\u0448\u0438\u0445 \u0446\u0435\u043b\u0435\u0439 \u044d\u0442\u043e\u0442 \u0441\u043f\u043e\u0441\u043e\u0431 \u043d\u0435 \u043f\u043e\u0434\u043e\u0439\u0434\u0435\u0442.<\/p>\n<p>\u041f\u043e \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0445\u043e\u0434\u0438\u0442 &#171;\u0434\u0435\u0440\u0435\u0432\u043e \u0441\u043e\u0431\u044b\u0442\u0438\u0439&#187; <code>pytest<\/code>, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u0435\u0440\u0435\u043f\u0435\u0447\u0430\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u0441\u0442\u0430\u0442\u044c\u044e (<a href=\"https:\/\/github.com\/pytest-dev\/pytest\/issues\/3261#issuecomment-755461808\" rel=\"noopener noreferrer nofollow\">\u043e\u0434\u043d\u043e \u0438\u0437 \u043f\u0435\u0440\u0432\u044b\u0445 \u0443\u043f\u043e\u043c\u0438\u043d\u0430\u043d\u0438\u0439<\/a>, <a href=\"https:\/\/stackoverflow.com\/questions\/65617105\/need-order-and-action-of-pytest-hooks-execution\" rel=\"noopener noreferrer nofollow\">\u0441\u0441\u044b\u043b\u043a\u0438<\/a>, <a href=\"https:\/\/paragkamble.medium.com\/understanding-hooks-in-pytest-892e91edbdb7\" rel=\"noopener noreferrer nofollow\">\u043f\u0435\u0440\u0435\u043f\u0435\u0447\u0430\u0442\u043a\u0438<\/a>) . \u042d\u0442\u043e &#171;\u0434\u0435\u0440\u0435\u0432\u043e&#187; \u0445\u043e\u0440\u043e\u0448\u043e \u0438\u043c\u0435\u0442\u044c \u043f\u043e\u0434 \u0440\u0443\u043a\u043e\u0439 \u043f\u0440\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432.<\/p>\n<figure class=\"bordered\"><\/figure>\n<p>\u0424\u0438\u043a\u0441\u0442\u0443\u0440\u044b &#171;\u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f&#187; \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u0444\u0430\u0437\u0435 &#171;collection&#187; (2) &#8212; \u043d\u0430 \u0442\u043e\u0439 \u0436\u0435 \u0444\u0430\u0437\u0435 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0441\u0431\u043e\u0440 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u0438\u0445 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u044f (\u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0447\u0435\u0433\u043e \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f &#171;\u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u0442\u0435\u0441\u0442\u043e\u0432&#187;. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0437\u0430\u0440\u0430\u043d\u0435\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0444\u0430\u0439\u043b \u0447\u0442\u043e\u0431\u044b \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0435\u0433\u043e \u0441\u0442\u0440\u043e\u043a\u0438 \u0438 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043c\u0430\u0441\u0441\u0438\u0432 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0440\u043e\u0447\u043d\u043e\u0439 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f.<\/p>\n<p>\u0414\u043e\u0441\u0442\u0443\u043f \u043a \u0444\u0430\u0439\u043b\u0443 \u043d\u0443\u0436\u0435\u043d \u0443\u0436\u0435 \u043d\u0430 \u0441\u0442\u0430\u0434\u0438\u0438 &#171;configure&#187; (1).<\/p>\n<h2>\u0412\u0441\u0435 &#8212; \u0432 \u0441\u0435\u0440\u0432\u0438\u0441<\/h2>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0447\u0430\u0442\u044c \u0443\u043f\u0430\u043a\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0432 \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0438\u0445 \u0437\u0430\u043f\u0443\u0441\u043a\u0430. \u0427\u0442\u043e\u0431\u044b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (\u0430 \u0442\u043e\u0447\u043d\u0435\u0435 &#8212; &#171;\u0443\u043f\u0430\u0432\u0448\u0438\u0445&#187; \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u043a), \u0443\u0434\u043e\u0431\u043d\u043e \u043e\u0431\u044a\u044f\u0432\u0438\u0442\u044c \u043e\u0431\u0449\u0438\u0435 \u0442\u0438\u043f\u044b. <\/p>\n<pre><code class=\"python\"># common_types.py  from enum import Enum from pydantic import BaseModel from typing import List, Optional   class ConfigType(str, Enum):     file_type_1 = 'file-type-1'     file_type_2 = 'file-type-2'   class TestResult(BaseModel):     id: str     outcome: str     rule: str     line_no: Optional[int] = None     field_name: Optional[str] = None     ref: Optional[str] = None   class FileValidationResult(BaseModel):     file_name: str     result: int     passed_cnt: int     failed_cnt: int     xfailed_cnt: int     skipped_cnt: int     total_duration_sec: float     failed_tests: List[TestResult]     version: str = None<\/code><\/pre>\n<p>\u042d\u0442\u0438 \u0442\u0438\u043f\u044b \u043f\u0440\u0438\u0433\u043e\u0434\u044f\u0442\u0441\u044f \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u0418\u0445 \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0432\u044b\u0437\u043e\u0432\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u0430.<\/p>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0444\u0430\u0439\u043b \u0438 \u0435\u0433\u043e \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u044b\u0439 \u0442\u0438\u043f, \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0435\u0433\u043e \u0432\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0438\u043c\u044f \u044d\u0442\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0442\u0438\u043f\u043e\u043c \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<pre><code class=\"python\"># main.py  ...  @app.post('\/validate_file', response_model=FileValidationResult) async def validate_file(config_type: ConfigType,                         file: UploadFile = File()                         ):      _, file_extension = os.path.splitext(file.filename)      with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as _file:         _content = await file.read()         _file.write(_content)         _tmp_file_name = _file.name      res = validator.run_test(_tmp_file_name, config_type)     res.file_name = file.filename     res.version = __version__      os.remove(_tmp_file_name)      return res<\/code><\/pre>\n<p>\u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0430\u043d\u0438\u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0431\u0438\u0440\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f. \u041a\u043e\u043d\u0435\u0447\u043d\u043e \u0431\u043e\u043b\u0435\u0435 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u0441\u043a\u043b\u0430\u0434\u044b\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u0432 \u043e\u0431\u0449\u0443\u044e \u043f\u0430\u043f\u043a\u0443 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0432  <code>pytest<\/code> \u0435\u0435 \u0446\u0435\u043b\u0438\u043a\u043e\u043c. \u0410 \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0441\u0432\u044f\u0437\u043a\u0443 \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430 \u0438 \u0442\u0438\u043f\u0430. <\/p>\n<pre><code class=\"python\"># validator.py  ...  def run_test(file_name: str, file_type: ConfigType):          ...        _folder = file_type.value     if os.path.isdir(_folder):         # If we have a folder with the name matching file type - we take it         files = [_folder]     else:         # If no folder defined - search files with names related to file type         _ft = file_type.value.replace('-', '_')         files = glob.glob(f'test_{_ft}*.py')      # Add parameter for name of the file being tested     files += ['--file', file_name]      # and run found tests against the given file. Plugins will help.     pytest.main(files, plugins=plugins)      # Process results of tests based on the internal state of `collector` instance     ...     <\/code><\/pre>\n<p>\u041e\u0447\u0435\u0432\u0438\u0434\u043d\u043e\u0435 \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435 \u043f\u0440\u043e \u043f\u0430\u043f\u043a\u0443 \u0441 \u0444\u0430\u0439\u043b\u0430\u043c\u0438 \u0442\u0435\u0441\u0442\u043e\u0432 \u043f\u0440\u043e\u0442\u0438\u0432 \u043f\u0440\u043e\u0441\u0442\u043e \u0444\u0430\u0439\u043b\u043e\u0432 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438. \u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u043f\u0430\u043f\u043a\u0438, \u043f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u043d\u043e\u0432\u044b\u0445 \u0442\u0438\u043f\u043e\u0432 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u043e\u0434\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0438 Dockerfile. \u041f\u0440\u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u043a\u043e\u0440\u0435\u043d\u044c \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0442\u0430\u043a\u043e\u0433\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u0442 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e.<\/p>\n<pre><code>FROM python:3.11-slim  COPY requirements.txt \/app\/ WORKDIR \/app\/ RUN pip install --no-cache-dir -r requirements.txt  # Install application COPY *.py \/app\/  RUN mkdir {file-type-1, file-type-2}  COPY file-type-1\/*.py \/app\/file-type-1\/ COPY file-type-2\/*.py \/app\/file-type-2\/  EXPOSE 8000  ENTRYPOINT [\"uvicorn\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\", \"--workers\", \"1\", \"main:app\"]<\/code><\/pre>\n<h2>\u0418 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0431\u044b \u0432\u0441\u0435 \u0433\u043e\u0442\u043e\u0432\u043e, \u043d\u043e &#8230;<\/h2>\n<p>\u0421\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u0444\u0430\u0439\u043b, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435, \u0432\u044b\u0434\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0432 \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435.<\/p>\n<p>\u041e\u0434\u043d\u0430\u043a\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u043e\u043a\u0430\u0436\u0435\u0442, \u0447\u0442\u043e \u0435\u0441\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u043e\u0434\u0441\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0435\u043c\u0443 \u0440\u0430\u0437\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b &#8212; \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u043e\u0440\u043d\u043e \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u0435\u0440\u0432\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430. <\/p>\n<p>\u041f\u0440\u0438\u0447\u0438\u043d\u0430 &#171;\u043f\u0440\u043e\u0441\u0442\u0430\u044f&#187;. \u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f <code>pytest<\/code> \u043d\u0430\u0441\u0442\u043e\u043b\u044c\u043a\u043e \u0433\u043b\u0443\u0431\u043e\u043a\u043e \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438 \u043a\u044d\u0448\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u0438 \u0444\u0438\u043a\u0441\u0442\u0443\u0440, \u0447\u0442\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0439 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a \u0432 \u043e\u0434\u043d\u043e\u043c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u0435\u043d. \u041e\u0431 \u044d\u0442\u043e\u043c <a href=\"https:\/\/docs.pytest.org\/en\/latest\/how-to\/usage.html\" rel=\"noopener noreferrer nofollow\">\u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u0430\u044f \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f<\/a> \u0441\u043e\u043e\u0431\u0449\u0430\u0435\u0442 &#8212; \u043d\u043e \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043e\u0431\u043e\u0439\u0442\u0438 \u044d\u0442\u043e \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-361244","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/361244","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=361244"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/361244\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=361244"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=361244"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=361244"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}