Skip to content

Commit 18a7a86

Browse files
Merge pull request #151 from JohnsonChin1009/docs/testing-documentation
docs: added documentation for testing
2 parents 421c257 + c4ffbcd commit 18a7a86

File tree

2 files changed

+1300
-0
lines changed

2 files changed

+1300
-0
lines changed

tests/README.md

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
# ACP Python SDK Automated Testing
2+
3+
<details>
4+
<summary>📑 Table of Contents</summary>
5+
6+
```
7+
tests/
8+
├── unit/ # Unit tests (mocked dependencies)
9+
│ ├── test_account.py
10+
│ ├── test_client.py
11+
│ ├── test_contract_client_v2.py
12+
│ ├── test_fare.py
13+
│ ├── test_job.py
14+
│ ├── test_job_offering.py
15+
│ ├── test_memo.py
16+
│ └── test_x402.py
17+
18+
├── integration/ # Integration tests (real network calls)
19+
│ ├── test_client_integration.py
20+
│ └── test_integration_contract_client_v2.py
21+
22+
├── conftest.py # Pytest fixtures and configuration
23+
├── .env.example # Environment variable template
24+
└── .env # Environment variables (gitignored)
25+
```
26+
27+
- [Introduction](#introduction)
28+
- [Purpose](#purpose)
29+
- [Running Tests](#running-tests)
30+
- [All Tests](#all-tests)
31+
- [Unit Tests Only](#unit-tests-only)
32+
- [Integration Tests Only](#integration-tests-only)
33+
- [Specific Test Files](#specific-test-files)
34+
- [Generating Coverage Report](#generate-coverage-report)
35+
- [How to Write Tests](#how-to-write-tests)
36+
- [Unit Tests](#unit-tests)
37+
- [Integration Tests](#integration-tests)
38+
39+
</details>
40+
41+
## Introduction
42+
43+
### Purpose
44+
45+
This test suite validates the ACP Python SDK's functionality across two levels:
46+
47+
- **Unit Tests** - Verify individual functions and classes in isolation
48+
- **Integration Tests** - Validate end-to-end functionality with real blockchain/API calls
49+
50+
The test suite ensures code quality, prevents regressions, and provides confidence when shipping new features.
51+
52+
## Running Tests
53+
54+
Below are commands to run the test suites.
55+
56+
### All Tests
57+
58+
```bash
59+
poetry run pytest
60+
```
61+
62+
### Unit Tests Only
63+
64+
```bash
65+
poetry run pytest tests/unit
66+
```
67+
68+
### Integration Tests Only
69+
70+
```bash
71+
poetry run pytest tests/integration
72+
```
73+
74+
### Specific Test Files
75+
76+
```bash
77+
poetry run pytest tests/unit/test_job.py
78+
```
79+
80+
### Run Tests with Verbose Output
81+
82+
```bash
83+
poetry run pytest -v
84+
```
85+
86+
### Generate Coverage Report
87+
88+
```bash
89+
# Terminal report with missing lines
90+
poetry run pytest --cov=virtuals_acp --cov-report=term-missing
91+
92+
# HTML report (opens in browser)
93+
poetry run pytest --cov=virtuals_acp --cov-report=html
94+
open htmlcov/index.html
95+
```
96+
97+
### Run Tests by Marker
98+
99+
```bash
100+
# Run only unit tests
101+
poetry run pytest -m unit
102+
103+
# Run only integration tests
104+
poetry run pytest -m integration
105+
106+
# Run tests that don't require network
107+
poetry run pytest -m "not requires_network"
108+
```
109+
110+
## How to Write Tests
111+
112+
### Unit Tests
113+
114+
Unit tests should be **isolated, fast, and deterministic**. These tests don't involve any on-chain activity or external dependencies.
115+
116+
**Location**: `tests/unit/`
117+
118+
**General Guidelines:**
119+
120+
- No network calls
121+
- No blockchain interactions
122+
- External dependencies are mocked using `unittest.mock` or `pytest-mock`
123+
- No `.env` needed
124+
125+
**Example Structure:**
126+
127+
```python
128+
# test_job.py
129+
import pytest
130+
from unittest.mock import MagicMock
131+
from virtuals_acp.job import ACPJob
132+
from virtuals_acp.exceptions import ACPError
133+
from virtuals_acp.models import ACPJobPhase, PriceType
134+
135+
class TestACPJob:
136+
"""Test suite for ACPJob class"""
137+
138+
@pytest.fixture
139+
def mock_acp_client(self):
140+
"""Create a mock VirtualsACP client"""
141+
client = MagicMock()
142+
client.config.base_fare = Fare(
143+
contract_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
144+
decimals=6
145+
)
146+
return client
147+
148+
@pytest.fixture
149+
def sample_job_data(self, mock_acp_client):
150+
"""Sample job data for testing"""
151+
return {
152+
"acp_client": mock_acp_client,
153+
"id": 123,
154+
"client_address": "0x1111111111111111111111111111111111111111",
155+
"provider_address": "0x2222222222222222222222222222222222222222",
156+
"price": 100.0,
157+
"memos": [],
158+
"phase": ACPJobPhase.REQUEST,
159+
}
160+
161+
class TestInitialization:
162+
"""Test job initialization"""
163+
164+
def test_should_initialize_with_all_parameters(self, sample_job_data):
165+
"""Should create a job with valid parameters"""
166+
job = ACPJob.model_construct(**sample_job_data)
167+
168+
assert job is not None
169+
assert job.id == 123
170+
assert job.phase == ACPJobPhase.REQUEST
171+
172+
def test_should_raise_error_for_invalid_parameters(self):
173+
"""Should throw error for invalid parameters"""
174+
with pytest.raises(ACPError):
175+
ACPJob(invalid_param="value")
176+
177+
# Mocking Examples
178+
def test_with_mocked_api_call(mocker):
179+
"""Example of mocking API calls"""
180+
mock_response = MagicMock()
181+
mock_response.json.return_value = {"data": [{"id": 1}]}
182+
mocker.patch('requests.get', return_value=mock_response)
183+
184+
result = fetch_jobs()
185+
assert len(result) == 1
186+
187+
def test_with_mocked_contract_client():
188+
"""Example of mocking contract client"""
189+
mock_client = MagicMock()
190+
mock_client.create_job.return_value = {"type": "CREATE_JOB"}
191+
192+
result = mock_client.create_job("0xProvider", "0xEvaluator")
193+
assert result["type"] == "CREATE_JOB"
194+
```
195+
196+
**What to Test:**
197+
198+
- Input Validation
199+
- Error Handling
200+
- Edge Cases
201+
- Business Logic
202+
- State Transitions
203+
- Helper Functions
204+
205+
### Integration Tests
206+
207+
Integration tests verify the SDK works correctly with external dependencies/services (blockchain, APIs).
208+
209+
**Location**: `tests/integration/`
210+
211+
**General Guidelines:**
212+
213+
- Require `.env` to be defined
214+
- Make real network & blockchain calls
215+
- Test partial end-to-end functionality
216+
- Use longer timeouts
217+
218+
**Environment Setup**
219+
220+
1. Copy `.env.example` to `.env`:
221+
222+
```bash
223+
cp tests/.env.example tests/.env
224+
```
225+
226+
2. Populate environment variables:
227+
228+
```bash
229+
# tests/.env
230+
# General Variables
231+
WHITELISTED_WALLET_PRIVATE_KEY=0x<PRIVATE_KEY>
232+
233+
# Seller Agent Variables
234+
SELLER_ENTITY_ID=<ENTITY_ID>
235+
SELLER_AGENT_WALLET_ADDRESS=<WALLET_ADDRESS>
236+
237+
# Buyer Agent Variables
238+
BUYER_ENTITY_ID=<ENTITY_ID>
239+
BUYER_AGENT_WALLET_ADDRESS=<WALLET_ADDRESS>
240+
```
241+
242+
**Example Structure:**
243+
244+
```python
245+
# test_integration_contract_client_v2.py
246+
import pytest
247+
import os
248+
from virtuals_acp.contract_clients.contract_client_v2 import ACPContractClientV2
249+
from virtuals_acp.configs.configs import BASE_MAINNET_CONFIG
250+
251+
class TestIntegrationACPContractClientV2:
252+
"""Integration tests for ACPContractClientV2"""
253+
254+
@pytest.fixture
255+
def wallet_private_key(self):
256+
"""Get private key from environment"""
257+
return os.getenv("WHITELISTED_WALLET_PRIVATE_KEY")
258+
259+
class TestInitialization:
260+
"""Test client initialization with real network"""
261+
262+
@pytest.mark.integration
263+
def test_should_initialize_client_successfully(self, wallet_private_key):
264+
"""Should initialize client with real credentials"""
265+
client = ACPContractClientV2.build(
266+
wallet_private_key,
267+
os.getenv("SELLER_ENTITY_ID"),
268+
os.getenv("SELLER_AGENT_WALLET_ADDRESS"),
269+
BASE_MAINNET_CONFIG
270+
)
271+
272+
assert client is not None
273+
assert client.agent_wallet_address is not None
274+
assert client.config.chain_id == 8453
275+
```
276+
277+
**Important Notes:**
278+
279+
- Integration tests load environment variables from `tests/.env`
280+
- If `.env` is missing, integration tests are skipped with a warning
281+
- Ensure test wallets are funded on the corresponding network (testnet/mainnet)
282+
- Use appropriate timeouts for network operations (`timeout = 300` in pytest.ini)
283+
284+
## Test Configuration
285+
286+
### pytest.ini
287+
288+
Key configuration options:
289+
290+
```ini
291+
[pytest]
292+
testpaths = tests # Where to find tests
293+
python_files = test_*.py # Test file pattern
294+
python_classes = Test* # Test class pattern
295+
python_functions = test_* # Test function pattern
296+
297+
# Markers for organizing tests
298+
markers =
299+
unit: Unit tests
300+
integration: Integration tests
301+
slow: Tests that take a long time
302+
requires_network: Tests requiring network
303+
requires_blockchain: Tests requiring blockchain
304+
305+
# Timeout for tests (prevents hanging)
306+
timeout = 300 # 5 minutes
307+
308+
# Coverage options (uncomment to enable)
309+
# addopts = --cov=virtuals_acp --cov-report=html
310+
```
311+
312+
### conftest.py
313+
314+
The `conftest.py` file handles:
315+
316+
- Loading `.env` variables automatically
317+
- Providing warnings if `.env` is missing
318+
- Shared fixtures across test files
319+
320+
## Best Practices
321+
322+
### Test Organization
323+
324+
- **Group related tests** using nested classes
325+
- **Use descriptive test names** starting with `test_should_`
326+
- **One assertion concept per test** (avoid testing too much in one test)
327+
- **Use fixtures** for reusable test data and mocks
328+
329+
### Mocking
330+
331+
- Always use `pytest.fixture` for shared mocks
332+
- Clear mocks between tests using `mocker.reset_mock()` or fresh fixtures
333+
- Mock at the appropriate level (not too deep, not too shallow)
334+
335+
### Coverage
336+
337+
- Aim for >90% statement coverage
338+
- Aim for >80% branch coverage
339+
- 100% function coverage for public methods
340+
- Use `--cov-report=html` to identify uncovered lines
341+
342+
### Running Tests in CI/CD
343+
344+
Tests run automatically on:
345+
346+
- Pull requests (unit tests only)
347+
- Pushes to main (all tests including integration)
348+
349+
See `.github/workflows/ci-test.yml` for CI configuration.
350+
351+
## Troubleshooting
352+
353+
### Integration Tests Skipped
354+
355+
If you see "Integration tests will be skipped", create `tests/.env` from `tests/.env.example`.
356+
357+
### Import Errors
358+
359+
Make sure to install dev dependencies:
360+
361+
```bash
362+
poetry install
363+
```
364+
365+
### Coverage Not Working
366+
367+
Ensure pytest-cov is installed:
368+
369+
```bash
370+
poetry add --group dev pytest-cov
371+
```

0 commit comments

Comments
 (0)