Testing & Quality Assurance¶
Netforce provides comprehensive testing capabilities including unit tests, integration tests, and automated quality assurance. The testing framework supports test-driven development, continuous integration, and performance testing.
Overview¶
The testing system includes:
- Unit Testing - Test individual methods and functions
- Model Testing - Test model logic and data integrity
- API Testing - Test REST and JSON-RPC endpoints
- UI Testing - Test user interface components
- Performance Testing - Load testing and profiling
Testing Framework¶
1. Test Base Classes¶
Foundation classes for different test types:
import unittest
import json
import time
from netforce.database import get_connection
from netforce.model import get_model
class NetforceTestCase(unittest.TestCase):
"""Base test case for Netforce"""
@classmethod
def setUpClass(cls):
"""Setup test database and environment"""
cls.setup_test_database()
cls.setup_test_data()
@classmethod
def tearDownClass(cls):
"""Cleanup after all tests"""
cls.cleanup_test_database()
def setUp(self):
"""Setup for each test"""
self.start_transaction()
self.test_user_id = self.create_test_user()
self.set_active_user(self.test_user_id)
self.test_company_id = self.create_test_company()
self.set_active_company(self.test_company_id)
def tearDown(self):
"""Cleanup after each test"""
self.rollback_transaction()
def create_test_user(self):
"""Create test user"""
return get_model("base.user").create({
"name": "Test User",
"login": f"test_{int(time.time())}",
"password": "test123",
"active": True
})
def create_test_company(self):
"""Create test company"""
return get_model("company").create({
"name": "Test Company",
"code": f"TEST_{int(time.time())}"
})
class ModelTestCase(NetforceTestCase):
"""Test case for model testing"""
def assertRecordExists(self, model_name, search_domain):
"""Assert that a record exists"""
records = get_model(model_name).search(search_domain)
self.assertTrue(records, f"No record found in {model_name} with {search_domain}")
def assertRecordCount(self, model_name, search_domain, expected_count):
"""Assert record count matches expected"""
count = get_model(model_name).search_count(search_domain)
self.assertEqual(count, expected_count,
f"Expected {expected_count} records, found {count}")
def assertFieldValue(self, model_name, record_id, field_name, expected_value):
"""Assert field value matches expected"""
record = get_model(model_name).browse(record_id)
actual_value = getattr(record, field_name)
self.assertEqual(actual_value, expected_value,
f"Field {field_name} expected {expected_value}, got {actual_value}")
class APITestCase(NetforceTestCase):
"""Test case for API testing"""
def setUp(self):
super().setUp()
self.api_client = self.create_api_client()
def create_api_client(self):
"""Create API client for testing"""
from netforce.test.api_client import APIClient
return APIClient(base_url="http://localhost:8080")
def assertAPISuccess(self, response):
"""Assert API response is successful"""
self.assertEqual(response.status_code, 200)
self.assertIsNone(response.json().get("error"))
def assertAPIError(self, response, expected_error=None):
"""Assert API response contains error"""
self.assertGreaterEqual(response.status_code, 400)
if expected_error:
self.assertIn(expected_error, response.json().get("error", ""))
Unit Testing¶
1. Model Unit Tests¶
Test individual model methods:
class TestAccountInvoice(ModelTestCase):
"""Test Account Invoice model"""
def setUp(self):
super().setUp()
self.customer_id = self.create_test_customer()
self.product_id = self.create_test_product()
def test_create_invoice(self):
"""Test creating a new invoice"""
invoice_data = {
"type": "out",
"contact_id": self.customer_id,
"lines": [{
"product_id": self.product_id,
"qty": 2,
"unit_price": 100.00
}]
}
invoice_id = get_model("account.invoice").create(invoice_data)
# Verify invoice was created
self.assertIsNotNone(invoice_id)
# Check field values
invoice = get_model("account.invoice").browse(invoice_id)
self.assertEqual(invoice.type, "out")
self.assertEqual(invoice.contact_id.id, self.customer_id)
self.assertEqual(invoice.amount_total, 200.00)
def test_invoice_validation(self):
"""Test invoice validation logic"""
invoice_id = self.create_test_invoice()
invoice = get_model("account.invoice").browse(invoice_id)
# Test validation
initial_state = invoice.state
invoice.validate()
# Verify state changed
self.assertNotEqual(invoice.state, initial_state)
self.assertEqual(invoice.state, "open")
# Verify number was assigned
self.assertIsNotNone(invoice.number)
def test_calculate_totals(self):
"""Test invoice total calculations"""
invoice_id = self.create_test_invoice(lines=[
{"product_id": self.product_id, "qty": 2, "unit_price": 50.00},
{"product_id": self.product_id, "qty": 1, "unit_price": 30.00}
])
invoice = get_model("account.invoice").browse(invoice_id)
# Test calculations
self.assertEqual(invoice.amount_subtotal, 130.00)
# Assuming 10% tax
self.assertEqual(invoice.amount_tax, 13.00)
self.assertEqual(invoice.amount_total, 143.00)
def test_invalid_invoice_creation(self):
"""Test creating invalid invoice raises error"""
with self.assertRaises(Exception):
get_model("account.invoice").create({
"type": "out",
# Missing required contact_id
"lines": []
})
def create_test_customer(self):
"""Create test customer"""
return get_model("contact").create({
"name": "Test Customer",
"is_customer": True
})
def create_test_product(self):
"""Create test product"""
return get_model("product").create({
"name": "Test Product",
"type": "product",
"sale_price": 100.00
})
def create_test_invoice(self, lines=None):
"""Create test invoice"""
if lines is None:
lines = [{"product_id": self.product_id, "qty": 1, "unit_price": 100.00}]
return get_model("account.invoice").create({
"type": "out",
"contact_id": self.customer_id,
"lines": lines
})
class TestSequenceGeneration(ModelTestCase):
"""Test sequence generation"""
def test_sequence_creation(self):
"""Test creating new sequence"""
seq_data = {
"name": "Test Invoice Sequence",
"type": "cust_invoice",
"prefix": "TEST%(Y)s-",
"padding": 4
}
seq_id = get_model("sequence").create(seq_data)
self.assertIsNotNone(seq_id)
# Test number generation
number = get_model("sequence").get_next_number(seq_id)
self.assertTrue(number.startswith("TEST2023-"))
self.assertEqual(len(number), 13) # TEST2023-0001
def test_sequence_patterns(self):
"""Test various sequence patterns"""
patterns = [
("INV%(Y)s-%(m)s-", r"INV\d{4}-\d{2}-\d{4}"),
("%(contact_code)s-PO", r"\w+-PO\d{4}"),
("SO%(y)s%(m)s%(d)s-", r"SO\d{6}-\d{4}")
]
for pattern, regex in patterns:
seq_id = get_model("sequence").create({
"name": f"Test {pattern}",
"prefix": pattern,
"padding": 4
})
number = get_model("sequence").get_next_number(seq_id, context={
"contact_code": "CUST001"
})
import re
self.assertRegex(number, regex)
2. Function Field Tests¶
Test computed and function fields:
class TestFunctionFields(ModelTestCase):
"""Test function field calculations"""
def test_invoice_total_calculation(self):
"""Test invoice total function field"""
invoice_id = self.create_test_invoice()
# Add invoice lines
line_ids = [
get_model("account.invoice.line").create({
"invoice_id": invoice_id,
"product_id": self.product_id,
"qty": 2,
"unit_price": 50.00
}),
get_model("account.invoice.line").create({
"invoice_id": invoice_id,
"product_id": self.product_id,
"qty": 1,
"unit_price": 30.00
})
]
# Test function field calculation
invoice = get_model("account.invoice").browse(invoice_id)
self.assertEqual(invoice.amount_subtotal, 130.00)
def test_stored_function_field(self):
"""Test stored function field updates"""
customer_id = self.create_test_customer()
# Create invoices for customer
for amount in [100, 200, 150]:
get_model("account.invoice").create({
"type": "out",
"contact_id": customer_id,
"state": "paid",
"amount_total": amount
})
# Check stored total sales field
customer = get_model("contact").browse(customer_id)
self.assertEqual(customer.total_sales, 450.00)
def test_function_field_context(self):
"""Test function field with context"""
invoice_id = self.create_test_invoice()
# Test with different contexts
contexts = [
{"date": "2023-01-01"},
{"currency_id": 1},
{"company_id": self.test_company_id}
]
for context in contexts:
invoice_data = get_model("account.invoice").read([invoice_id],
["amount_total"],
context=context)
self.assertIsNotNone(invoice_data[0]["amount_total"])
Integration Testing¶
1. Workflow Integration Tests¶
Test complete business workflows:
class TestSalesWorkflow(ModelTestCase):
"""Test complete sales workflow"""
def test_quote_to_invoice_workflow(self):
"""Test complete quote to invoice process"""
# 1. Create quotation
quote_id = get_model("sale.order").create({
"type": "quotation",
"contact_id": self.customer_id,
"lines": [{
"product_id": self.product_id,
"qty": 5,
"unit_price": 100.00
}]
})
quote = get_model("sale.order").browse(quote_id)
self.assertEqual(quote.state, "draft")
# 2. Confirm quotation
quote.confirm()
self.assertEqual(quote.state, "confirmed")
# 3. Create invoice
invoice_id = quote.create_invoice()
self.assertIsNotNone(invoice_id)
# 4. Verify invoice data
invoice = get_model("account.invoice").browse(invoice_id)
self.assertEqual(invoice.amount_total, 500.00)
self.assertEqual(invoice.contact_id.id, self.customer_id)
# 5. Validate and pay invoice
invoice.validate()
invoice.pay()
self.assertEqual(invoice.state, "paid")
def test_purchase_to_payment_workflow(self):
"""Test complete purchase workflow"""
supplier_id = self.create_test_supplier()
# 1. Create purchase order
po_id = get_model("purchase.order").create({
"contact_id": supplier_id,
"lines": [{
"product_id": self.product_id,
"qty": 10,
"unit_price": 50.00
}]
})
# 2. Confirm purchase order
po = get_model("purchase.order").browse(po_id)
po.confirm()
# 3. Receive goods
picking_id = po.create_picking()
picking = get_model("stock.picking").browse(picking_id)
picking.validate()
# 4. Create vendor bill
bill_id = po.create_invoice()
bill = get_model("account.invoice").browse(bill_id)
bill.validate()
# 5. Process payment
payment_id = bill.create_payment()
payment = get_model("account.payment").browse(payment_id)
payment.post()
# Verify final states
self.assertEqual(po.state, "done")
self.assertEqual(bill.state, "paid")
def create_test_supplier(self):
"""Create test supplier"""
return get_model("contact").create({
"name": "Test Supplier",
"is_supplier": True
})
2. Multi-Company Integration Tests¶
Test multi-company functionality:
class TestMultiCompanyIntegration(ModelTestCase):
"""Test multi-company integration"""
def setUp(self):
super().setUp()
self.company_a = self.create_test_company("Company A")
self.company_b = self.create_test_company("Company B")
self.user_a = self.create_company_user(self.company_a)
self.user_b = self.create_company_user(self.company_b)
def test_company_data_isolation(self):
"""Test that company data is properly isolated"""
# Create invoice in company A
self.set_active_user(self.user_a)
self.set_active_company(self.company_a)
invoice_a = get_model("account.invoice").create({
"type": "out",
"contact_id": self.customer_id,
"company_id": self.company_a
})
# Switch to company B
self.set_active_user(self.user_b)
self.set_active_company(self.company_b)
# Verify company B cannot see company A's invoice
invoices = get_model("account.invoice").search([])
self.assertNotIn(invoice_a, invoices)
# Create invoice in company B
invoice_b = get_model("account.invoice").create({
"type": "out",
"contact_id": self.customer_id,
"company_id": self.company_b
})
# Verify only company B's invoice is visible
invoices = get_model("account.invoice").search([])
self.assertIn(invoice_b, invoices)
self.assertNotIn(invoice_a, invoices)
def create_company_user(self, company_id):
"""Create user for specific company"""
return get_model("base.user").create({
"name": f"User for {company_id}",
"login": f"user_{company_id}_{int(time.time())}",
"companies": [company_id],
"default_company_id": company_id
})
API Testing¶
1. JSON-RPC API Tests¶
Test JSON-RPC endpoints:
class TestJSONRPCAPI(APITestCase):
"""Test JSON-RPC API endpoints"""
def test_login_api(self):
"""Test login API endpoint"""
response = self.api_client.call("auth.login", {
"login": "admin",
"password": "admin"
})
self.assertAPISuccess(response)
data = response.json()
self.assertIn("session_id", data["result"])
self.assertIn("user_id", data["result"])
def test_model_create_api(self):
"""Test model create via API"""
# Login first
login_response = self.api_client.call("auth.login", {
"login": "admin",
"password": "admin"
})
session_id = login_response.json()["result"]["session_id"]
# Create record
response = self.api_client.call("model.create", {
"model": "contact",
"data": {
"name": "API Test Contact",
"is_customer": True
},
"session_id": session_id
})
self.assertAPISuccess(response)
contact_id = response.json()["result"]
self.assertIsNotNone(contact_id)
# Verify record was created
verify_response = self.api_client.call("model.read", {
"model": "contact",
"ids": [contact_id],
"fields": ["name", "is_customer"],
"session_id": session_id
})
self.assertAPISuccess(verify_response)
data = verify_response.json()["result"][0]
self.assertEqual(data["name"], "API Test Contact")
self.assertTrue(data["is_customer"])
def test_api_error_handling(self):
"""Test API error handling"""
# Test invalid login
response = self.api_client.call("auth.login", {
"login": "invalid",
"password": "invalid"
})
self.assertAPIError(response, "Invalid credentials")
# Test unauthorized access
response = self.api_client.call("model.create", {
"model": "contact",
"data": {"name": "Test"},
"session_id": "invalid_session"
})
self.assertAPIError(response, "Invalid session")
def test_api_permissions(self):
"""Test API permission enforcement"""
# Create limited user
limited_user = get_model("base.user").create({
"name": "Limited User",
"login": f"limited_{int(time.time())}",
"password": "test123"
})
# Login as limited user
response = self.api_client.call("auth.login", {
"login": f"limited_{int(time.time())}",
"password": "test123"
})
session_id = response.json()["result"]["session_id"]
# Try to access restricted model
response = self.api_client.call("model.search", {
"model": "base.user", # Restricted model
"domain": [],
"session_id": session_id
})
self.assertAPIError(response, "Access denied")
2. REST API Tests¶
Test REST endpoints:
class TestRESTAPI(APITestCase):
"""Test REST API endpoints"""
def test_get_records(self):
"""Test GET /api/contacts"""
# Create test data
contact_id = get_model("contact").create({
"name": "REST Test Contact"
})
# Test GET request
response = self.api_client.get(f"/api/contacts")
self.assertEqual(response.status_code, 200)
data = response.json()
self.assertGreater(len(data), 0)
def test_create_record(self):
"""Test POST /api/contacts"""
contact_data = {
"name": "New REST Contact",
"email": "test@example.com"
}
response = self.api_client.post("/api/contacts", json=contact_data)
self.assertEqual(response.status_code, 201)
created_contact = response.json()
self.assertEqual(created_contact["name"], contact_data["name"])
def test_update_record(self):
"""Test PUT /api/contacts/{id}"""
# Create contact
contact_id = get_model("contact").create({
"name": "Original Name"
})
# Update via REST
update_data = {"name": "Updated Name"}
response = self.api_client.put(f"/api/contacts/{contact_id}",
json=update_data)
self.assertEqual(response.status_code, 200)
# Verify update
updated_contact = get_model("contact").browse(contact_id)
self.assertEqual(updated_contact.name, "Updated Name")
Performance Testing¶
1. Load Testing¶
Test system performance under load:
class TestPerformance(NetforceTestCase):
"""Performance and load testing"""
def test_bulk_record_creation(self):
"""Test creating large number of records"""
start_time = time.time()
record_count = 1000
# Create records in batches
batch_size = 100
for i in range(0, record_count, batch_size):
batch_data = []
for j in range(batch_size):
batch_data.append({
"name": f"Bulk Contact {i+j}",
"email": f"bulk{i+j}@example.com"
})
get_model("contact").create_batch(batch_data)
end_time = time.time()
duration = end_time - start_time
# Assert performance requirements
self.assertLess(duration, 30, "Bulk creation took too long")
# Verify all records created
count = get_model("contact").search_count([
["name", "ilike", "Bulk Contact%"]
])
self.assertEqual(count, record_count)
def test_search_performance(self):
"""Test search performance with large dataset"""
# Create test data
self.create_large_dataset(10000)
# Test various search patterns
search_tests = [
([["name", "ilike", "Contact%"]], "name search"),
([["email", "!=", None]], "email filter"),
([["date_create", ">=", "2023-01-01"]], "date range"),
([["name", "ilike", "Contact%"], ["email", "!=", None]], "combined filters")
]
for domain, test_name in search_tests:
start_time = time.time()
results = get_model("contact").search(domain, limit=100)
end_time = time.time()
duration = end_time - start_time
self.assertLess(duration, 2, f"{test_name} search too slow: {duration}s")
self.assertLessEqual(len(results), 100, "Limit not respected")
def test_memory_usage(self):
"""Test memory usage during operations"""
import psutil
import os
process = psutil.Process(os.getpid())
# Get baseline memory
baseline_memory = process.memory_info().rss / 1024 / 1024 # MB
# Perform memory-intensive operation
large_dataset = []
for i in range(10000):
large_dataset.append(get_model("contact").create({
"name": f"Memory Test {i}",
"description": "x" * 1000 # 1KB description
}))
# Check memory usage
peak_memory = process.memory_info().rss / 1024 / 1024 # MB
memory_increase = peak_memory - baseline_memory
self.assertLess(memory_increase, 100,
f"Memory usage too high: {memory_increase}MB")
# Cleanup and check memory release
get_model("contact").delete(large_dataset)
# Force garbage collection
import gc
gc.collect()
final_memory = process.memory_info().rss / 1024 / 1024 # MB
memory_released = peak_memory - final_memory
self.assertGreater(memory_released / memory_increase, 0.8,
"Memory not properly released")
def create_large_dataset(self, count):
"""Create large dataset for testing"""
batch_size = 1000
for i in range(0, count, batch_size):
batch = []
for j in range(min(batch_size, count - i)):
batch.append({
"name": f"Contact {i+j:06d}",
"email": f"contact{i+j}@example.com",
"phone": f"+1-555-{(i+j):04d}",
"date_create": time.strftime("%Y-%m-%d %H:%M:%S")
})
get_model("contact").create_batch(batch)
2. Database Performance Tests¶
Test database query performance:
class TestDatabasePerformance(NetforceTestCase):
"""Test database performance"""
def test_query_optimization(self):
"""Test that queries are properly optimized"""
# Test with EXPLAIN ANALYZE
db = get_connection()
# Test index usage
query = """
EXPLAIN ANALYZE
SELECT * FROM contact
WHERE name ILIKE %s
ORDER BY date_create DESC
LIMIT 100
"""
result = db.execute(query, ["%Test%"])
explain_output = result.fetchall()
# Check that index is used
explain_text = " ".join([str(row) for row in explain_output])
self.assertNotIn("Seq Scan", explain_text,
"Query should use index, not sequential scan")
def test_n_plus_1_queries(self):
"""Test for N+1 query problems"""
# Create test data with relationships
company_ids = []
for i in range(10):
company_id = get_model("company").create({
"name": f"Company {i}"
})
company_ids.append(company_id)
# Create contacts for each company
for j in range(5):
get_model("contact").create({
"name": f"Contact {i}-{j}",
"company_id": company_id
})
# Monitor query count
db = get_connection()
query_count_before = db.query_count
# Load contacts with company names (potential N+1)
contacts = get_model("contact").search_browse([])
company_names = [c.company_id.name for c in contacts if c.company_id]
query_count_after = db.query_count
query_increase = query_count_after - query_count_before
# Should not have N+1 queries (one query per contact)
self.assertLess(query_increase, 15,
f"Too many queries: {query_increase} (possible N+1 problem)")
Testing Utilities¶
1. Test Data Factories¶
Create reusable test data:
class TestDataFactory:
"""Factory for creating test data"""
@staticmethod
def create_user(name="Test User", **kwargs):
"""Create test user"""
data = {
"name": name,
"login": f"test_{int(time.time())}_{name.lower().replace(' ', '_')}",
"password": "test123",
"active": True
}
data.update(kwargs)
return get_model("base.user").create(data)
@staticmethod
def create_company(name="Test Company", **kwargs):
"""Create test company"""
data = {
"name": name,
"code": f"TEST_{int(time.time())}",
"currency": "USD"
}
data.update(kwargs)
return get_model("company").create(data)
@staticmethod
def create_customer(name="Test Customer", **kwargs):
"""Create test customer"""
data = {
"name": name,
"is_customer": True,
"email": f"{name.lower().replace(' ', '.')}@example.com"
}
data.update(kwargs)
return get_model("contact").create(data)
@staticmethod
def create_product(name="Test Product", **kwargs):
"""Create test product"""
data = {
"name": name,
"type": "product",
"sale_price": 100.00,
"cost_price": 50.00
}
data.update(kwargs)
return get_model("product").create(data)
@staticmethod
def create_invoice(customer_id=None, **kwargs):
"""Create test invoice"""
if not customer_id:
customer_id = TestDataFactory.create_customer()
product_id = TestDataFactory.create_product()
data = {
"type": "out",
"contact_id": customer_id,
"lines": [{
"product_id": product_id,
"qty": 1,
"unit_price": 100.00
}]
}
data.update(kwargs)
return get_model("account.invoice").create(data)
# Usage in tests
class TestWithFactory(ModelTestCase):
def test_invoice_with_factory(self):
"""Test using factory for test data"""
customer_id = TestDataFactory.create_customer("John Doe")
invoice_id = TestDataFactory.create_invoice(
customer_id=customer_id,
lines=[{
"product_id": TestDataFactory.create_product("Widget"),
"qty": 5,
"unit_price": 25.00
}]
)
invoice = get_model("account.invoice").browse(invoice_id)
self.assertEqual(invoice.amount_total, 125.00)
2. Mock Objects¶
Create mock objects for testing:
class MockEmailService:
"""Mock email service for testing"""
def __init__(self):
self.sent_emails = []
def send_email(self, to, subject, body, **kwargs):
"""Mock send email"""
self.sent_emails.append({
"to": to,
"subject": subject,
"body": body,
"timestamp": time.time()
})
return True
def get_sent_count(self):
"""Get count of sent emails"""
return len(self.sent_emails)
def clear_sent(self):
"""Clear sent email history"""
self.sent_emails = []
class TestWithMocks(ModelTestCase):
"""Test using mock objects"""
def setUp(self):
super().setUp()
self.mock_email = MockEmailService()
# Replace real email service with mock
self.patch_email_service()
def patch_email_service(self):
"""Replace email service with mock"""
import netforce.email
netforce.email.send_email = self.mock_email.send_email
def test_email_sending(self):
"""Test that emails are sent correctly"""
invoice_id = TestDataFactory.create_invoice()
invoice = get_model("account.invoice").browse(invoice_id)
# Method that sends email
invoice.send_to_customer()
# Verify email was sent
self.assertEqual(self.mock_email.get_sent_count(), 1)
sent_email = self.mock_email.sent_emails[0]
self.assertIn(invoice.contact_id.email, sent_email["to"])
self.assertIn(invoice.number, sent_email["subject"])
Running Tests¶
1. Test Runner¶
Set up test execution:
#!/usr/bin/env python3
"""
Test runner for Netforce
"""
import unittest
import sys
import os
import argparse
def discover_tests(test_dir="tests", pattern="test_*.py"):
"""Discover all test files"""
loader = unittest.TestLoader()
suite = loader.discover(test_dir, pattern=pattern)
return suite
def run_tests(verbosity=2, test_pattern=None):
"""Run all tests"""
if test_pattern:
suite = unittest.TestSuite()
loader = unittest.TestLoader()
# Load specific test pattern
try:
suite.addTest(loader.loadTestsFromName(test_pattern))
except Exception as e:
print(f"Error loading test {test_pattern}: {e}")
return False
else:
suite = discover_tests()
runner = unittest.TextTestRunner(verbosity=verbosity)
result = runner.run(suite)
return result.wasSuccessful()
def main():
parser = argparse.ArgumentParser(description="Netforce Test Runner")
parser.add_argument("-v", "--verbose", action="store_true",
help="Verbose output")
parser.add_argument("-t", "--test", help="Run specific test")
parser.add_argument("--coverage", action="store_true",
help="Generate coverage report")
args = parser.parse_args()
verbosity = 2 if args.verbose else 1
if args.coverage:
import coverage
cov = coverage.Coverage()
cov.start()
success = run_tests(verbosity=verbosity, test_pattern=args.test)
if args.coverage:
cov.stop()
cov.save()
print("\nCoverage Report:")
cov.report()
cov.html_report(directory="htmlcov")
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
2. Continuous Integration¶
Set up CI configuration:
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_netforce
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install coverage pytest
- name: Run tests
env:
DATABASE_URL: postgresql://postgres:postgres@localhost/test_netforce
run: |
python test_runner.py --coverage
- name: Upload coverage
uses: codecov/codecov-action@v1
Best Practices¶
1. Test Organization¶
Structure tests effectively:
tests/
├── unit/
│ ├── test_models.py
│ ├── test_fields.py
│ └── test_utils.py
├── integration/
│ ├── test_workflows.py
│ ├── test_api.py
│ └── test_multi_company.py
├── performance/
│ ├── test_load.py
│ └── test_database.py
├── fixtures/
│ ├── test_data.json
│ └── sample_files/
└── utils/
├── factories.py
├── mocks.py
└── helpers.py
2. Test Data Management¶
Manage test data effectively:
def setup_test_environment():
"""Setup clean test environment"""
# Use separate test database
setup_test_database()
# Load minimal test data
load_test_fixtures()
# Clear caches
clear_all_caches()
def cleanup_test_environment():
"""Cleanup after tests"""
# Clean up test files
cleanup_test_files()
# Reset database
reset_test_database()
# Clear temporary data
clear_temp_data()
Next Steps¶
- Learn about Security for security testing practices
- Explore Advanced Patterns for complex testing scenarios
- Check Background Jobs for testing async operations
- Review Performance for optimization testing