diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/codestyle.yml
similarity index 69%
rename from .github/workflows/pre-commit.yml
rename to .github/workflows/codestyle.yml
index 55dd8360e..f49912f42 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/codestyle.yml
@@ -1,4 +1,4 @@
-name: pre-commit
+name: PaddleOCR Code Style Check
 
 on:
   pull_request:
@@ -6,11 +6,13 @@ on:
     branches: ['main', 'release/*']
 
 jobs:
-  pre-commit:
+  check-code-style:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-    - uses: actions/setup-python@v3
+    - uses: actions/checkout@v4
+      with:
+        ref: ${{ github.ref }}
+    - uses: actions/setup-python@v5
       with:
         python-version: '3.10'
     # Install Dependencies for Python
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
new file mode 100644
index 000000000..04333130f
--- /dev/null
+++ b/.github/workflows/tests.yaml
@@ -0,0 +1,30 @@
+name: PaddleOCR PR Tests
+
+on:
+  push:
+  pull_request:
+    branches: ["main", "release/*"]
+
+permissions:
+  contents: read
+
+jobs:
+  test-pr:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v4
+    - name: Set up Python 3.10
+      uses: actions/setup-python@v5
+      with:
+        python-version: "3.10"
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install pytest
+        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+        pip install "paddlepaddle==2.5" requests
+        pip install -e .
+    - name: Test with pytest
+      run: |
+        pytest tests/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5cc16ba23..a94ef86ab 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -35,3 +35,16 @@ repos:
     hooks:
     -   id: black
         files: (.*\.(py|pyi|bzl)|BUILD|.*\.BUILD|WORKSPACE)$
+
+# Flake8
+-   repo: https://github.com/pycqa/flake8
+    rev: 7.0.0
+    hooks:
+    -   id: flake8
+        args:
+            - --count
+            - --select=E9,F63,F7,F82
+            - --show-source
+            - --statistics
+        exclude: ^benchmark/|^test_tipc/
+
diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py
index d2edd93d7..bd0e483c8 100644
--- a/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py
+++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py
@@ -25,7 +25,7 @@ class RandomNoise:
             return data
         data["img"] = (
             random_noise(data["img"], mode="gaussian", clip=True) * 255
-        ).astype(im.dtype)
+        ).astype(data["img"].dtype)
         return data
 
 
diff --git a/deploy/hubserving/kie_ser/module.py b/deploy/hubserving/kie_ser/module.py
index 2c046be07..fa95a91f7 100644
--- a/deploy/hubserving/kie_ser/module.py
+++ b/deploy/hubserving/kie_ser/module.py
@@ -142,7 +142,7 @@ class KIESer(hub.Module):
 
 
 if __name__ == "__main__":
-    ocr = OCRSystem()
+    ocr = KIESer()
     ocr._initialize()
     image_path = [
         "./doc/imgs/11.jpg",
diff --git a/deploy/hubserving/kie_ser_re/module.py b/deploy/hubserving/kie_ser_re/module.py
index 4f2bc4479..5e30a51a7 100644
--- a/deploy/hubserving/kie_ser_re/module.py
+++ b/deploy/hubserving/kie_ser_re/module.py
@@ -144,7 +144,7 @@ class KIESerRE(hub.Module):
 
 
 if __name__ == "__main__":
-    ocr = OCRSystem()
+    ocr = KIESerRE()
     ocr._initialize()
     image_path = [
         "./doc/imgs/11.jpg",
diff --git a/ppocr/data/imaug/label_ops.py b/ppocr/data/imaug/label_ops.py
index 39e413b2d..58c6610f8 100644
--- a/ppocr/data/imaug/label_ops.py
+++ b/ppocr/data/imaug/label_ops.py
@@ -841,11 +841,11 @@ class TableBoxEncode(object):
         return data
 
     def xyxyxyxy2xywh(self, boxes):
-        new_bboxes = np.zeros([len(bboxes), 4])
-        new_bboxes[:, 0] = bboxes[:, 0::2].min()  # x1
-        new_bboxes[:, 1] = bboxes[:, 1::2].min()  # y1
-        new_bboxes[:, 2] = bboxes[:, 0::2].max() - new_bboxes[:, 0]  # w
-        new_bboxes[:, 3] = bboxes[:, 1::2].max() - new_bboxes[:, 1]  # h
+        new_bboxes = np.zeros([len(boxes), 4])
+        new_bboxes[:, 0] = boxes[:, 0::2].min()  # x1
+        new_bboxes[:, 1] = boxes[:, 1::2].min()  # y1
+        new_bboxes[:, 2] = boxes[:, 0::2].max() - new_bboxes[:, 0]  # w
+        new_bboxes[:, 3] = boxes[:, 1::2].max() - new_bboxes[:, 1]  # h
         return new_bboxes
 
     def xyxy2xywh(self, bboxes):
diff --git a/ppocr/losses/distillation_loss.py b/ppocr/losses/distillation_loss.py
index 4d0f751c5..6313f8d8e 100644
--- a/ppocr/losses/distillation_loss.py
+++ b/ppocr/losses/distillation_loss.py
@@ -1184,7 +1184,9 @@ class DistillCTCLogits(KLCTCLogits):
             loss = super().forward(out1, out2, ctc_label)
             if isinstance(loss, dict):
                 for key in loss:
-                    loss_dict["{}_{}_{}".format(self.name, model_name, idx)] = loss[key]
+                    loss_dict[
+                        "{}_{}_{}".format(self.name, self.model_name_pairs, idx)
+                    ] = loss[key]
             else:
                 loss_dict["{}_{}".format(self.name, idx)] = loss
         return loss_dict
diff --git a/ppocr/metrics/vqa_token_re_metric.py b/ppocr/metrics/vqa_token_re_metric.py
index d39917000..8c85be576 100644
--- a/ppocr/metrics/vqa_token_re_metric.py
+++ b/ppocr/metrics/vqa_token_re_metric.py
@@ -19,7 +19,7 @@ from __future__ import print_function
 import numpy as np
 import paddle
 
-__all__ = ["KIEMetric"]
+__all__ = ["VQAReTokenMetric"]
 
 
 class VQAReTokenMetric(object):
diff --git a/ppocr/metrics/vqa_token_ser_metric.py b/ppocr/metrics/vqa_token_ser_metric.py
index b6033c3ae..3afcb0518 100644
--- a/ppocr/metrics/vqa_token_ser_metric.py
+++ b/ppocr/metrics/vqa_token_ser_metric.py
@@ -19,7 +19,7 @@ from __future__ import print_function
 import numpy as np
 import paddle
 
-__all__ = ["KIEMetric"]
+__all__ = ["VQASerTokenMetric"]
 
 
 class VQASerTokenMetric(object):
diff --git a/ppocr/modeling/backbones/rec_efficientb3_pren.py b/ppocr/modeling/backbones/rec_efficientb3_pren.py
index d153ad6d8..916a090e2 100644
--- a/ppocr/modeling/backbones/rec_efficientb3_pren.py
+++ b/ppocr/modeling/backbones/rec_efficientb3_pren.py
@@ -27,7 +27,7 @@ import paddle
 import paddle.nn as nn
 import paddle.nn.functional as F
 
-__all__ = ["EfficientNetb3"]
+__all__ = ["EfficientNetb3_PREN"]
 
 GlobalParams = collections.namedtuple(
     "GlobalParams",
diff --git a/ppocr/modeling/heads/rec_aster_head.py b/ppocr/modeling/heads/rec_aster_head.py
index ba0acaeeb..dbef77f68 100644
--- a/ppocr/modeling/heads/rec_aster_head.py
+++ b/ppocr/modeling/heads/rec_aster_head.py
@@ -132,7 +132,7 @@ class AttentionRecognitionHead(nn.Layer):
         # Decoder
         state = paddle.zeros([1, batch_size, self.sDim])
 
-        predicted_ids, predicted_scores = [], []
+        predicted_ids, predicted_scores, predicted = [], [], None
         for i in range(self.max_len_labels):
             if i == 0:
                 y_prev = paddle.full(shape=[batch_size], fill_value=self.num_classes)
diff --git a/ppocr/utils/loggers/wandb_logger.py b/ppocr/utils/loggers/wandb_logger.py
index 83596d86d..3b528b3fa 100644
--- a/ppocr/utils/loggers/wandb_logger.py
+++ b/ppocr/utils/loggers/wandb_logger.py
@@ -1,5 +1,8 @@
 import os
 from .base_logger import BaseLogger
+from ppocr.utils.logging import get_logger
+
+logger = get_logger()
 
 
 class WandbLogger(BaseLogger):
@@ -11,7 +14,7 @@ class WandbLogger(BaseLogger):
         entity=None,
         save_dir=None,
         config=None,
-        **kwargs
+        **kwargs,
     ):
         try:
             import wandb
diff --git a/tests/test_paddleocr_api.py b/tests/test_paddleocr_api.py
new file mode 100644
index 000000000..0af794d2b
--- /dev/null
+++ b/tests/test_paddleocr_api.py
@@ -0,0 +1,116 @@
+from typing import Any
+
+import pytest
+from paddleocr import PaddleOCR, PPStructure
+
+
+# Test image paths
+IMAGE_PATHS_OCR = ["./doc/imgs_en/254.jpg", "./doc/imgs_en/img_10.jpg"]
+IMAGE_PATHS_STRUCTURE = [
+    "./ppstructure/docs/table/layout.jpg",
+    "./ppstructure/docs/table/1.png",
+]
+
+
+@pytest.fixture(params=["en", "ch"])
+def ocr_engine(request: Any) -> PaddleOCR:
+    """
+    Initialize PaddleOCR engine with different languages.
+
+    Args:
+        request: pytest fixture request object.
+
+    Returns:
+        An instance of PaddleOCR.
+    """
+    return PaddleOCR(lang=request.param)
+
+
+def test_ocr_initialization(ocr_engine: PaddleOCR) -> None:
+    """
+    Test PaddleOCR initialization.
+
+    Args:
+        ocr_engine: An instance of PaddleOCR.
+    """
+    assert ocr_engine is not None
+
+
+@pytest.mark.parametrize("image_path", IMAGE_PATHS_OCR)
+def test_ocr_function(ocr_engine: PaddleOCR, image_path: str) -> None:
+    """
+    Test PaddleOCR OCR functionality with different images.
+
+    Args:
+        ocr_engine: An instance of PaddleOCR.
+        image_path: Path to the image to be processed.
+    """
+    result = ocr_engine.ocr(image_path)
+    assert result is not None
+    assert isinstance(result, list)
+
+
+@pytest.mark.parametrize("image_path", IMAGE_PATHS_OCR)
+def test_ocr_det_only(ocr_engine: PaddleOCR, image_path: str) -> None:
+    """
+    Test PaddleOCR OCR functionality with detection only.
+
+    Args:
+        ocr_engine: An instance of PaddleOCR.
+        image_path: Path to the image to be processed.
+    """
+    result = ocr_engine.ocr(image_path, det=True, rec=False)
+    assert result is not None
+    assert isinstance(result, list)
+
+
+@pytest.mark.parametrize("image_path", IMAGE_PATHS_OCR)
+def test_ocr_rec_only(ocr_engine: PaddleOCR, image_path: str) -> None:
+    """
+    Test PaddleOCR OCR functionality with recognition only.
+
+    Args:
+        ocr_engine: An instance of PaddleOCR.
+        image_path: Path to the image to be processed.
+    """
+    result = ocr_engine.ocr(image_path, det=False, rec=True)
+    assert result is not None
+    assert isinstance(result, list)
+
+
+@pytest.fixture(params=["en", "ch"])
+def structure_engine(request: Any) -> PPStructure:
+    """
+    Initialize PPStructure engine with different languages.
+
+    Args:
+        request: pytest fixture request object.
+
+    Returns:
+        An instance of PPStructure.
+    """
+    return PPStructure(lang=request.param)
+
+
+def test_structure_initialization(structure_engine: PPStructure) -> None:
+    """
+    Test PPStructure initialization.
+
+    Args:
+        structure_engine: An instance of PPStructure.
+    """
+    assert structure_engine is not None
+
+
+@pytest.mark.parametrize("image_path", IMAGE_PATHS_STRUCTURE)
+def test_structure_function(structure_engine: PPStructure, image_path: str) -> None:
+    """
+    Test PPStructure structure analysis functionality with different images.
+
+    Args:
+        structure_engine: An instance of PPStructure.
+        image_path: Path to the image to be processed.
+    """
+    result = structure_engine(image_path)
+    assert result is not None
+    assert isinstance(result, list)