diff --git a/.gitignore b/.gitignore index 5a1d27b4..5dc7a5ea 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ dist/* *.egg-info .python-version logs/% -.coverage \ No newline at end of file +.coverage +locustfile.py \ No newline at end of file diff --git a/README.md b/README.md index c621c412..c7d67baa 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,8 @@ To ensure the installation or upgrade is successful, you can execute command `at ```text $ ate -V -jenkins-mail-py version: 0.2.4 -ApiTestEngine version: 0.3.3 +jenkins-mail-py version: 0.2.5 +ApiTestEngine version: 0.4.0 ``` Execute the command `ate -h` to view command help. @@ -213,10 +213,45 @@ $ ate filepath/testcase.yml --report-name ${BUILD_NUMBER} \ --jenkins-build-number ${BUILD_NUMBER} ``` +## Performance test + +With reuse of [`Locust`][Locust], you can run performance test without extra work. + +```bash +$ ate-locust -V +Locust 0.8a2 +``` + +For full usage, you can run `ate-locust -h` to see help, and you will find that it is the same with `locust -h`. + +The only difference is the `-f` argument. If you specify `-f` with a Python locustfile, it will be the same as `locust`, while if you specify `-f` with a `YAML/JSON` testcase file, it will convert to Python locustfile first and then pass to `locust`. + +```bash +$ ate-locust -f examples/first-testcase.yml +[2017-08-18 17:20:43,915] Leos-MacBook-Air.local/INFO/locust.main: Starting web monitor at *:8089 +[2017-08-18 17:20:43,918] Leos-MacBook-Air.local/INFO/locust.main: Starting Locust 0.8a2 +``` + +In this case, you can reuse all features of [`Locust`][Locust]. + +Enjoy! + ## Supported Python Versions Python `2.7`, `3.3`, `3.4`, `3.5`, `3.6` and `3.7-dev`. +`ApiTestEngine` has been tested on `macOS`, `Linux` and `Windows` platforms. + +## Development + +To develop or debug `ApiTestEngine`, you can install relevant requirements and use `main-ate.py` or `main-locust.py` as entrances. + +```bash +$ pip install -r requirements_dev.txt +$ python main-ate -h +$ python main-locust -h +``` + ## To learn more ... - [《接口自动化测试的最佳工程实践(ApiTestEngine)》](http://debugtalk.com/post/ApiTestEngine-api-test-best-practice/) diff --git a/ate/__init__.py b/ate/__init__.py index a8a1bf6e..222c11cf 100644 --- a/ate/__init__.py +++ b/ate/__init__.py @@ -1 +1 @@ -__version__ = '0.3.4' \ No newline at end of file +__version__ = '0.4.0' \ No newline at end of file diff --git a/ate/cli.py b/ate/cli.py index 5283a763..3453b26c 100644 --- a/ate/cli.py +++ b/ate/cli.py @@ -1,15 +1,17 @@ import argparse +import codecs import logging import os +import sys from collections import OrderedDict - import PyUnitReport + from ate import __version__ from ate.task import create_task -def main(): - """ parse command line options and run commands. +def main_ate(): + """ API test: parse command line options and run commands. """ parser = argparse.ArgumentParser( description='Api Test Engine.') @@ -81,3 +83,65 @@ def main(): mailer.send_mail(subject, results, flag_code) return flag_code + +def main_locust(): + """ Performance test with locust: parse command line options and run commands. + """ + try: + from locust.main import main + except ImportError: + print("Locust is not installed, exit.") + exit(1) + + sys.argv[0] = 'locust' + if len(sys.argv) == 1: + sys.argv.extend(["-h"]) + + if sys.argv[1] in ["-h", "--help", "-V", "--version"]: + main() + sys.exit(0) + + try: + testcase_index = sys.argv.index('-f') + 1 + assert testcase_index < len(sys.argv) + except (ValueError, AssertionError): + print("Testcase file is not specified, exit.") + sys.exit(1) + + testcase_file_path = sys.argv[testcase_index] + sys.argv[testcase_index] = parse_locustfile(testcase_file_path) + main() + +def parse_locustfile(file_path): + """ parse testcase file and return locustfile path. + if file_path is a Python file, assume it is a locustfile + if file_path is a YAML/JSON file, convert it to locustfile + """ + if not os.path.isfile(file_path): + print("file path invalid, exit.") + sys.exit(1) + + file_suffix = os.path.splitext(file_path)[1] + if file_suffix == ".py": + locustfile_path = file_path + elif file_suffix in ['.yaml', '.yml', '.json']: + locustfile_path = gen_locustfile(file_path) + else: + # '' or other suffix + print("file type should be YAML/JSON/Python, exit.") + sys.exit(1) + + return locustfile_path + +def gen_locustfile(testcase_file_path): + """ generate locustfile from template. + """ + locustfile_path = 'locustfile.py' + with codecs.open('ate/locustfile_template', encoding='utf-8') as template: + with codecs.open(locustfile_path, 'w', encoding='utf-8') as locustfile: + template_content = template.read() + template_content = template_content.replace("$HOST", "https://skypixel.com") + template_content = template_content.replace("$TESTCASE_FILE", testcase_file_path) + locustfile.write(template_content) + + return locustfile_path diff --git a/ate/locustfile_template b/ate/locustfile_template new file mode 100644 index 00000000..11a7c478 --- /dev/null +++ b/ate/locustfile_template @@ -0,0 +1,26 @@ +#coding: utf-8 +import zmq +import os +from locust import HttpLocust, TaskSet, task +from ate import utils, runner, exception + +class WebPageTasks(TaskSet): + def on_start(self): + self.test_runner = runner.Runner(self.client) + self.testset = self.locust.testset + + @task + def test_specified_scenario(self): + try: + self.test_runner.run_testset(self.testset) + except exception.ValidationError: + pass + +class WebPageUser(HttpLocust): + host = "$HOST" + task_set = WebPageTasks + min_wait = 1000 + max_wait = 5000 + + testsets = utils.load_testcases_by_path("$TESTCASE_FILE") + testset = testsets[0] diff --git a/main-ate.py b/main-ate.py new file mode 100644 index 00000000..4146b317 --- /dev/null +++ b/main-ate.py @@ -0,0 +1,5 @@ +""" used for debugging +""" + +from ate.cli import main_ate +main_ate() diff --git a/main-locust.py b/main-locust.py new file mode 100644 index 00000000..e85d4410 --- /dev/null +++ b/main-locust.py @@ -0,0 +1,5 @@ +""" used for debugging +""" + +from ate.cli import main_locust +main_locust() diff --git a/main.py b/main.py deleted file mode 100644 index 18fdc38c..00000000 --- a/main.py +++ /dev/null @@ -1,2 +0,0 @@ -from ate.cli import main -main() \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt index 0b2c72d8..d9c5fdd7 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,3 +5,4 @@ coveralls coverage -e git+https://github.com/debugtalk/PyUnitReport.git#egg=PyUnitReport -e git+https://github.com/debugtalk/jenkins-mail-py.git#egg=jenkins-mail-py +-e git+https://github.com/locustio/locust.git#egg=locustio \ No newline at end of file diff --git a/setup.py b/setup.py index 744401a9..c68fcc18 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,9 @@ setup( url='https://github.com/debugtalk/ApiTestEngine', license='MIT', packages=find_packages(exclude=['test.*', 'test']), + package_data={ + 'ate': ['locustfile_template'], + }, keywords='api test', install_requires=[ "requests", @@ -29,11 +32,15 @@ setup( extras_require={ 'mail': [ "jenkins-mail-py" + ], + 'locust': [ + "locustio" ] }, dependency_links=[ "git+https://github.com/debugtalk/PyUnitReport.git#egg=PyUnitReport-0", - "git+https://github.com/debugtalk/jenkins-mail-py.git#egg=jenkins-mail-py-0" + "git+https://github.com/debugtalk/jenkins-mail-py.git#egg=jenkins-mail-py-0", + "git+https://github.com/locustio/locust.git#egg=locust-0" ], classifiers=[ "Development Status :: 3 - Alpha", @@ -46,7 +53,8 @@ setup( ], entry_points={ 'console_scripts': [ - 'ate=ate.cli:main' + 'ate=ate.cli:main_ate', + 'ate-locust=ate.cli:main_locust' ] } )