一、要解决的问题

  1. 文本输入产品需求,生成测试用例
  2. 上传Pdf/word(图片、表格),解析后生成测试用例
  3. 生成测试用例前定制默认提示词
  4. 支持不同的大模型,可控制相关度、用例数量生成测试用例
  5. 测试用例支持json、excel、xmind思维导图格式
  6. 可清晰透视消耗的token,用例分布

二、 效果

2.1、文档解析

2.2 定制提示词

2.3、LLM参数调整及联动

2.4 生成效果

表格形式

用例分布及消耗的资源

三、关键技术点解析

3.1、遍历word文档的图片及表格

###要保持文档的结构不变,元信息不变。能支持嵌套表格

def parse_document(self) -> List[Dict[str, Union[str, List]]]:
    """
    解析文档内容,按顺序提取文本、表格和图像
    """
    if not self.document:
        self.load_document()

    # 先提取文档中的所有图片
    all_images = self.extract_images_from_document()

    document_content = []
    element_count = {'text': 0, 'table': 0, 'image': 0}

    try:
        logger.info("开始解析文档内容...")

        for block_type, block in self.iter_block_items(self.document):
            if block_type == 'paragraph':
                paragraph = block
                if self.is_toc_paragraph(paragraph):
                    continue

                # 提取段落中的图片
                images = self.extract_images_from_paragraph(paragraph, all_images)
                for desc, image_text in images:
                    document_content.append({
                        'type': 'image',
                        'content': image_text,
                        'description': desc
                    })
                    element_count['image'] += 1

                text = paragraph.text.strip()
                if text:
                    document_content.append({
                        'type': 'text',
                        'content': text,
                        'style': paragraph.style.name,
                        'font_size': paragraph.style.font.size.pt if paragraph.style.font.size else None
                    })
                    element_count['text'] += 1

            elif block_type == 'table':
                table_data = self.parse_table(block)
                if table_data:
                    document_content.append({
                        'type': 'table',
                        'content': table_data,
                        'rows': len(table_data),
                        'columns': len(table_data[0]) if table_data else 0
                    })
                    element_count['table'] += 1

        logger.info(
            f"文档解析完成。文本段落: {element_count['text']}, 表格: {element_count['table']}, 图像: {element_count['image']}")
        return document_content

    except Exception as e:
        logger.error(f"文档解析过程中发生错误: {str(e)}", exc_info=True)
        raise

3.2 提取图片

def extract_images_from_paragraph(self, paragraph: Paragraph, all_images: Dict[str, io.BytesIO]) -> List[
    Tuple[str, str]]:
    """
    从段落中提取关联的图片
    """
    images = []
    try:
        # 获取段落XML
        p_xml = paragraph._p.xml
        root = ET.fromstring(p_xml)

        # 查找所有drawing元素
        for drawing in root.findall('.//{*}drawing'):
            # 查找blip元素(包含图片引用)
            for blip in drawing.findall('.//{*}blip'):
                rId = blip.attrib.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}embed')
                if rId and rId in self.doc_rels:
                    image_path = 'word/' + self.doc_rels[rId]  # 构建完整图片路径
                    if image_path in all_images:
                        self.image_counter += 1
                        image_stream = all_images[image_path]
                        ocr_text = self.extract_text_from_image(image_stream)
                        images.append((f"Image {self.image_counter}", ocr_text))
    except Exception as e:
        logger.error(f"从段落提取图片失败: {str(e)}")

    return images

图片使用pytesseract 或者百度paddleOCR识别文字。

3.3 提取表格

def parse_table(self, table: Table) -> List[List[Union[str, List]]]:
    """
    解析表格内容,支持嵌套表格
    """
    table_data = []
    try:
        for row in table.rows:
            row_data = []
            for cell in row.cells:
                cell_content = self.parse_table_cell(cell)
                row_data.append(cell_content)
            table_data.append(row_data)
        return table_data
    except Exception as e:
        logger.error(f"表格解析失败: {str(e)}", exc_info=True)
        return []

3.4 选取LLM 大模型

def _load_providers_from_env(self) -> Dict[str, Dict]:
    """
    从.env文件加载LLM提供者配置
    格式示例:
    LLM_PROVIDERS=OpenRoute,QWen,DeepSeek  # 定义启用的提供者
    OPENROUTE_MODELS=gpt-3.5-turbo,gpt-4    # 定义各提供者的模型
    """
    providers = {}
    enabled_providers = os.getenv("LLM_PROVIDERS", "OpenRoute,QWen,DeepSeek,Volces,SiliconFlow,BigModel,HunYuan,QianFan,MoonShot").split(",")

    for provider in enabled_providers:
        provider = provider.strip()
        if not provider:
            continue

        # 获取基础配置
        base_url = os.getenv(f"{provider.upper()}_BASE_URL")
        app_key = os.getenv(f"{provider.upper()}_APP_KEY")

        if not app_key:
            logger.warning(f"跳过未配置APP_KEY的提供者: {provider}")
            continue

        # 获取模型列表
        models_env = os.getenv(f"{provider.upper()}_MODELS", "")
        models = [m.strip() for m in models_env.split(",") if m.strip()]

        if not models:
            logger.warning(f"提供者 {provider} 没有配置可用模型,使用默认")
            models = self._get_default_models(provider)

        providers[provider] = {
            "base_url": base_url,
            "app_key": app_key,
            "models": models,
            "timeout": int(os.getenv(f"{provider.upper()}_TIMEOUT", str(self.timeout))),
            "max_tokens": int(os.getenv(f"{provider.upper()}_MAX_TOKENS", "8192"))
        }

    return providers

3.5 生成测试用例

def generate_testcase(
        self,
        platform: str,
        model: str,
        prompt: str,
        temperature: float = 0.7,
        max_tokens: Optional[int] = None
) -> Tuple[Optional[str], Optional[Dict]]:
    """
    生成测试用例
    :param platform: 平台名称
    :param model: 模型名称
    :param prompt: 提示词
    :param temperature: 温度参数(0-1)
    :param max_tokens: 最大token数(可选)
    :return: (生成的文本, 元数据)
    """
    if platform not in self.available_models:
        logger.error(f"不支持的平台: {platform}")
        return None, None

    provider_config = self.available_models[platform]
    app_key = provider_config["app_key"]
    base_url = provider_config["base_url"]
    models = provider_config["models"]

    if not app_key:
        logger.error(f"{platform} API密钥未配置")
        return None, None

    if model not in models:
        logger.error(f"模型 {model} 不在 {platform} 的可用模型列表中")
        return None, None

    # 使用提供者特定配置或默认值
    timeout = provider_config.get("timeout", self.timeout)
    max_tokens = max_tokens or provider_config.get("max_tokens", 806)
    logger.info(f"base_url={base_url},api_key={app_key}")
    #try:
    client = openai.OpenAI(
        api_key=app_key,
        base_url=base_url,
        timeout=timeout
    )

    # 记录开始时间
    start_time = time.time()
    # 发起请求(带重试机制)
    print(f"prompt={prompt}")
    for attempt in range(self.max_retries + 1):
        try:
            response = client.chat.completions.create(
                model=model,
                messages=[{"role": "user", "content": prompt}],
                temperature=min(max(temperature, 0), 1),  # 确保在0-1范围内
                max_tokens=max_tokens,
                stream=False
            )
            break  # 成功则跳出重试循环
        except Exception as e:
            if attempt == self.max_retries:
                raise
            wait_time = (attempt + 1) * 2  # 指数退避
            logger.warning(f"请求失败,第{attempt + 1}次重试,等待{wait_time}秒...")
            time.sleep(wait_time)

    # 处理响应
    completion = response.choices[0].message.content
    elapsed = time.time() - start_time

    logger.info(
        f"LLM请求成功 | 平台: {platform} | 模型: {model} | "
        f"耗时: {elapsed:.2f}s | 使用token: {response.usage.total_tokens}"
    )

    return completion, {
        "model": model,
        "provider": platform,
        "usage": {
            "prompt_tokens": response.usage.prompt_tokens,
            "completion_tokens": response.usage.completion_tokens,
            "total_tokens": response.usage.total_tokens
        },
        "timing": {
            "response_time": elapsed,
            "start_time": start_time,
            "end_time": time.time()
        },
        "config": {
            "temperature": temperature,
            "max_tokens": max_tokens
        }
    }

3.6 解析应答转为思维导图

def json_to_opml(json_data, title_str):
    opml = ET.Element('opml', version='2.0')
    head = ET.SubElement(opml, 'head')
    title = ET.SubElement(head, 'title')
    title.text = title_str
    body = ET.SubElement(opml, 'body')
    root_outline = ET.SubElement(body, 'outline', {
        'text': "用例根目录"
    })
    build_outline(root_outline, json_data)
    return opml

3.7 更多

加入:

https://t.zsxq.com/ZgMii 获取源代码。或者加v: liangjianzhao2020加入高质量测试效能群

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐