How to do automated testing if you can't program

Posted by dpsd on Wed, 10 Nov 2021 19:32:40 +0100

preface

Testers who can test and understand programming are still scarce, and most organizations may not invest in this aspect. Therefore, let ordinary test engineers simply learn to carry out automated testing, which is still in market demand.

Before, I was confused that ordinary test engineers could not deeply participate in the case preparation of automated testing, so I searched and found a pytest plug-in called pytest play. Using this plug-in, you can carry out automated work as long as you write YAML files according to the rules, and there is almost no introduction to the plug-in in in Chinese. Therefore, I specially wrote this article, Let me give you an in-depth introduction.

What can you do

Today's protagonist is pytest play, which is a plug-in of a well-known automated testing framework pytest. So, what can this plug-in do for us?

According to the official documents, pytest play can be used to do some automated work, including automated testing.

What are the characteristics

There are many tools that can do automated testing. What are the characteristics of pytest play?

First, non-technical users are available. This should be the biggest feature and advantage of pytest play. For the majority of testers who are not proficient in programming, as long as they learn some relatively simple syntax rules, they can complete the automatic test task by writing clear format text files, which is still very fragrant.

Secondly, test configuration. As we know, YAML is a good file format. We must have used it to write the data and parameters of test cases. When using pytest play, we just need to write the test actions, data and assertions.

Thirdly, it is extensible. Pytest play itself, as an extension of pytest, is designed to be extensible. At present, play is known_ Selenium plug-in, which can drive the browser for Web page testing, with play_ The requests driver initiates HTTP requests for HTTP interface testing and plays_ SQL can execute SQL and assert the results. Of course, you can also develop your own plug-ins.

The above are the main features of pytest play. Other features are easy to install, easy to use, parameterization, integration report, integration with test management tools, smooth learning curve and so on.

How to use

Pytest play can be used in two ways, one without Python and the other. This can meet the needs of different levels.

With test_ The file with the yml extension at the beginning will be automatically recognized and run, or the entire test set can be run with the name or keyword.

No Python mode

The following is an example of a Python free test project, which contains only an optional environment variable file env-ALPHA.yml and a test script file test_login.yml.

$ tree
.
├── env-ALPHA.yml    (OPTIONAL)
└── test_login.yml

The following is the content of the environment variable configuration file. As external parameters, it can be used to run tests in different environments.

$ cat env-ALPHA.yml
pytest-play:
  base_url: https://www.yoursite.com

The following is a test plan file that contains actions, assertions, and metadata (optional).

$ cat test_login.yml
---
markers:
  - login
test_data:
  - username: siteadmin
    password: siteadmin
  - username: editor
    password: editor
  - username: reader
    password: reader
---
- comment: Access the initial page
  type: get
  provider: selenium
  url: "$base_url"
- comment: Click the login link
  locator:
    type: id
    value: personaltools-login
  type: clickElement
  provider: selenium
- comment: enter one user name
  locator:
    type: id
    value: __ac_name
  text: "$username"
  type: setElementText
  provider: selenium
- comment: Input password
  locator:
    type: id
    value: __ac_password
  text: "$password"
  type: setElementText
  provider: selenium
- comment: Click the login button
  locator:
    type: css
    value: ".pattern-modal-buttons > input[name=submit]"
  type: clickElement
  provider: selenium
- comment: Wait for the page to load
  locator:
    type: css
    value: ".icon-user"
  type: waitForElementVisible
  provider: selenium

The sample file is divided into two sections with -.

The first segment is the metadata segment, which is not required. You can use markers to set several tags for the current test, which is convenient to filter and run the test according to the tags. You can also use test_data configures the case run data. In this example, three groups of test data are configured, and the whole test will run three times.

The second paragraph is the test script, which uses - to configure five behaviors.
Comment: a comment that briefly describes this item. It can be output to the report for easy viewing and editing. It is not necessary.
provider: used to specify the command to be executed. In this case, selenium will call the selenium plug-in.
type: it can be used as a subcommand of the provider. For example, waitForElementVisible is waiting for HTML elements to be visible.
locator: used to locate HTML elements. type is the location method and value is the location parameter.

Python mode

This method can be used for behavior driven testing using pytest BDD.

The following example is test_ The content of login.py defines the name test_ The login function, called the data test scheme, finally calls the execute_ of play. The raw method runs, extra_ The variables parameter is an environment variable class parameter.

def test_login(play):
    data = play.get_file_contents(
        'my', 'path', 'etc', 'login.yml')
    play.execute_raw(data, extra_variables={})

Operation result report

Use the – junit XML parameter on the command line to generate a result report in junit format.

--junit-xml results.xml

In the report, you can see the errors of each test case, the commands executed each time, and the time consumption. You will see each command line output by default, unless the - s or – capture=no parameter is specified at run time.

The following is an example of an execution report.

<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="0" failures="0" name="pytest" skipped="0" tests="1" time="0.360">
	<testcase classname="test_assertion.yml" file="test_assertion.yml" name="test_assertion.yml" time="0.326">
		<system-out>{'expression': '1 == 1', 'provider': 'python', 'type': 'assert', '_elapsed': 0.0003077983856201172}
{'expression': '0 == 0', 'provider': 'python', 'type': 'assert', '_elapsed': 0.0002529621124267578}
		</system-out>
	</testcase>
</testsuite>

The result report can be customized. You can output custom attributes and execution time in the report.

Play is used below_ In the example where the requests plug-in tests the HTTP interface, a custom attribute configuration for collecting indicators is inserted before the assertion. The provider is metrics and the custom attribute name is categories_time, the corresponding indicator type is record_elapsed (similar time). In addition, the example asserts against custom attributes.

test_data:
  - category: dev
  - category: movie
  - category: food
---
- type: GET
  provider: play_requests
  url: https://api.chucknorris.io/jokes/categories
  expression: "'$category' in response.json()"
- provider: metrics
  type: record_elapsed
  name: categories_time
- type: assert
  provider: python
  expression: "variables['categories_time'] < 2.5"
  comment: You can use it in assertions categories_time

The output report is as follows. As you can see, categories are output_ Time custom attribute and its value.

<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="0" failures="0" name="pytest" skipped="0" tests="3" time="2.031">
	<testcase classname="test_categories.yml" file="test_categories.yml" name="test_categories.yml0" time="0.968">
		<properties>
			<property name="categories_time" value="0.5829994678497314"/>
		</properties>
		<system-out>{'expression': "'dev' in response.json()", 'provider': 'play_requests', 'type': 'GET', 'url': 'https://api.chucknorris.io/jokes/categories', '_elapsed': 0.5829994678497314}
{'name': 'categories_time', 'provider': 'metrics', 'type': 'record_elapsed', '_elapsed': 3.3855438232421875e-05}
{'comment': 'You can use it in assertions categories_time', 'expression': "variables['categories_time'] < 2.5", 'provider': 'python', 'type': 'assert', '_elapsed': 0.0006382465362548828}
		</system-out>
	</testcase>
	<testcase classname="test_categories.yml" file="test_categories.yml" name="test_categories.yml1" time="0.481">
		<properties>
			<property name="categories_time" value="0.4184422492980957"/>
		</properties>
		<system-out>{'expression': "'movie' in response.json()", 'provider': 'play_requests', 'type': 'GET', 'url': 'https://api.chucknorris.io/jokes/categories', '_elapsed': 0.4184422492980957}
{'name': 'categories_time', 'provider': 'metrics', 'type': 'record_elapsed', '_elapsed': 2.09808349609375e-05}
{'comment': 'You can use it in assertions categories_time', 'expression': "variables['categories_time'] < 2.5", 'provider': 'python', 'type': 'assert', '_elapsed': 0.000553131103515625}
		</system-out>
	</testcase>
	<testcase classname="test_categories.yml" file="test_categories.yml" name="test_categories.yml2" time="0.534">
		<properties>
			<property name="categories_time" value="0.463592529296875"/>
		</properties>
		<system-out>{'expression': "'food' in response.json()", 'provider': 'play_requests', 'type': 'GET', 'url': 'https://api.chucknorris.io/jokes/categories', '_elapsed': 0.463592529296875}
{'name': 'categories_time', 'provider': 'metrics', 'type': 'record_elapsed', '_elapsed': 2.09808349609375e-05}
{'comment': 'You can use it in assertions categories_time', 'expression': "variables['categories_time'] < 2.5", 'provider': 'python', 'type': 'assert', '_elapsed': 0.00054931640625}
		</system-out>
	</testcase>
</testsuite>

How to reuse test steps

In different test schemes, there should be reusable test steps, so that when modifying, you can change one place. Pytest play takes this into account and realizes the reuse of test steps by using the include provider.

In the following example, both provider and type are include keywords, and the test scheme reuses the common test steps specified in path.

- provider: include
  type: include
  path: "/some-path/included-scenario.yml"

Of course, if there are variables in the test steps, they can be configured under the common root directory of these test scripts.

Default command (reuse of commands)

Similar to type inheritance in code, pytest play also provides a mechanism for reusing test commands.

If play is used_ When requests tests multiple interfaces of the same service, the same header needs to be passed in every call. In this case, the default command can be used.

In the following example, Section 1 uses the store of the python command_ The variable subcommand defines a variable named bear with a value of bear. Store using Python command in Section 2_ Variable subcommand, defined with the name play_ The default command of requests sets the public HTTP header in expression. Play is directly reused in Section 3_ The requests command eliminates the need to define HTTP headers.

- provider: python
  type: store_variable
  name: bearer
  expression: "'BEARER'"
- provider: python
  type: store_variable
  name: play_requests
  expression: "{'parameters': {'headers': {'Authorization': '$bearer'}}}"
- provider: play_requests
  type: GET
  comment: this is an authenticated request!
  url: "$base_url"

Declare variables, assertions

As you can see in the above example, we can use the python command store in the script_ The variable subcommand declares variables for use in subsequent steps.

In the following example, a variable named foo is declared, and its value is set through an expression expression expression. The expression is computable and the final value is 2. In the subsequent test steps, assertions are made through the assert subcommand of the python command.

- provider: python
  type: store_variable
  expression: "1+1"
  name: foo
- provider: python
  type: assert
  expression: variables['foo'] == 2

sleep

During the test process, it is sometimes necessary to pause the operation for a period of time, and pytest play is also supported. The following example will pause for 2 seconds.

- provider: python
  type: sleep
  seconds: 2

Execute expression

Pytest play supports the simple execution of an expression through the exec subcommand of the python command.

- provider: python
  type: exec
  expression: "1+1"

loop

Complex test steps are not supported. Pytest play supports running a test step in a loop.

In the following example, the subcommand is while, indicating that this is a loop. The adjacent expression is the condition for entering the cycle. The variable countdown is required to be greater than or equal to 0. Timeout indicates that the timeout of the cycle is 2.3 seconds, poll indicates that it is judged every 0.1 seconds, sub_commands represents the loop body, which contains the test steps to be executed in the loop.

- provider: python
  type: while
  expression: variables['countdown'] >= 0
  timeout: 2.3
  poll: 0.1
  sub_commands:
  - provider: python
    type: store_variable
    name: countdown
    expression: variables['countdown'] - 1

Condition step

Pytest play also supports judging whether to skip the current test step according to conditions. In the following example, skip_ The expression in condition is a condition to skip the current command.

- provider: include
  type: include
  path: "/some-path/assertions.yml"
  skip_condition: variables['cassandra_assertions'] is True

Assert cumulative time

Pytest play maintains a named_ The elapsed variable indicates the elapsed time after the start of the test, which can be used in assertions.

- type: GET
  provider: play_requests
  url: https://api.chucknorris.io/jokes/categories
  expression: "'dev' in response.json()"
- type: assert
  provider: python
  expression: "variables['_elapsed'] > 0"

Time tracking

Sometimes, we need to know that each test command takes time to execute, and pytest play is also supported.

In the following example, through the subcommand record of the metrics command_ elapsed_ start,record_elapsed_stop respectively record the start and end time of the step and give it to load respectively_ Time and live_ search_ There are two indicators.

- provider: selenium
  type: get
  url: https://www.plone-demo.info/
- provider: metrics
  type: record_elapsed_start
  name: load_time
- provider: selenium
  type: setElementText
  text: plone 5
  locator:
    type: id
    value: searchGadget
- provider: metrics
  type: record_elapsed_stop
  name: load_time
- provider: metrics
  type: record_elapsed_start
  name: live_search_time
- provider: selenium
  type: waitForElementVisible
  locator:
    type: css
    value: li[data-url$="https://www.plone-demo.info/front-page"]
- provider: metrics
  type: record_elapsed_stop
  name: live_search_time

The following is the result report after operation. You can see the values of the two indicators, which are about 1.1s and 1.09s respectively.

<?xml version="1.0" encoding="utf-8"?>
<testsuite errors="0" failures="0" name="pytest" skipped="0" tests="1" time="13.650">
	<testcase classname="test_search.yml" file="test_search.yml" name="test_search.yml" time="13.580">
		<properties>
			<property name="load_time" value="1.1175920963287354"/>
			<property name="live_search_time" value="1.0871295928955078"/>
		</properties>
		<system-out>{'provider': 'selenium', 'type': 'get', 'url': 'https://www.plone-demo.info/', '_elapsed': 9.593282461166382}
{'name': 'load_time', 'provider': 'metrics', 'type': 'record_elapsed_start', '_elapsed': 1.1682510375976562e-05}
{'locator': {'type': 'id', 'value': 'searchGadget'}, 'provider': 'selenium', 'text': 'plone 5', 'type': 'setElementText', '_elapsed': 1.1019845008850098}
{'name': 'load_time', 'provider': 'metrics', 'type': 'record_elapsed_stop', '_elapsed': 1.9788742065429688e-05}
{'name': 'live_search_time', 'provider': 'metrics', 'type': 'record_elapsed_start', '_elapsed': 1.0013580322265625e-05}
{'locator': {'type': 'css', 'value': 'li[data-url$="https://www.plone-demo.info/front-page"]'}, 'provider': 'selenium', 'type': 'waitForElementVisible', '_elapsed': 1.060795545578003}
{'name': 'live_search_time', 'provider': 'metrics', 'type': 'record_elapsed_stop', '_elapsed': 2.3603439331054688e-05}
		</system-out>
	</testcase>
</testsuite>

Measurement time can be used to predict system behavior and compare performance under different branches or scenarios. In addition to the above methods provided by pytest play, you can also install pytest StatsD plug-in, StatsD and graphite Web integration to achieve this purpose.

StatsD and graphite web can be deployed using Docker. See:
https://graphite.readthedocs.io/en/latest/install.html

The pytest statsd plug-in installation commands are as follows:

pip install pytest-play[statsd]

The usage is as follows:

--stats-d [--stats-prefix play --stats-host http://myserver.com --stats-port 3000]

– stats-d is used to enable statistics, – stats host specifies the HTTP address of the target server receiving indicator data, – stats port specifies the port of the target server, – stats prefix is used to distinguish the source when the target server is public.

To collect and send indicator data, you can use record_elapsed,record_elapsed_start,record_elapsed_stop and other commands, you can also use record_property command, but metric must be provided at the same time_ Type additional parameter. The specific usage is as follows:

- provider: metrics
  type: record_property
  name: categories_time
  expression: "variables['_elapsed']*1000"
  metric_type: timing
- provider: metrics
  type: record_property
  name: fridge_temperature
  expression: "4"
  metric_type: gauge

If metric is not provided_ Type, the indicator data will not be sent to StatsD.

The following is an example of the final generated statistical graph.

HTTP interface response time example

Browser performance example

performance testing

You can use the pytest play test scheme as a performance test using bzt/Taurus.

The specific method is to add a bzt/Taurus YAML file. Note that the file name cannot be test_ start. Here is a complete example:
https://github.com/pytest-dev/pytest-play/tree/features/examples/bzt_performance

settings:
  artifacts-dir: /tmp/%Y-%m-%d_%H-%M-%S.%f

execution:
- executor: pytest
  scenario: pytest-run
  iterations: 1

scenarios:
  pytest-run:
    # additional-args: --stats-d --stats-prefix play
    script: scripts/

services:
- module: shellexec
  prepare:
  - pip3 install -r https://raw.githubusercontent.com/davidemoro/pytest-play-docker/master/requirements.txt

The operation method is as follows:

docker run --rm -it -v $(pwd):/src --user root --entrypoint "bzt" davidemoro/pytest-play bzt.yml

After the command is executed, you can see bzt start and run each test scenario:

Use dynamic data directly in the message body

Store is usually used_ Variable subcommand to provide variable support. Variables in this way can be used in multiple places. Sometimes, when you need to temporarily generate a value when sending a message (REST or MQTT), you need to use {! Expression!} to declare an expression. An example is as follows.

- comment: python expressions in mqtt payload (without declaring variables)
  provider: mqtt
  type: publish
  host: "$mqtt_host"
  port: "$mqtt_port"
  endpoint: "$mqtt_endpoint/$device_serial_number"
  payload: '{
        "measure_id":   [124],
        "obj_id_L":     [0],
        "measureType":  ["float"],
        "start_time":   {! int(datetime.datetime.utcnow().timestamp()*1000) !},
        "bin_value":    [1]
    }'

extend

Pytest play provides a set of extension mechanism to support custom commands.

Extend the command as follows:

command = {'type': 'print', 'provider': 'newprovider', 'message': 'Hello, World!'}

You also need to implement the command provider:

from pytest_play.providers import BaseProvider

class NewProvider(BaseProvider):

    def this_is_not_a_command(self):
        """ Commands should be command_ prefixed """

    def command_print(self, command):
        print(command['message'])

    def command_yetAnotherCommand(self, command):
        print(command)

And register in setup.py:

entry_points={
    'playcommands': [
        'print = your_package.providers:NewProvider',
    ],
},

Example

Here are some examples of addresses:

  • https://github.com/pytest-dev/pytest-play/tree/master/examples
  • https://github.com/davidemoro/pytest-play-docker/tree/master/tests
  • https://github.com/davidemoro/pytest-play-plone-example

plug-in unit

The existing third-party plug-ins are as follows:

  • play_selenium: test Web pages through Selenium/Splinter driven browsers
  • play_requests: drives Python's requests library to send requests and test the HTTP interface
  • play_sql: supports accessing SQL database and asserting
  • play_cassandra: support Cassandra distributed NoSQL database
  • play_dynamodb: query and assert AWS DynamoDB
  • play_ websockets: support websockets
  • play_mqtt: provides support for MQTT

OK, that's all for pytest play

The following is the supporting information. For the friends doing [software testing], it should be the most comprehensive and complete war preparation warehouse. This warehouse has also accompanied me through the most difficult journey. I hope it can also help you!

Finally, it can be in the official account: the sad spicy bar! Get a 216 page interview document of Software Test Engineer for free. And the corresponding video learning tutorials for free!, It includes basic knowledge, Linux essentials, Shell, Internet program principles, Mysql database, special topics of packet capture tools, interface test tools, test advanced Python programming, Web automation test, APP automation test, interface automation test, advanced continuous test integration, test architecture, development test framework, performance test, security test, etc.

Don't fight alone in learning. It's best to stay together and grow together. The effect of mass effect is very powerful. If we study together and punch in together, we will have more motivation to learn and stick to it. You can join our testing technology exchange group: 914172719 (there are various software testing resources and technical discussions)

Friends who like software testing, if my blog is helpful to you and if you like my blog content, please click "like", "comment" and "collect" for three times!

Haowen recommendation

Job transfer interview, job hopping interview, these interview skills that software testers must know!

Interview experience: move bricks in first tier cities! Another software testing post, 5000 will be satisfied

Interviewer: I've worked for three years and have a preliminary test? I'm afraid your title of software test engineer should be enclosed in double quotation marks

What kind of person is suitable for software testing?

The man who left work on time was promoted before me

The test post changed jobs repeatedly and disappeared

Topics: Operation & Maintenance Programmer unit testing software testing IT