|
1 | 1 | # LoadDensity |
2 | | -A high‑performance load testing and automation tool. |
3 | | -It supports fast user spawning, flexible templates, and generates reports in multiple formats. |
4 | | -Designed to be cross‑platform and easy to integrate into your projects. |
5 | | - |
6 | | -- Load automation: Quickly set up and run load tests |
7 | | -- User templates: Simple configuration for reusable test users |
8 | | -- Load Density scripts: Define and execute repeatable scenarios |
9 | | -- Report generation: Export results in JSON, HTML, or XML |
10 | | -- High throughput: Thousands of requests per second |
11 | | -- Fast user spawning: Scale up test users instantly |
12 | | -- Multi‑test support: Run multiple tests on a single task |
13 | | -- Configurable test duration: Specify how long tests should run |
14 | | -- OS independent: Works across major operating systems |
15 | | -- Remote automation: Execute tests remotely |
16 | | -- Project & template support: Organize and reuse test setup |
| 2 | + |
| 3 | +[](https://pypi.org/project/je_load_density/) |
| 4 | +[](https://pypi.org/project/je_load_density/) |
| 5 | +[](https://opensource.org/licenses/MIT) |
| 6 | +[](https://loaddensity.readthedocs.io/en/latest/) |
| 7 | + |
| 8 | +**LoadDensity** is a high-performance load & stress testing automation framework built on top of [Locust](https://locust.io/). It provides a simplified wrapper around Locust's core functionality, enabling fast user spawning, flexible test configuration via templates and JSON-driven scripts, report generation in multiple formats (HTML / JSON / XML), a built-in GUI, remote execution via TCP socket server, and a callback mechanism for post-test workflows. |
| 9 | + |
| 10 | +**[繁體中文](README/README_zh-TW.md)** | **[简体中文](README/README_zh-CN.md)** |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## Features |
| 15 | + |
| 16 | +- **Simplified Locust Wrapper** — Abstracts Locust's `Environment`, `Runner`, and `User` classes behind a clean, high-level API. |
| 17 | +- **Two User Types** — Supports both `HttpUser` and `FastHttpUser` (geventhttpclient-based, higher throughput). |
| 18 | +- **Fast User Spawning** — Scale to thousands of concurrent users with configurable spawn rate. |
| 19 | +- **JSON-Driven Test Scripts** — Define test scenarios as JSON files and execute them without writing Python code. |
| 20 | +- **Action Executor** — A built-in event-driven executor that maps action names to functions. Supports batch execution and file-driven execution. |
| 21 | +- **Report Generation** — Export test results in three formats: |
| 22 | + - **HTML** — Styled tables with success/failure records |
| 23 | + - **JSON** — Structured data for programmatic consumption |
| 24 | + - **XML** — Standard XML output for CI/CD integration |
| 25 | +- **Request Hook** — Automatically records every request (success and failure) with method, URL, status code, response body, headers, and errors. |
| 26 | +- **Callback Executor** — Chain a trigger function with a callback function for post-test workflows (e.g., run test then generate report). |
| 27 | +- **TCP Socket Server** — Remote execution server based on gevent. Accepts JSON commands over TCP to execute tests remotely. |
| 28 | +- **Project Scaffolding** — Auto-generate project directory structure with keyword templates and executor scripts. |
| 29 | +- **Package Manager** — Dynamically load external Python packages and register their functions into the executor at runtime. |
| 30 | +- **GUI (Optional)** — PySide6-based graphical interface with real-time log display, supporting English and Traditional Chinese. |
| 31 | +- **CLI Support** — Run tests, execute scripts, or scaffold projects directly from the command line. |
| 32 | +- **Cross-Platform** — Works on Windows, macOS, and Linux. |
17 | 33 |
|
18 | 34 | ## Installation |
19 | 35 |
|
| 36 | +### Basic (CLI & Library) |
| 37 | + |
| 38 | +```bash |
| 39 | +pip install je_load_density |
| 40 | +``` |
| 41 | + |
| 42 | +### With GUI Support |
| 43 | + |
| 44 | +```bash |
| 45 | +pip install je_load_density[gui] |
| 46 | +``` |
| 47 | + |
| 48 | +This installs [PySide6](https://doc.qt.io/qtforpython/) and [qt-material](https://github.com/UN-GCPDS/qt-material) for the graphical interface. |
| 49 | + |
| 50 | +## Requirements |
| 51 | + |
| 52 | +- Python **3.10** or later |
| 53 | +- [Locust](https://locust.io/) (installed automatically as a dependency) |
| 54 | + |
| 55 | +## Quick Start |
| 56 | + |
| 57 | +### 1. Using the Python API |
| 58 | + |
| 59 | +```python |
| 60 | +from je_load_density import start_test |
| 61 | + |
| 62 | +# Define user configuration and tasks |
| 63 | +result = start_test( |
| 64 | + user_detail_dict={"user": "fast_http_user"}, |
| 65 | + user_count=50, |
| 66 | + spawn_rate=10, |
| 67 | + test_time=10, |
| 68 | + tasks={ |
| 69 | + "get": {"request_url": "http://httpbin.org/get"}, |
| 70 | + "post": {"request_url": "http://httpbin.org/post"}, |
| 71 | + } |
| 72 | +) |
| 73 | +``` |
| 74 | + |
| 75 | +**Parameters:** |
| 76 | +| Parameter | Type | Default | Description | |
| 77 | +|---|---|---|---| |
| 78 | +| `user_detail_dict` | `dict` | — | User type configuration. `{"user": "fast_http_user"}` or `{"user": "http_user"}` | |
| 79 | +| `user_count` | `int` | `50` | Total number of simulated users | |
| 80 | +| `spawn_rate` | `int` | `10` | Number of users spawned per second | |
| 81 | +| `test_time` | `int` | `60` | Test duration in seconds. `None` for unlimited | |
| 82 | +| `web_ui_dict` | `dict` | `None` | Enable Locust Web UI, e.g. `{"host": "127.0.0.1", "port": 8089}` | |
| 83 | +| `tasks` | `dict` | — | HTTP method to request URL mapping | |
| 84 | + |
| 85 | +### 2. Using JSON Script Files |
| 86 | + |
| 87 | +Create a JSON file (`test_scenario.json`): |
| 88 | + |
| 89 | +```json |
| 90 | +[ |
| 91 | + ["LD_start_test", { |
| 92 | + "user_detail_dict": {"user": "fast_http_user"}, |
| 93 | + "user_count": 50, |
| 94 | + "spawn_rate": 10, |
| 95 | + "test_time": 5, |
| 96 | + "tasks": { |
| 97 | + "get": {"request_url": "http://httpbin.org/get"}, |
| 98 | + "post": {"request_url": "http://httpbin.org/post"} |
| 99 | + } |
| 100 | + }] |
| 101 | +] |
| 102 | +``` |
| 103 | + |
| 104 | +Execute from Python: |
| 105 | + |
| 106 | +```python |
| 107 | +from je_load_density import execute_action, read_action_json |
| 108 | + |
| 109 | +execute_action(read_action_json("test_scenario.json")) |
| 110 | +``` |
| 111 | + |
| 112 | +### 3. Using the CLI |
| 113 | + |
| 114 | +```bash |
| 115 | +# Execute a single JSON script file |
| 116 | +python -m je_load_density -e test_scenario.json |
| 117 | + |
| 118 | +# Execute all JSON files in a directory |
| 119 | +python -m je_load_density -d ./test_scripts/ |
| 120 | + |
| 121 | +# Execute an inline JSON string |
| 122 | +python -m je_load_density --execute_str '[["LD_start_test", {"user_detail_dict": {"user": "fast_http_user"}, "user_count": 10, "spawn_rate": 5, "test_time": 5, "tasks": {"get": {"request_url": "http://httpbin.org/get"}}}]]' |
| 123 | + |
| 124 | +# Scaffold a new project with templates |
| 125 | +python -m je_load_density -c MyProject |
| 126 | +``` |
| 127 | + |
| 128 | +### 4. Using the GUI |
| 129 | + |
| 130 | +```python |
| 131 | +from je_load_density.gui.main_window import LoadDensityUI |
| 132 | +from PySide6.QtWidgets import QApplication |
| 133 | +import sys |
| 134 | + |
| 135 | +app = QApplication(sys.argv) |
| 136 | +window = LoadDensityUI() |
| 137 | +window.show() |
| 138 | +sys.exit(app.exec()) |
| 139 | +``` |
| 140 | + |
| 141 | +## Report Generation |
| 142 | + |
| 143 | +After running a test, generate reports from the recorded data: |
| 144 | + |
| 145 | +```python |
| 146 | +from je_load_density import ( |
| 147 | + generate_html_report, |
| 148 | + generate_json_report, |
| 149 | + generate_xml_report, |
| 150 | +) |
| 151 | + |
| 152 | +# HTML report — creates "my_report.html" |
| 153 | +generate_html_report("my_report") |
| 154 | + |
| 155 | +# JSON report — creates "my_report_success.json" and "my_report_failure.json" |
| 156 | +generate_json_report("my_report") |
| 157 | + |
| 158 | +# XML report — creates "my_report_success.xml" and "my_report_failure.xml" |
| 159 | +generate_xml_report("my_report") |
| 160 | +``` |
| 161 | + |
| 162 | +## Advanced Usage |
| 163 | + |
| 164 | +### Action Executor |
| 165 | + |
| 166 | +The executor maps string action names to callable functions. All built-in Python functions are also available. |
| 167 | + |
| 168 | +```python |
| 169 | +from je_load_density import executor, add_command_to_executor |
| 170 | + |
| 171 | +# Register a custom function |
| 172 | +def my_custom_action(message): |
| 173 | + print(f"Custom: {message}") |
| 174 | + |
| 175 | +add_command_to_executor({"my_action": my_custom_action}) |
| 176 | + |
| 177 | +# Execute actions programmatically |
| 178 | +executor.execute_action([ |
| 179 | + ["my_action", ["Hello World"]], |
| 180 | + ["print", ["Test complete"]], |
| 181 | +]) |
| 182 | +``` |
| 183 | + |
| 184 | +**Built-in executor actions:** |
| 185 | +| Action Name | Description | |
| 186 | +|---|---| |
| 187 | +| `LD_start_test` | Start a load test | |
| 188 | +| `LD_generate_html` | Generate HTML fragments | |
| 189 | +| `LD_generate_html_report` | Generate full HTML report file | |
| 190 | +| `LD_generate_json` | Generate JSON data structure | |
| 191 | +| `LD_generate_json_report` | Generate JSON report files | |
| 192 | +| `LD_generate_xml` | Generate XML strings | |
| 193 | +| `LD_generate_xml_report` | Generate XML report files | |
| 194 | +| `LD_execute_action` | Execute a list of actions | |
| 195 | +| `LD_execute_files` | Execute actions from multiple files | |
| 196 | +| `LD_add_package_to_executor` | Dynamically load a package into the executor | |
| 197 | + |
| 198 | +### Callback Executor |
| 199 | + |
| 200 | +Chain a trigger function with a callback: |
| 201 | + |
| 202 | +```python |
| 203 | +from je_load_density import callback_executor |
| 204 | + |
| 205 | +def after_test(): |
| 206 | + print("Test finished, generating report...") |
| 207 | + |
| 208 | +callback_executor.callback_function( |
| 209 | + trigger_function_name="user_test", |
| 210 | + callback_function=after_test, |
| 211 | + user_detail_dict={"user": "fast_http_user"}, |
| 212 | + user_count=10, |
| 213 | + spawn_rate=5, |
| 214 | + test_time=5, |
| 215 | + tasks={"get": {"request_url": "http://httpbin.org/get"}}, |
| 216 | +) |
| 217 | +``` |
| 218 | + |
| 219 | +### TCP Socket Server (Remote Execution) |
| 220 | + |
| 221 | +Start a TCP server that accepts JSON commands: |
| 222 | + |
| 223 | +```python |
| 224 | +from je_load_density import start_load_density_socket_server |
| 225 | + |
| 226 | +# Start server (blocking) |
| 227 | +start_load_density_socket_server(host="localhost", port=9940) |
| 228 | +``` |
| 229 | + |
| 230 | +Send commands from a client: |
| 231 | + |
| 232 | +```python |
| 233 | +import socket, json |
| 234 | + |
| 235 | +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 236 | +sock.connect(("localhost", 9940)) |
| 237 | + |
| 238 | +command = json.dumps([ |
| 239 | + ["LD_start_test", { |
| 240 | + "user_detail_dict": {"user": "fast_http_user"}, |
| 241 | + "user_count": 10, "spawn_rate": 5, "test_time": 5, |
| 242 | + "tasks": {"get": {"request_url": "http://httpbin.org/get"}} |
| 243 | + }] |
| 244 | +]) |
| 245 | +sock.send(command.encode("utf-8")) |
| 246 | +response = sock.recv(8192) |
| 247 | +print(response.decode("utf-8")) |
| 248 | +sock.close() |
| 249 | +``` |
| 250 | + |
| 251 | +Send `"quit_server"` to gracefully shut down the server. |
| 252 | + |
| 253 | +### Project Scaffolding |
| 254 | + |
| 255 | +Generate a project with keyword templates and executor scripts: |
| 256 | + |
| 257 | +```python |
| 258 | +from je_load_density import create_project_dir |
| 259 | + |
| 260 | +create_project_dir(project_path="./my_tests", parent_name="LoadDensity") |
| 261 | +``` |
| 262 | + |
| 263 | +This creates: |
| 264 | +``` |
| 265 | +my_tests/ |
| 266 | +└── LoadDensity/ |
| 267 | + ├── keyword/ |
| 268 | + │ ├── keyword1.json # FastHttpUser test template |
| 269 | + │ └── keyword2.json # HttpUser test template |
| 270 | + └── executor/ |
| 271 | + ├── executor_one_file.py # Execute single keyword file |
| 272 | + └── executor_folder.py # Execute all files in keyword/ |
20 | 273 | ``` |
21 | | -pip install je_locust_wrapper |
| 274 | + |
| 275 | +### Dynamic Package Loading |
| 276 | + |
| 277 | +Load external packages and register their functions into the executor: |
| 278 | + |
| 279 | +```python |
| 280 | +from je_load_density import executor |
| 281 | + |
| 282 | +# Load a package and make its functions available as executor actions |
| 283 | +executor.execute_action([ |
| 284 | + ["LD_add_package_to_executor", ["my_custom_package"]] |
| 285 | +]) |
| 286 | +``` |
| 287 | + |
| 288 | +### Test Records |
| 289 | + |
| 290 | +Access raw test records programmatically: |
| 291 | + |
| 292 | +```python |
| 293 | +from je_load_density import test_record_instance |
| 294 | + |
| 295 | +# After running a test |
| 296 | +for record in test_record_instance.test_record_list: |
| 297 | + print(record["Method"], record["test_url"], record["status_code"]) |
| 298 | + |
| 299 | +for error in test_record_instance.error_record_list: |
| 300 | + print(error["Method"], error["test_url"], error["error"]) |
| 301 | + |
| 302 | +# Clear records |
| 303 | +test_record_instance.clear_records() |
22 | 304 | ``` |
23 | 305 |
|
24 | | -## Require |
| 306 | +## Architecture |
25 | 307 |
|
26 | 308 | ``` |
27 | | -python 3.9 or later |
| 309 | +je_load_density/ |
| 310 | +├── __init__.py # Public API exports |
| 311 | +├── __main__.py # CLI entry point |
| 312 | +├── gui/ # PySide6 GUI (optional dependency) |
| 313 | +│ ├── main_window.py # Main window (QMainWindow) |
| 314 | +│ ├── main_widget.py # Test parameter form & log panel |
| 315 | +│ ├── load_density_gui_thread.py # Background thread for tests |
| 316 | +│ ├── log_to_ui_filter.py # Log interceptor for GUI display |
| 317 | +│ └── language_wrapper/ # i18n (English, Traditional Chinese) |
| 318 | +├── wrapper/ |
| 319 | +│ ├── create_locust_env/ # Locust Environment & Runner setup |
| 320 | +│ ├── start_wrapper/ # High-level start_test() entry point |
| 321 | +│ ├── user_template/ # HttpUser & FastHttpUser wrappers |
| 322 | +│ ├── proxy/ # User proxy container & configuration |
| 323 | +│ └── event/ # Request hook (records all requests) |
| 324 | +└── utils/ |
| 325 | + ├── executor/ # Action executor (event-driven) |
| 326 | + ├── generate_report/ # HTML, JSON, XML report generators |
| 327 | + ├── test_record/ # Test record storage |
| 328 | + ├── socket_server/ # TCP server for remote execution |
| 329 | + ├── callback/ # Callback function executor |
| 330 | + ├── project/ # Project scaffolding & templates |
| 331 | + ├── package_manager/ # Dynamic package loading |
| 332 | + ├── json/ # JSON file read/write utilities |
| 333 | + ├── xml/ # XML structure utilities |
| 334 | + ├── file_process/ # Directory file listing |
| 335 | + ├── logging/ # Logger instance |
| 336 | + └── exception/ # Custom exceptions & error tags |
28 | 337 | ``` |
29 | 338 |
|
30 | 339 | ## Tested Platforms |
31 | | -- Windows 10 ~ 11 |
32 | | -- macOS 10.15 ~ 11 Big Sur |
| 340 | + |
| 341 | +- Windows 10 / 11 |
| 342 | +- macOS 10.15 ~ 11 (Big Sur) |
33 | 343 | - Ubuntu 20.04 |
34 | 344 | - Raspberry Pi 3B+ |
35 | | -- All test cases are located in the test directory |
| 345 | + |
| 346 | +## License |
| 347 | + |
| 348 | +This project is licensed under the [MIT License](LICENSE). |
| 349 | + |
| 350 | +## Contributing |
| 351 | + |
| 352 | +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. |
| 353 | + |
| 354 | +## Links |
| 355 | + |
| 356 | +- **PyPI**: https://pypi.org/project/je_load_density/ |
| 357 | +- **Documentation**: https://loaddensity.readthedocs.io/en/latest/ |
| 358 | +- **Source Code**: https://github.com/Intergration-Automation-Testing/LoadDensity |
0 commit comments