Banana Vision Studio自动化测试Python脚本开发如果你正在用Banana Vision Studio做产品拆解图有没有想过一个问题每次更新模型或者调整参数后怎么确保生成的效果还是稳定的我最近就遇到了这个麻烦。团队更新了一个小版本结果生成的爆炸图里有几个零件的标注箭头位置全乱了。客户那边等着要图这边却要重新检查几十张图那叫一个手忙脚乱。后来我们痛定思痛决定搞一套自动化测试体系。现在每次更新跑一遍脚本就知道有没有问题心里踏实多了。今天我就把这套方法分享给你让你也能轻松搞定Banana Vision Studio的质量稳定性。1. 为什么需要自动化测试你可能觉得Banana Vision Studio就是个生成图片的工具手动测测不就行了刚开始我也这么想但实际用下来手动测试有几个大问题时间成本太高一张复杂的工业拆解图从上传参考图到生成结果少说也要一两分钟。测个十几种场景半小时就没了。要是测几十种半天时间就搭进去了。容易遗漏今天测了A功能明天更新了B功能你可能会忘记再测A。结果就是新功能没问题老功能却出了bug。结果不稳定人工判断“这张图好不好”主观性太强。你觉得箭头位置偏了一点没关系我觉得必须精确到像素。没有统一标准团队内部都容易扯皮。回归测试困难每次更新后要把所有功能重新测一遍这个工作量想想都头疼。很多人就偷懒只测新功能结果就是老bug时不时冒出来。自动化测试就是为了解决这些问题。写一次脚本以后每次更新一键运行几分钟内就能知道这次更新有没有引入新问题。更重要的是它能给出客观的测试结果减少人为判断的误差。2. 环境准备与基础框架搭建2.1 Python环境配置首先确保你的Python环境是3.8或以上版本。我推荐用虚拟环境这样不同项目的依赖不会互相干扰。# 创建虚拟环境 python -m venv banana_test_env # 激活虚拟环境 # Windows banana_test_env\Scripts\activate # Mac/Linux source banana_test_env/bin/activate # 安装必要依赖 pip install requests pillow opencv-python numpy pytest如果你用的是CSDN星图平台部署的Banana Vision Studio还需要安装对应的SDK# 安装星图平台SDK pip install csdn-ai-sdk2.2 项目结构规划一个好的项目结构能让后续维护轻松很多。我建议这样组织banana_vision_tests/ ├── tests/ # 测试用例目录 │ ├── __init__.py │ ├── test_basic.py # 基础功能测试 │ ├── test_quality.py # 质量测试 │ └── test_performance.py # 性能测试 ├── utils/ # 工具函数 │ ├── __init__.py │ ├── api_client.py # API封装 │ └── image_utils.py # 图片处理工具 ├── config/ # 配置文件 │ └── settings.py ├── data/ # 测试数据 │ ├── input_images/ # 输入图片 │ └── expected_results/ # 预期结果 ├── reports/ # 测试报告 ├── requirements.txt # 依赖列表 └── run_tests.py # 测试运行入口2.3 基础测试框架选择Python里测试框架很多我推荐用unittest因为它是Python标准库自带的不需要额外安装而且功能足够强大。如果你需要更灵活的测试也可以考虑pytest。不过对于Banana Vision Studio这种API测试unittest完全够用。3. 核心测试用例设计与实现3.1 API连接测试这是最基础的测试确保我们能正常连接到Banana Vision Studio服务。import unittest import requests import time from utils.api_client import BananaVisionClient class TestAPIConnection(unittest.TestCase): 测试API连接基础功能 def setUp(self): 每个测试用例执行前的准备工作 # 从配置文件读取API配置 self.client BananaVisionClient( api_keyyour_api_key_here, base_urlhttps://your-banana-vision-endpoint.com ) # 准备测试图片 self.test_image_path data/input_images/test_product.jpg def test_api_health_check(self): 测试API健康状态 try: response self.client.health_check() self.assertEqual(response.status_code, 200) self.assertIn(status, response.json()) self.assertEqual(response.json()[status], healthy) print( API健康检查通过) except Exception as e: self.fail(fAPI健康检查失败: {str(e)}) def test_basic_image_upload(self): 测试图片上传功能 try: # 上传图片并获取任务ID task_id self.client.upload_image(self.test_image_path) self.assertIsNotNone(task_id) self.assertTrue(len(task_id) 10) # 任务ID应该有足够长度 # 等待处理完成 max_wait_time 60 # 最大等待60秒 wait_interval 2 # 每2秒检查一次 for _ in range(max_wait_time // wait_interval): status self.client.get_task_status(task_id) if status completed: break elif status failed: self.fail(图片处理失败) time.sleep(wait_interval) else: self.fail(处理超时) # 获取处理结果 result self.client.get_result(task_id) self.assertIsNotNone(result) self.assertIn(image_url, result) print( 基础图片上传测试通过) except Exception as e: self.fail(f图片上传测试失败: {str(e)}) def test_invalid_image_format(self): 测试无效图片格式处理 # 创建一个无效的图片文件 invalid_image_path data/input_images/invalid.txt with open(invalid_image_path, w) as f: f.write(This is not an image) try: task_id self.client.upload_image(invalid_image_path) # 如果上传成功检查任务状态应该是失败 if task_id: status self.client.get_task_status(task_id) self.assertEqual(status, failed) print( 无效图片格式处理测试通过) except requests.exceptions.HTTPError as e: # 或者API直接返回错误 self.assertEqual(e.response.status_code, 400) print( 无效图片格式处理测试通过API正确拒绝)3.2 生成质量测试对于Banana Vision Studio来说生成图片的质量是最关键的。我们需要测试生成的拆解图是否满足要求。import cv2 import numpy as np from PIL import Image class TestGenerationQuality(unittest.TestCase): 测试生成质量 def test_exploded_view_accuracy(self): 测试爆炸图准确性 # 准备输入图片和预期结果 input_image data/input_images/mechanical_part.jpg expected_result data/expected_results/exploded_view.png # 调用API生成爆炸图 result self.client.generate_exploded_view( image_pathinput_image, styletechnical_illustration, detail_levelhigh ) # 保存生成结果 generated_image reports/generated_exploded_view.png result.save(generated_image) # 对比生成结果和预期结果 similarity self.compare_images(generated_image, expected_result) # 相似度应该达到一定阈值 self.assertGreaterEqual(similarity, 0.85, f图片相似度不足: {similarity:.2f}) print(f 爆炸图准确性测试通过相似度: {similarity:.2f}) def test_label_position_accuracy(self): 测试标注位置准确性 # 生成带标注的拆解图 result self.client.generate_labeled_diagram( image_pathdata/input_images/electronic_device.jpg, label_stylearrow_with_text ) # 使用OpenCV检测箭头和文字位置 image cv2.imread(reports/generated_labeled.png) gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 检测箭头使用模板匹配或特征检测 arrow_positions self.detect_arrows(gray) # 检查箭头数量 self.assertGreaterEqual(len(arrow_positions), 5, 标注箭头数量不足) # 检查箭头分布是否均匀 positions_array np.array(arrow_positions) x_std np.std(positions_array[:, 0]) y_std np.std(positions_array[:, 1]) # 标准差不能太大说明分布不能太集中 self.assertLess(x_std, image.shape[1] * 0.3, 箭头X方向分布不均匀) self.assertLess(y_std, image.shape[0] * 0.3, 箭头Y方向分布不均匀) print(f 标注位置测试通过检测到{len(arrow_positions)}个箭头) def compare_images(self, img1_path, img2_path, threshold0.8): 比较两张图片的相似度 # 使用结构相似性指数SSIM from skimage.metrics import structural_similarity as ssim img1 cv2.imread(img1_path) img2 cv2.imread(img2_path) # 调整到相同尺寸 if img1.shape ! img2.shape: img2 cv2.resize(img2, (img1.shape[1], img1.shape[0])) # 转换为灰度图 gray1 cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) gray2 cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 计算SSIM score, _ ssim(gray1, gray2, fullTrue) return score def detect_arrows(self, gray_image): 检测图片中的箭头 # 使用边缘检测 edges cv2.Canny(gray_image, 50, 150) # 使用霍夫变换检测直线 lines cv2.HoughLinesP(edges, 1, np.pi/180, threshold50, minLineLength30, maxLineGap10) arrow_positions [] if lines is not None: for line in lines: x1, y1, x2, y2 line[0] # 计算线段中点作为箭头位置 mid_x (x1 x2) // 2 mid_y (y1 y2) // 2 arrow_positions.append((mid_x, mid_y)) return arrow_positions3.3 性能与稳定性测试除了功能正确性我们还需要关注性能和稳定性。import time import statistics class TestPerformanceAndStability(unittest.TestCase): 测试性能与稳定性 def test_response_time(self): 测试响应时间 test_cases [ (small_image.jpg, 简单产品), (medium_complexity.jpg, 中等复杂度), (high_complexity.jpg, 高复杂度) ] response_times [] for image_file, description in test_cases: image_path fdata/input_images/{image_file} start_time time.time() result self.client.generate_exploded_view( image_pathimage_path, styletechnical_illustration ) end_time time.time() response_time end_time - start_time response_times.append(response_time) print(f{description}响应时间: {response_time:.2f}秒) # 单次测试的响应时间要求 if 高复杂度 in description: self.assertLess(response_time, 120, 高复杂度图片处理超时) elif 中等复杂度 in description: self.assertLess(response_time, 60, 中等复杂度图片处理超时) else: self.assertLess(response_time, 30, 简单图片处理超时) # 计算平均响应时间 avg_time statistics.mean(response_times) print(f 平均响应时间: {avg_time:.2f}秒) def test_concurrent_requests(self): 测试并发请求处理能力 import threading results [] errors [] def make_request(request_id): 单个请求线程 try: start_time time.time() result self.client.generate_exploded_view( image_pathdata/input_images/test_product.jpg, styletechnical_illustration ) end_time time.time() results.append({ id: request_id, time: end_time - start_time, success: True }) except Exception as e: errors.append({ id: request_id, error: str(e) }) # 创建5个并发线程 threads [] for i in range(5): thread threading.Thread(targetmake_request, args(i,)) threads.append(thread) thread.start() # 等待所有线程完成 for thread in threads: thread.join() # 检查结果 self.assertEqual(len(errors), 0, f并发请求出现错误: {errors}) self.assertEqual(len(results), 5, 并发请求数量不符) # 检查响应时间差异不能太大 response_times [r[time] for r in results] time_std statistics.stdev(response_times) self.assertLess(time_std, 10.0, 并发请求响应时间差异过大) print(f 并发测试通过5个请求平均时间: {statistics.mean(response_times):.2f}秒) def test_long_running_stability(self): 测试长时间运行的稳定性 total_requests 20 successful_requests 0 for i in range(total_requests): try: # 交替使用不同的生成模式 if i % 3 0: result self.client.generate_exploded_view( image_pathdata/input_images/test_product.jpg, styletechnical_illustration ) elif i % 3 1: result self.client.generate_labeled_diagram( image_pathdata/input_images/test_product.jpg, label_stylearrow_with_text ) else: result self.client.generate_cutaway_view( image_pathdata/input_images/test_product.jpg, cut_planevertical ) successful_requests 1 print(f请求 {i1}/{total_requests} 成功) # 每5个请求休息一下模拟真实使用场景 if (i 1) % 5 0: time.sleep(2) except Exception as e: print(f请求 {i1} 失败: {str(e)}) success_rate successful_requests / total_requests self.assertGreaterEqual(success_rate, 0.9, f稳定性测试成功率不足: {success_rate:.2%}) print(f 稳定性测试通过成功率: {success_rate:.2%})4. 高级功能与异常处理4.1 参数边界测试测试各种参数组合的边界情况确保API能够正确处理。class TestParameterBoundaries(unittest.TestCase): 测试参数边界情况 def test_style_parameter_variations(self): 测试不同风格参数 styles [ technical_illustration, exploded_view, cutaway, schematic, photorealistic ] for style in styles: try: result self.client.generate_diagram( image_pathdata/input_images/test_product.jpg, stylestyle, detail_levelmedium ) self.assertIsNotNone(result) print(f 风格 {style} 测试通过) except Exception as e: # 如果API不支持某种风格应该明确返回错误 self.assertIn(unsupported style, str(e).lower()) print(f 风格 {style} 不被支持符合预期) def test_detail_level_boundaries(self): 测试细节级别边界 # 测试有效的细节级别 valid_levels [low, medium, high, ultra] for level in valid_levels: result self.client.generate_diagram( image_pathdata/input_images/test_product.jpg, styletechnical_illustration, detail_levellevel ) self.assertIsNotNone(result) # 测试无效的细节级别 invalid_levels [, invalid, 最高, None] for level in invalid_levels: try: result self.client.generate_diagram( image_pathdata/input_images/test_product.jpg, styletechnical_illustration, detail_levellevel ) # 如果执行到这里说明API没有正确验证参数 self.fail(f无效参数 {level} 应该被拒绝) except Exception as e: # 期望API返回参数错误 self.assertIn(invalid, str(e).lower()) def test_image_size_limits(self): 测试图片大小限制 # 测试过小的图片 small_image self.create_test_image(100, 100) # 100x100像素 try: result self.client.generate_diagram( image_datasmall_image, styletechnical_illustration ) # 有些API可能支持小图有些可能拒绝 # 这里我们只记录不强制失败 print( 小尺寸图片被接受) except Exception as e: print(f 小尺寸图片被正确拒绝: {str(e)}) # 测试过大的图片需要根据API限制调整 large_image self.create_test_image(10000, 10000) # 可能超限 try: result self.client.generate_diagram( image_datalarge_image, styletechnical_illustration ) print( 大尺寸图片被接受) except Exception as e: print(f 大尺寸图片被正确拒绝: {str(e)}) def create_test_image(self, width, height): 创建测试图片 from PIL import Image, ImageDraw image Image.new(RGB, (width, height), colorwhite) draw ImageDraw.Draw(image) # 画一个简单的矩形让图片有点内容 draw.rectangle([10, 10, width-10, height-10], outlineblack, width2) return image4.2 错误处理与恢复良好的错误处理能让测试更加健壮。class TestErrorHandling(unittest.TestCase): 测试错误处理机制 def test_network_timeout_handling(self): 测试网络超时处理 # 创建一个会超时的客户端设置很短的超时时间 timeout_client BananaVisionClient( api_keytest_key, base_urlhttps://your-banana-vision-endpoint.com, timeout0.1 # 0.1秒超时几乎肯定会超时 ) try: result timeout_client.generate_diagram( image_pathdata/input_images/test_product.jpg ) self.fail(应该触发超时错误) except requests.exceptions.Timeout: print( 网络超时被正确捕获) except Exception as e: # 其他类型的错误也可以接受 print(f 错误被捕获: {type(e).__name__}) def test_rate_limit_handling(self): 测试速率限制处理 # 快速发送多个请求触发速率限制 errors_count 0 for i in range(20): # 发送20个快速请求 try: result self.client.generate_diagram( image_pathdata/input_images/test_product.jpg ) time.sleep(0.1) # 稍微休息一下 except requests.exceptions.HTTPError as e: if e.response.status_code 429: # Too Many Requests errors_count 1 print(f请求 {i1} 被速率限制) # 遇到速率限制后等待一段时间 time.sleep(2) else: raise # 应该至少触发一次速率限制 self.assertGreater(errors_count, 0, 应该触发速率限制) print(f 速率限制处理测试通过触发 {errors_count} 次限制) def test_invalid_api_key(self): 测试无效API密钥处理 invalid_client BananaVisionClient( api_keyinvalid_key_123456, base_urlhttps://your-banana-vision-endpoint.com ) try: result invalid_client.generate_diagram( image_pathdata/input_images/test_product.jpg ) self.fail(无效API密钥应该被拒绝) except requests.exceptions.HTTPError as e: self.assertEqual(e.response.status_code, 401) # Unauthorized print( 无效API密钥被正确拒绝) def test_service_unavailable(self): 测试服务不可用时的处理 # 使用一个不存在的端点 unavailable_client BananaVisionClient( api_keytest_key, base_urlhttps://nonexistent-endpoint-12345.com ) try: result unavailable_client.generate_diagram( image_pathdata/input_images/test_product.jpg ) self.fail(应该无法连接到服务) except requests.exceptions.ConnectionError: print( 服务不可用错误被正确捕获) except Exception as e: print(f 连接错误被捕获: {type(e).__name__})5. 测试报告与持续集成5.1 生成详细测试报告测试不仅要能运行还要能生成清晰的报告。import json import datetime from html import escape class TestReporter: 测试报告生成器 def __init__(self): self.results [] self.start_time None self.end_time None def start_test_run(self): 开始测试运行 self.start_time datetime.datetime.now() print(f 测试开始于: {self.start_time}) def add_test_result(self, test_name, status, message, detailsNone): 添加测试结果 result { test_name: test_name, status: status, # passed, failed, skipped, error message: message, timestamp: datetime.datetime.now().isoformat(), details: details or {} } self.results.append(result) # 控制台输出 status_icon if status passed else if status failed else print(f{status_icon} {test_name}: {message}) def end_test_run(self): 结束测试运行 self.end_time datetime.datetime.now() duration self.end_time - self.start_time # 统计结果 total len(self.results) passed sum(1 for r in self.results if r[status] passed) failed sum(1 for r in self.results if r[status] failed) print(f\n 测试完成!) print(f 总测试数: {total}) print(f 通过: {passed}) print(f 失败: {failed}) print(f 耗时: {duration.total_seconds():.2f}秒) # 生成报告文件 self.generate_html_report() self.generate_json_report() def generate_html_report(self): 生成HTML格式的测试报告 html_content f !DOCTYPE html html head titleBanana Vision Studio 测试报告/title style body {{ font-family: Arial, sans-serif; margin: 40px; }} .summary {{ background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 30px; }} .test-result {{ padding: 10px; margin: 5px 0; border-left: 4px solid; }} .passed {{ border-color: #4CAF50; background: #e8f5e9; }} .failed {{ border-color: #f44336; background: #ffebee; }} .skipped {{ border-color: #ff9800; background: #fff3e0; }} .stats {{ display: flex; gap: 20px; margin-top: 10px; }} .stat-box {{ padding: 10px; border-radius: 5px; text-align: center; min-width: 100px; }} .stat-passed {{ background: #4CAF50; color: white; }} .stat-failed {{ background: #f44336; color: white; }} .stat-total {{ background: #2196F3; color: white; }} /style /head body h1Banana Vision Studio 自动化测试报告/h1 div classsummary h2测试概览/h2 p测试时间: {self.start_time} 到 {self.end_time}/p p总耗时: {(self.end_time - self.start_time).total_seconds():.2f}秒/p div classstats div classstat-box stat-total h3总测试数/h3 p{len(self.results)}/p /div div classstat-box stat-passed h3通过/h3 p{sum(1 for r in self.results if r[status] passed)}/p /div div classstat-box stat-failed h3失败/h3 p{sum(1 for r in self.results if r[status] failed)}/p /div /div /div h2详细测试结果/h2 for result in self.results: status_class result[status] html_content f div classtest-result {status_class} h3{escape(result[test_name])}/h3 p状态: strong{result[status].upper()}/strong/p p时间: {result[timestamp]}/p p信息: {escape(result[message])}/p /div html_content /body /html report_path freports/test_report_{self.start_time.strftime(%Y%m%d_%H%M%S)}.html with open(report_path, w, encodingutf-8) as f: f.write(html_content) print(f HTML报告已生成: {report_path}) def generate_json_report(self): 生成JSON格式的测试报告 report_data { test_run: { start_time: self.start_time.isoformat(), end_time: self.end_time.isoformat(), duration_seconds: (self.end_time - self.start_time).total_seconds() }, summary: { total_tests: len(self.results), passed: sum(1 for r in self.results if r[status] passed), failed: sum(1 for r in self.results if r[status] failed), skipped: sum(1 for r in self.results if r[status] skipped) }, results: self.results } report_path freports/test_report_{self.start_time.strftime(%Y%m%d_%H%M%S)}.json with open(report_path, w, encodingutf-8) as f: json.dump(report_data, f, indent2, ensure_asciiFalse) print(f JSON报告已生成: {report_path}) # 在测试运行中使用报告器 def run_all_tests(): 运行所有测试并生成报告 reporter TestReporter() reporter.start_test_run() # 创建测试套件 loader unittest.TestLoader() suite unittest.TestSuite() # 添加测试类 suite.addTests(loader.loadTestsFromTestCase(TestAPIConnection)) suite.addTests(loader.loadTestsFromTestCase(TestGenerationQuality)) suite.addTests(loader.loadTestsFromTestCase(TestPerformanceAndStability)) suite.addTests(loader.loadTestsFromTestCase(TestParameterBoundaries)) suite.addTests(loader.loadTestsFromTestCase(TestErrorHandling)) # 运行测试 runner unittest.TextTestRunner(verbosity2) result runner.run(suite) # 收集结果到报告器 for test_case in suite: # 这里简化处理实际需要更精细的结果收集 reporter.add_test_result( test_namestr(test_case), statuspassed # 简化处理实际需要从result对象获取 ) reporter.end_test_run() return result.wasSuccessful()5.2 集成到持续集成流程最后我们可以把测试脚本集成到CI/CD流程中比如GitHub Actions。# .github/workflows/banana-vision-tests.yml name: Banana Vision Studio Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] schedule: # 每天凌晨2点运行一次 - cron: 0 2 * * * jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install pytest coverage - name: Run tests env: BANANA_VISION_API_KEY: ${{ secrets.BANANA_VISION_API_KEY }} BANANA_VISION_BASE_URL: ${{ secrets.BANANA_VISION_BASE_URL }} run: | python run_tests.py - name: Upload test reports if: always() uses: actions/upload-artifactv3 with: name: test-reports path: reports/ - name: Send notification on failure if: failure() uses: actions/github-scriptv6 with: script: | github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: Banana Vision Studio 测试失败, body: 自动化测试运行失败请检查最新提交。\n\n查看详细报告${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}, labels: [bug, tests] })6. 总结整套自动化测试体系用下来最大的感受就是心里有底了。以前每次更新都提心吊胆现在只要跑一遍测试脚本几分钟就知道这次更新有没有问题。这套测试方案覆盖了从基础功能到高级特性的各个方面既有API连接测试确保服务可用也有生成质量测试保证输出效果还有性能测试关注响应速度以及异常处理测试提高系统健壮性。实际部署时建议先从核心功能开始比如爆炸图生成和标注准确性这些是Banana Vision Studio最核心的功能。然后再逐步补充其他测试用例。测试数据也很重要要准备一些典型的工业产品图片覆盖简单到复杂的各种情况。最后生成的测试报告不仅给开发团队看也可以给产品经理和客户看让他们直观地了解系统的稳定性和可靠性。特别是HTML格式的报告视觉效果很好适合在项目会议上展示。如果你也在用Banana Vision Studio做工业拆解图强烈建议花点时间搭建这样一套自动化测试体系。前期投入一些时间后期能省下大量的手动测试和问题排查时间绝对是值得的。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。