Skip to content

Chapter 15: Testing

15.2 Integration Testing

LiveServerTestCase

LiveServerTestCase is used to run integration tests that require a real HTTP server:

python
# test_integration.py
from django.test import LiveServerTestCase
from django.contrib.auth.models import User
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
import time

class BlogIntegrationTest(LiveServerTestCase):
    """Blog integration test"""
    
    @classmethod
    def setUpClass(cls):
        """Test class initialization"""
        super().setUpClass()
        
        # Configure Chrome options
        chrome_options = Options()
        chrome_options.add_argument('--headless')  # Headless mode
        chrome_options.add_argument('--no-sandbox')
        chrome_options.add_argument('--disable-dev-shm-usage')
        
        # Start Chrome browser
        cls.browser = webdriver.Chrome(options=chrome_options)
        cls.browser.implicitly_wait(10)
    
    @classmethod
    def tearDownClass(cls):
        """Test class cleanup"""
        cls.browser.quit()
        super().tearDownClass()
    
    def setUp(self):
        """Setup before each test"""
        # Create test user
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.user.save()
        
        # Create test article
        from .models import Article, Category
        self.category = Category.objects.create(name='Test Category')
        self.article = Article.objects.create(
            title='Test Article',
            author=self.user,
            category=self.category,
            content='This is the content of the test article',
            status='published'
        )
        
        super().setUp()
    
    def test_user_can_view_article_list(self):
        """Test that users can view the article list"""
        # Visit article list page
        self.browser.get(f'{self.live_server_url}/')
        
        # Wait for page to load
        WebDriverWait(self.browser, 10).until(
            EC.presence_of_element_located((By.TAG_NAME, "h1"))
        )
        
        # Check page title
        self.assertIn('Blog', self.browser.title)
        
        # Check if article title is displayed
        page_text = self.browser.find_element(By.TAG_NAME, "body").text
        self.assertIn('Test Article', page_text)
    
    def test_user_can_view_article_detail(self):
        """Test that users can view article details"""
        # Visit article detail page
        self.browser.get(
            f'{self.live_server_url}/articles/{self.article.slug}/'
        )
        
        # Wait for page to load
        WebDriverWait(self.browser, 10).until(
            EC.presence_of_element_located((By.TAG_NAME, "h1"))
        )
        
        # Check article title
        title_element = self.browser.find_element(By.TAG_NAME, "h1")
        self.assertEqual(title_element.text, 'Test Article')
        
        # Check article content
        content_element = self.browser.find_element(By.CLASS_NAME, "article-content")
        self.assertIn('This is the content of the test article', content_element.text)
    
    def test_user_can_login_and_create_article(self):
        """Test that users can login and create articles"""
        # Visit login page
        self.browser.get(f'{self.live_server_url}/accounts/login/')
        
        # Fill login form
        username_input = self.browser.find_element(By.NAME, "username")
        password_input = self.browser.find_element(By.NAME, "password")
        login_button = self.browser.find_element(By.XPATH, "//button[@type='submit']")
        
        username_input.send_keys('testuser')
        password_input.send_keys('testpass123')
        login_button.click()
        
        # Wait for successful login and redirect
        WebDriverWait(self.browser, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "alert-success"))
        )
        
        # Visit create article page
        self.browser.get(f'{self.live_server_url}/articles/new/')
        
        # Fill article form
        title_input = self.browser.find_element(By.NAME, "title")
        content_input = self.browser.find_element(By.NAME, "content")
        submit_button = self.browser.find_element(By.XPATH, "//button[@type='submit']")
        
        title_input.send_keys('New Test Article')
        content_input.send_keys('This is the content of the new test article')
        submit_button.click()
        
        # Wait for article creation success
        WebDriverWait(self.browser, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "alert-success"))
        )
        
        # Verify article was created
        self.assertIn('Article created successfully', self.browser.page_source)

# Using Playwright for more modern browser testing
# test_playwright.py
import pytest
from django.test import LiveServerTestCase
from playwright.sync_api import sync_playwright

class PlaywrightBlogTest(LiveServerTestCase):
    """Blog test using Playwright"""
    
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.playwright = sync_playwright().start()
        cls.browser = cls.playwright.chromium.launch(headless=True)
        cls.page = cls.browser.new_page()
    
    @classmethod
    def tearDownClass(cls):
        cls.browser.close()
        cls.playwright.stop()
        super().tearDownClass()
    
    def test_article_search_functionality(self):
        """Test article search functionality"""
        # Create test data
        from .models import Article, Category
        from django.contrib.auth.models import User
        
        user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        category = Category.objects.create(name='Test Category')
        Article.objects.create(
            title='Python Programming Guide',
            author=user,
            category=category,
            content='Python is a powerful programming language',
            status='published'
        )
        Article.objects.create(
            title='JavaScript Introduction',
            author=user,
            category=category,
            content='JavaScript is the core language of frontend development',
            status='published'
        )
        
        # Visit homepage
        self.page.goto(f"{self.live_server_url}/")
        
        # Use search function
        search_input = self.page.locator("input[name='q']")
        search_input.fill("Python")
        search_input.press("Enter")
        
        # Verify search results
        self.page.wait_for_selector(".article-list")
        search_results = self.page.locator(".article-item")
        assert search_results.count() == 1
        assert "Python Programming Guide" in self.page.content()

Selenium Integration

More examples of Selenium integration testing:

python
# test_selenium_advanced.py
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options

class AdvancedSeleniumTest(LiveServerTestCase):
    """Advanced Selenium test"""
    
    def setUp(self):
        """Test setup"""
        chrome_options = Options()
        chrome_options.add_argument('--headless')
        chrome_options.add_argument('--no-sandbox')
        chrome_options.add_argument('--disable-dev-shm-usage')
        
        self.browser = webdriver.Chrome(options=chrome_options)
        self.browser.implicitly_wait(10)
        
        # Create test data
        from django.contrib.auth.models import User
        from .models import Article, Category, Comment
        
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        self.category = Category.objects.create(name='Test Category')
        self.article = Article.objects.create(
            title='Test Article',
            author=self.user,
            category=self.category,
            content='Test article content',
            status='published'
        )
        
        super().setUp()
    
    def tearDown(self):
        """Test cleanup"""
        self.browser.quit()
        super().tearDown()
    
    def test_comment_system(self):
        """Test comment system"""
        # Login
        self.browser.get(f'{self.live_server_url}/accounts/login/')
        self.browser.find_element(By.NAME, "username").send_keys('testuser')
        self.browser.find_element(By.NAME, "password").send_keys('testpass123')
        self.browser.find_element(By.XPATH, "//button[@type='submit']").click()
        
        # Visit article detail
        self.browser.get(
            f'{self.live_server_url}/articles/{self.article.slug}/'
        )
        
        # Add comment
        comment_textarea = self.browser.find_element(By.NAME, "content")
        comment_textarea.send_keys('This is a test comment')
        
        submit_button = self.browser.find_element(
            By.XPATH, "//button[contains(text(), 'Post Comment')]"
        )
        submit_button.click()
        
        # Verify comment was added
        WebDriverWait(self.browser, 10).until(
            EC.presence_of_element_located((By.CLASS_NAME, "comment-content"))
        )
        
        comment_content = self.browser.find_element(
            By.CLASS_NAME, "comment-content"
        ).text
        self.assertIn('This is a test comment', comment_content)
    
    def test_responsive_design(self):
        """Test responsive design"""
        # Test desktop version
        self.browser.set_window_size(1920, 1080)
        self.browser.get(f'{self.live_server_url}/')
        
        # Check desktop navigation
        desktop_nav = self.browser.find_element(By.CLASS_NAME, "navbar-desktop")
        self.assertTrue(desktop_nav.is_displayed())
        
        # Test mobile version
        self.browser.set_window_size(375, 667)
        self.browser.refresh()
        
        # Check mobile navigation
        mobile_nav_toggle = self.browser.find_element(By.CLASS_NAME, "navbar-toggle")
        self.assertTrue(mobile_nav_toggle.is_displayed())
    
    def test_form_validation(self):
        """Test form validation"""
        # Login
        self.browser.get(f'{self.live_server_url}/accounts/login/')
        self.browser.find_element(By.NAME, "username").send_keys('testuser')
        self.browser.find_element(By.NAME, "password").send_keys('testpass123')
        self.browser.find_element(By.XPATH, "//button[@type='submit']").click()
        
        # Visit create article page
        self.browser.get(f'{self.live_server_url}/articles/new/')
        
        # Submit empty form
        submit_button = self.browser.find_element(By.XPATH, "//button[@type='submit']")
        submit_button.click()
        
        # Verify error messages
        error_messages = self.browser.find_elements(By.CLASS_NAME, "error-message")
        self.assertGreater(len(error_messages), 0)
        
        # Fill in valid data and submit
        self.browser.find_element(By.NAME, "title").send_keys('Valid Title')
        self.browser.find_element(By.NAME, "content").send_keys('Valid Content')
        submit_button.click()
        
        # Verify success message
        success_message = self.browser.find_element(By.CLASS_NAME, "alert-success")
        self.assertTrue(success_message.is_displayed())

Test Coverage

Configuring and using test coverage tools:

python
# Install coverage tool
# pip install coverage

# .coveragerc configuration file
"""
[run]
source = .
omit = 
    */venv/*
    */env/*
    */migrations/*
    */tests/*
    manage.py
    */settings/*
    */wsgi.py
    */asgi.py

[report]
exclude_lines =
    pragma: no cover
    def __repr__
    raise AssertionError
    raise NotImplementedError
    if __name__ == .__main__.:

[html]
directory = coverage_html_report
"""

# Run tests with coverage
# coverage run --source='.' manage.py test
# coverage report
# coverage html

# pytest configuration file pytest.ini
"""
[pytest]
DJANGO_SETTINGS_MODULE = myproject.settings
python_files = tests.py test_*.py *_tests.py
addopts = --cov=. --cov-report=html --cov-report=term
"""

# Using pytest for testing
# test_pytest.py
import pytest
from django.test import Client
from django.contrib.auth.models import User
from .models import Article, Category

@pytest.fixture
def client():
    """Test client fixture"""
    return Client()

@pytest.fixture
def user():
    """User fixture"""
    return User.objects.create_user(
        username='testuser',
        password='testpass123'
    )

@pytest.fixture
def article(user):
    """Article fixture"""
    category = Category.objects.create(name='Test Category')
    return Article.objects.create(
        title='Test Article',
        author=user,
        category=category,
        content='Test content',
        status='published'
    )

def test_article_list_view(client, article):
    """Test article list view"""
    response = client.get('/')
    assert response.status_code == 200
    assert 'Test Article' in str(response.content)

def test_article_detail_view(client, article):
    """Test article detail view"""
    response = client.get(f'/articles/{article.slug}/')
    assert response.status_code == 200
    assert 'Test Article' in str(response.content)

@pytest.mark.django_db
def test_article_creation():
    """Test article creation"""
    user = User.objects.create_user(
        username='testuser',
        password='testpass123'
    )
    category = Category.objects.create(name='Test Category')
    
    article = Article.objects.create(
        title='New Article',
        author=user,
        category=category,
        content='New article content',
        status='published'
    )
    
    assert article.title == 'New Article'
    assert article.is_published

# Performance test example
import time
from django.test import TestCase

class PerformanceTest(TestCase):
    """Performance test"""
    
    def setUp(self):
        # Create large amount of test data
        from django.contrib.auth.models import User
        from .models import Article, Category
        
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        self.category = Category.objects.create(name='Test Category')
        
        # Create 1000 articles for performance testing
        for i in range(1000):
            Article.objects.create(
                title=f'Test Article{i}',
                author=self.user,
                category=self.category,
                content=f'Test content{i}',
                status='published'
            )
    
    def test_article_list_performance(self):
        """Test article list performance"""
        start_time = time.time()
        
        # Execute 100 queries
        for _ in range(100):
            response = self.client.get('/articles/')
            self.assertEqual(response.status_code, 200)
        
        end_time = time.time()
        total_time = end_time - start_time
        
        # Assert average response time is less than 100ms
        average_time = total_time / 100
        self.assertLess(average_time, 0.1)

Continuous Integration

Configuring continuous integration environment:

yaml
# .github/workflows/test.yml
name: Django CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:13
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      
      redis:
        image: redis:6
        ports:
          - 6379:6379
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: 3.9
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install coverage
    
    - name: Run migrations
      run: python manage.py migrate
    
    - name: Run tests with coverage
      run: coverage run --source='.' manage.py test
    
    - name: Generate coverage report
      run: |
        coverage report
        coverage xml
    
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v1
      with:
        file: ./coverage.xml
        flags: unittests
        name: codecov-umbrella

Through these integration testing practices, you can ensure that the application's functionality and performance in a real environment meet expectations.

Summary

Core points of Django integration testing:

  1. ✅ Use LiveServerTestCase for browser integration testing
  2. ✅ Master the use of tools like Selenium and Playwright
  3. ✅ Implement test coverage analysis to ensure code quality
  4. ✅ Configure continuous integration environment to automate testing processes
  5. ✅ Focus on performance testing and response time optimization

Integration testing can verify that collaboration between system components is working properly.

Next Article

We will learn about database optimization techniques.

16.1 Database Optimization →

Directory

Return to Course Directory

Released under the [BY-NC-ND License](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.en).