|
| 1 | +<p align="center"> |
| 2 | + <br>English | <a href="README.zh.md">中文</a> |
| 3 | +</p> |
| 4 | + |
| 5 | +*** |
| 6 | + |
| 7 | +## Introduction |
| 8 | + |
| 9 | +xbot is a `lightweight`, `easy-to-use`, and `extensible` test automation framework. |
| 10 | + |
| 11 | +## Installation |
| 12 | + |
| 13 | +Install xbot via pip: |
| 14 | + |
| 15 | +``` |
| 16 | +pip install xbot.framework |
| 17 | +``` |
| 18 | + |
| 19 | +Type `xbot --help` to check: |
| 20 | + |
| 21 | +``` |
| 22 | +$ xbot --help |
| 23 | +usage: xbot [-h] [-d DIRECTORY] [-b TESTBED] [-s TESTSET] [-f {verbose,brief}] [-v] {init,run} |
| 24 | +
|
| 25 | +positional arguments: |
| 26 | +{init,run} |
| 27 | +
|
| 28 | +optional arguments: |
| 29 | +-h, --help show this help message and exit |
| 30 | +-d DIRECTORY, --directory DIRECTORY |
| 31 | + directory to init (required by `init` command) |
| 32 | +-b TESTBED, --testbed TESTBED |
| 33 | + testbed filepath (required by `run` command) |
| 34 | +-s TESTSET, --testset TESTSET |
| 35 | + testset filepath (required by `run` command) |
| 36 | +-f {verbose,brief}, --outfmt {verbose,brief} |
| 37 | + output format (option for `run` command, options: verbose/brief, default: brief) |
| 38 | +-v, --version show program's version number and exit |
| 39 | +``` |
| 40 | + |
| 41 | + |
| 42 | +## Getting Started |
| 43 | + |
| 44 | +Initialize a test project: |
| 45 | + |
| 46 | +``` |
| 47 | +$ xbot init -d ./testproj |
| 48 | +Initialized ./testproj |
| 49 | +``` |
| 50 | + |
| 51 | +The test project directory structure: |
| 52 | + |
| 53 | +``` |
| 54 | +./testproj |
| 55 | +├── README.md |
| 56 | +├── lib # test libraries |
| 57 | +│ ├── __init__.py |
| 58 | +│ ├── testbed.py # testbed base |
| 59 | +│ └── testcase.py # testcase base |
| 60 | +├── requirements.txt |
| 61 | +├── testbeds # directory storing testbeds |
| 62 | +│ └── testbed_example.yml |
| 63 | +├── testcases # directory storing testcases |
| 64 | +│ ├── __init__.py |
| 65 | +│ └── examples |
| 66 | +│ ├── __init__.py |
| 67 | +│ ├── nonpass |
| 68 | +│ │ ├── __init__.py |
| 69 | +│ │ ├── tc_eg_nonpass_error_clsname.py |
| 70 | +│ │ ├── tc_eg_nonpass_error_syntax.py |
| 71 | +│ │ ├── tc_eg_nonpass_fail_setup_with_failfast_false.py |
| 72 | +│ │ ├── tc_eg_nonpass_fail_setup_with_failfast_true.py |
| 73 | +│ │ ├── tc_eg_nonpass_fail_step_with_failfast_false.py |
| 74 | +│ │ ├── tc_eg_nonpass_fail_step_with_failfast_true.py |
| 75 | +│ │ ├── tc_eg_nonpass_skip_excluded.py |
| 76 | +│ │ ├── tc_eg_nonpass_skip_not_included.py |
| 77 | +│ │ └── tc_eg_nonpass_timeout.py |
| 78 | +│ └── pass |
| 79 | +│ ├── __init__.py |
| 80 | +│ ├── tc_eg_pass_create_dirs_and_files.py |
| 81 | +│ └── tc_eg_pass_get_values_from_testbed.py |
| 82 | +└── testsets # directory storing testsets |
| 83 | + └── testset_example.yml |
| 84 | +``` |
| 85 | + |
| 86 | +Testbed example(`testbeds/testbed_example.yml`): |
| 87 | + |
| 88 | +```yaml |
| 89 | +# Testbed is used to store the information about the test environment. |
| 90 | +# The information can be accessed by self.testbed.get() in the testcases. |
| 91 | +example: |
| 92 | + key1: value1 |
| 93 | + key2: |
| 94 | + key2-1: value2-1 |
| 95 | + key2-2: value2-2 |
| 96 | + key3: |
| 97 | + - value3-1 |
| 98 | + - value3-2 |
| 99 | + - value3-3 |
| 100 | + key4: |
| 101 | + - name: jack |
| 102 | + age: 20 |
| 103 | + - name: tom |
| 104 | + age: 30 |
| 105 | +``` |
| 106 | +
|
| 107 | +Testset example(`testsets/testset_example.yml`): |
| 108 | + |
| 109 | +```yaml |
| 110 | +# Testset is used to organize testcases to be executed. |
| 111 | +tags: # `exclude` has higher priority than `include`. |
| 112 | + include: # Include testcases with these tags. |
| 113 | + - tag1 |
| 114 | + exclude: # Exclude testcases with these tags. |
| 115 | + - tag2 |
| 116 | +paths: |
| 117 | + - testcases/examples/pass/tc_eg_pass_get_values_from_testbed.py |
| 118 | + - testcases/examples/pass/tc_eg_pass_create_dirs_and_files.py |
| 119 | + # Recursively include all testcases in the directory, |
| 120 | + # only match files with the prefix 'tc_' and suffix '.py'. |
| 121 | + - testcases/examples/nonpass/ |
| 122 | +``` |
| 123 | +
|
| 124 | +Run the testcases(must execute under the test project directory): |
| 125 | +
|
| 126 | +``` |
| 127 | +$ xbot run -b testbeds/testbed_example.yml -s testsets/testset_example.yml |
| 128 | +(1/11) PASS 0:00:01 tc_eg_pass_get_values_from_testbed |
| 129 | +(2/11) PASS 0:00:01 tc_eg_pass_create_dirs_and_files |
| 130 | +(3/11) ERROR 0:00:00 tc_eg_nonpass_error_clsname |
| 131 | +(4/11) ERROR 0:00:00 tc_eg_nonpass_error_syntax |
| 132 | +(5/11) FAIL 0:00:01 tc_eg_nonpass_fail_setup_with_failfast_false |
| 133 | +(6/11) FAIL 0:00:01 tc_eg_nonpass_fail_setup_with_failfast_true |
| 134 | +(7/11) FAIL 0:00:01 tc_eg_nonpass_fail_step_with_failfast_false |
| 135 | +(8/11) FAIL 0:00:01 tc_eg_nonpass_fail_step_with_failfast_true |
| 136 | +(9/11) SKIP 0:00:00 tc_eg_nonpass_skip_excluded |
| 137 | +(10/11) SKIP 0:00:00 tc_eg_nonpass_skip_not_included |
| 138 | +(11/11) TIMEOUT 0:00:03 tc_eg_nonpass_timeout |
| 139 | + |
| 140 | +report: /Users/wan/CodeProjects/xbot.framework/testproj/logs/testbed_example/2024-07-02_12-19-43/report.html |
| 141 | +``` |
| 142 | +
|
| 143 | +Test report and logs will be generated in the `logs` subdirectory. |
| 144 | + |
| 145 | +Example report: |
| 146 | + |
| 147 | + |
| 148 | + |
| 149 | +Example log: |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | + |
| 154 | +## Testcase Development |
| 155 | + |
| 156 | +Testcases are stored in the `testcases` subdirectory, below is a example(`testcases/examples/pass/tc_eg_pass_create_dirs_and_files.py`): |
| 157 | + |
| 158 | +```python |
| 159 | +import os |
| 160 | +import tempfile |
| 161 | +import shutil |
| 162 | +
|
| 163 | +from xbot.framework.utils import assertx |
| 164 | +from lib.testcase import TestCase |
| 165 | +
|
| 166 | +
|
| 167 | +class tc_eg_pass_create_dirs_and_files(TestCase): |
| 168 | + """ |
| 169 | + Test creating directories and files. |
| 170 | + """ |
| 171 | + TIMEOUT = 60 |
| 172 | + FAILFAST = True |
| 173 | + TAGS = ['tag1'] |
| 174 | +
|
| 175 | + def setup(self): |
| 176 | + """ |
| 177 | + Prepare test environment. |
| 178 | + """ |
| 179 | + self.workdir = tempfile.mkdtemp() |
| 180 | + self.info('Created workdir: %s', self.workdir) |
| 181 | +
|
| 182 | + def step1(self): |
| 183 | + """ |
| 184 | + Create a subdirectory 'dir' under the temporary working directory and check if it is created successfully. |
| 185 | + """ |
| 186 | + self.dir1 = os.path.join(self.workdir, 'dir1') |
| 187 | + os.mkdir(self.dir1) |
| 188 | + assertx(os.path.exists(self.dir1), '==', True) |
| 189 | +
|
| 190 | + def step2(self): |
| 191 | + """ |
| 192 | + Create an empty file 'file1' under 'dir1' and check if it is created successfully. |
| 193 | + """ |
| 194 | + self.file1 = os.path.join(self.dir1, 'file1') |
| 195 | + open(self.file1, 'w').close() |
| 196 | + assertx(os.path.exists(self.file1), '==', True) |
| 197 | +
|
| 198 | + def step3(self): |
| 199 | + """ |
| 200 | + Write 'hello world' to 'file1' and check if it is written successfully. |
| 201 | + """ |
| 202 | + with open(self.file1, 'w') as f: |
| 203 | + f.write('hello world') |
| 204 | + with open(self.file1, 'r') as f: |
| 205 | + assertx(f.read(), '==', 'hello world') |
| 206 | +
|
| 207 | + def teardown(self): |
| 208 | + """ |
| 209 | + Clean up test environment. |
| 210 | + """ |
| 211 | + shutil.rmtree(self.workdir) |
| 212 | + self.info('Removed workdir: %s', self.workdir) |
| 213 | + self.sleep(1) |
| 214 | +``` |
| 215 | + |
| 216 | +- Testcase `MUST` inherit from the `TestCase` base class; |
| 217 | +- Testcase `MUST` implement the preset steps in the setup method, write pass if there are no specific steps; |
| 218 | +- Testcase `MUST` implement the cleanup steps in the teardown method, write pass if there are no specific steps; |
| 219 | +- Test steps are named in the form of `step1, step2, ...`, the number at the end is the execution order; |
| 220 | +- The `TIMEOUT` attribute defines the maximum execution time of the testcase(unit: `seconds`), the testcase will be forced to end and the result will be set to TIMEOUT if it exceeds the time limit; |
| 221 | +- When `FAILFAST` attribute is *True*, the subsequent test steps will be skipped and the teardown will be executed immediately if a test step fails; |
| 222 | +- The `TAGS` attribute defines the testcase *tags*, which can be used to filter testcases to be executed in the testset; |
| 223 | + |
| 224 | +## Test libraries development |
| 225 | + |
| 226 | +Test libraries are stored in the `lib` subdirectory, write the test libraries according to the business requirements, import and use them in the testcases. |
| 227 | + |
| 228 | +## Plugins |
| 229 | + |
| 230 | +| Name | Description | |
| 231 | +|--------------------------------------------------------------------|---------------------------------------| |
| 232 | +| [xbot.plugins.ssh](https://github.com/zhaowcheng/xbot.plugins.ssh) | SSH library for xbot.framework | |
| 233 | +| xbot.plugins.http(`planning`) | HTTP library for xbot.framework | |
| 234 | +| xbot.plugins.wui(`planning`) | WebUI library for xbot.framework | |
| 235 | +| xbot.plugins.gui(`planning`) | GUI library for xbot.framework | |
| 236 | +| xbot.plugins.pgsql(`planning`) | PostgreSQL library for xbot.framework | |
0 commit comments