Skip to content

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