| | import re |
| | import ast |
| | from typing import Dict, Any, List, Optional, Set, Tuple |
| |
|
| | from validators.base import BaseValidator, Validator |
| |
|
| | class TestGenerator(BaseValidator, Validator): |
| | """テストケース生成を行うクラス""" |
| | |
| | def __init__(self, client): |
| | """テスト生成クライアントを初期化""" |
| | super().__init__(client) |
| | |
| | self.test_model = "qwen-2.5-coder-32b" |
| | self.generated_tests = None |
| | |
| | async def validate(self, code, context=None): |
| | """テスト生成を実行する(インターフェース実装)""" |
| | self.generated_tests = await self.generate_comprehensive_tests(code, context) |
| | return self.generated_tests |
| | |
| | def get_result_summary(self): |
| | """テスト生成結果の要約を返す(インターフェース実装)""" |
| | if self.generated_tests is None: |
| | return "テストはまだ生成されていません。" |
| | |
| | |
| | lines = self.generated_tests.split("\n") |
| | test_count = sum(1 for line in lines if line.strip().startswith("def test_")) |
| | return f"{test_count}個のテストケースが生成されました。" |
| | |
| | async def generate_comprehensive_tests(self, code: str, requirements: str = None, language: str = "python") -> str: |
| | """要件とコードから包括的なテストを生成する""" |
| | |
| | test_analysis = self._analyze_code_for_testing(code, language) |
| | |
| | |
| | prompt = self._build_test_generation_prompt(code, test_analysis, requirements, language) |
| | |
| | |
| | messages = [ |
| | { |
| | "role": "system", |
| | "content": prompt |
| | } |
| | ] |
| | |
| | response = await self._make_async_api_call( |
| | self.test_model, |
| | messages, |
| | temperature=0.3, |
| | max_tokens=2500 |
| | ) |
| | |
| | |
| | test_code = response.choices[0].message.content |
| | |
| | return test_code |
| | |
| | def _analyze_code_for_testing(self, code: str, language: str) -> Dict[str, Any]: |
| | """テスト生成のためのコード解析を行う""" |
| | result = { |
| | "functions": [], |
| | "classes": [], |
| | "edge_cases": [], |
| | "identified_patterns": [], |
| | "complexity_analysis": {} |
| | } |
| | |
| | if language == "python": |
| | self._analyze_python_code(code, result) |
| | elif language == "javascript": |
| | self._analyze_javascript_code(code, result) |
| | elif language == "java": |
| | self._analyze_java_code(code, result) |
| | elif language == "cpp" or language == "c++": |
| | self._analyze_cpp_code(code, result) |
| | |
| | |
| | self._identify_edge_cases_and_patterns(code, result, language) |
| | |
| | return result |
| | |
| | def _analyze_python_code(self, code: str, result: Dict[str, Any]) -> None: |
| | """Pythonコードの解析を行う""" |
| | try: |
| | tree = ast.parse(code) |
| | |
| | |
| | for node in ast.walk(tree): |
| | if isinstance(node, ast.FunctionDef): |
| | function_info = { |
| | "name": node.name, |
| | "args": [], |
| | "returns": None, |
| | "docstring": ast.get_docstring(node), |
| | "is_method": False, |
| | "decorators": [d.id if isinstance(d, ast.Name) else "" for d in node.decorator_list] |
| | } |
| | |
| | |
| | for arg in node.args.args: |
| | arg_info = {"name": arg.arg, "type": None} |
| | if arg.annotation: |
| | if isinstance(arg.annotation, ast.Name): |
| | arg_info["type"] = arg.annotation.id |
| | elif isinstance(arg.annotation, ast.Subscript): |
| | if isinstance(arg.annotation.value, ast.Name): |
| | arg_info["type"] = f"{arg.annotation.value.id}[...]" |
| | function_info["args"].append(arg_info) |
| | |
| | |
| | if node.returns: |
| | if isinstance(node.returns, ast.Name): |
| | function_info["returns"] = node.returns.id |
| | elif isinstance(node.returns, ast.Subscript): |
| | if isinstance(node.returns.value, ast.Name): |
| | function_info["returns"] = f"{node.returns.value.id}[...]" |
| | |
| | result["functions"].append(function_info) |
| | |
| | |
| | for node in ast.walk(tree): |
| | if isinstance(node, ast.ClassDef): |
| | class_info = { |
| | "name": node.name, |
| | "methods": [], |
| | "attributes": [], |
| | "docstring": ast.get_docstring(node) |
| | } |
| | |
| | |
| | for item in node.body: |
| | if isinstance(item, ast.FunctionDef): |
| | method_info = { |
| | "name": item.name, |
| | "args": [], |
| | "returns": None, |
| | "docstring": ast.get_docstring(item), |
| | "is_method": True, |
| | "decorators": [d.id if isinstance(d, ast.Name) else "" for d in item.decorator_list] |
| | } |
| | |
| | |
| | for arg in item.args.args: |
| | if arg.arg != "self" and arg.arg != "cls": |
| | arg_info = {"name": arg.arg, "type": None} |
| | if arg.annotation: |
| | if isinstance(arg.annotation, ast.Name): |
| | arg_info["type"] = arg.annotation.id |
| | elif isinstance(arg.annotation, ast.Subscript): |
| | if isinstance(arg.annotation.value, ast.Name): |
| | arg_info["type"] = f"{arg.annotation.value.id}[...]" |
| | method_info["args"].append(arg_info) |
| | |
| | |
| | if item.returns: |
| | if isinstance(item.returns, ast.Name): |
| | method_info["returns"] = item.returns.id |
| | elif isinstance(item.returns, ast.Subscript): |
| | if isinstance(item.returns.value, ast.Name): |
| | method_info["returns"] = f"{item.returns.value.id}[...]" |
| | |
| | class_info["methods"].append(method_info) |
| | |
| | elif isinstance(item, ast.Assign): |
| | |
| | for target in item.targets: |
| | if isinstance(target, ast.Name): |
| | class_info["attributes"].append(target.id) |
| | |
| | result["classes"].append(class_info) |
| | |
| | |
| | result["complexity_analysis"] = self._analyze_code_complexity(tree) |
| | |
| | except SyntaxError as e: |
| | print(f"[Error] Python syntax error during code analysis: {str(e)}") |
| | except Exception as e: |
| | print(f"[Error] Failed to analyze Python code: {str(e)}") |
| | |
| | def _analyze_javascript_code(self, code: str, result: Dict[str, Any]) -> None: |
| | """JavaScriptコードの解析を行う(正規表現ベースの簡易解析)""" |
| | |
| | function_patterns = [ |
| | r'function\s+(\w+)\s*\((.*?)\)\s*{', |
| | r'const\s+(\w+)\s*=\s*function\s*\((.*?)\)\s*{', |
| | r'const\s+(\w+)\s*=\s*\((.*?)\)\s*=>\s*{', |
| | r'const\s+(\w+)\s*=\s*\((.*?)\)\s*=>' |
| | ] |
| | |
| | for pattern in function_patterns: |
| | for match in re.finditer(pattern, code): |
| | function_name = match.group(1) |
| | args_str = match.group(2).strip() |
| | args = [arg.strip() for arg in args_str.split(',') if arg.strip()] |
| | |
| | function_info = { |
| | "name": function_name, |
| | "args": [{"name": arg.split('=')[0].strip(), "type": None} for arg in args], |
| | "returns": None, |
| | "docstring": None, |
| | "is_method": False, |
| | "decorators": [] |
| | } |
| | |
| | |
| | docstring_pattern = r'/\*\*\s*([\s\S]*?)\s*\*/' |
| | doc_search_area = code[:match.start()] |
| | last_doc = None |
| | for doc_match in re.finditer(docstring_pattern, doc_search_area): |
| | last_doc = doc_match.group(1) |
| | |
| | if last_doc: |
| | function_info["docstring"] = last_doc |
| | |
| | result["functions"].append(function_info) |
| | |
| | |
| | class_patterns = [ |
| | r'class\s+(\w+)(?:\s+extends\s+(\w+))?\s*{', |
| | ] |
| | |
| | for pattern in class_patterns: |
| | for match in re.finditer(pattern, code): |
| | class_name = match.group(1) |
| | parent_class = match.group(2) if len(match.groups()) > 1 else None |
| | |
| | class_start = match.end() |
| | |
| | brace_count = 1 |
| | class_end = class_start |
| | for i in range(class_start, len(code)): |
| | if code[i] == '{': |
| | brace_count += 1 |
| | elif code[i] == '}': |
| | brace_count -= 1 |
| | if brace_count == 0: |
| | class_end = i + 1 |
| | break |
| | |
| | class_body = code[class_start:class_end] |
| | |
| | |
| | method_pattern = r'(?:async\s+)?(?:static\s+)?(\w+)\s*\((.*?)\)\s*{' |
| | methods = [] |
| | |
| | for method_match in re.finditer(method_pattern, class_body): |
| | method_name = method_match.group(1) |
| | if method_name not in ['constructor', 'get', 'set']: |
| | method_args_str = method_match.group(2).strip() |
| | method_args = [arg.strip() for arg in method_args_str.split(',') if arg.strip()] |
| | |
| | method_info = { |
| | "name": method_name, |
| | "args": [{"name": arg.split('=')[0].strip(), "type": None} for arg in method_args], |
| | "returns": None, |
| | "docstring": None, |
| | "is_method": True, |
| | "decorators": [] |
| | } |
| | |
| | methods.append(method_info) |
| | |
| | |
| | docstring_pattern = r'/\*\*\s*([\s\S]*?)\s*\*/' |
| | doc_search_area = code[:match.start()] |
| | last_doc = None |
| | for doc_match in re.finditer(docstring_pattern, doc_search_area): |
| | last_doc = doc_match.group(1) |
| | |
| | class_info = { |
| | "name": class_name, |
| | "methods": methods, |
| | "attributes": [], |
| | "docstring": last_doc |
| | } |
| | |
| | result["classes"].append(class_info) |
| | |
| | def _analyze_java_code(self, code: str, result: Dict[str, Any]) -> None: |
| | """Javaコードの解析を行う(正規表現ベースの簡易解析)""" |
| | |
| | method_pattern = r'(?:public|protected|private|static|\s) +(?:[\w<>\[\]]+\s+)(\w+) *\([^\)]*\) *(?:\{|[^;])' |
| | for match in re.finditer(method_pattern, code): |
| | method_name = match.group(1) |
| | |
| | |
| | start = match.start() |
| | end = match.end() |
| | param_area = code[start:end] |
| | param_match = re.search(r'\((.*?)\)', param_area) |
| | params = [] |
| | |
| | if param_match: |
| | param_str = param_match.group(1).strip() |
| | if param_str: |
| | param_items = param_str.split(',') |
| | for param in param_items: |
| | param = param.strip() |
| | if param: |
| | parts = param.split() |
| | if len(parts) >= 2: |
| | param_type = parts[0] |
| | param_name = parts[1] |
| | params.append({"name": param_name, "type": param_type}) |
| | |
| | |
| | return_match = re.search(r'(?:public|protected|private|static|\s) +([\w<>\[\]]+)\s+' + re.escape(method_name), param_area) |
| | return_type = return_match.group(1) if return_match else "void" |
| | |
| | |
| | if "class" in code[:start]: |
| | continue |
| | |
| | function_info = { |
| | "name": method_name, |
| | "args": params, |
| | "returns": return_type, |
| | "docstring": None, |
| | "is_method": False, |
| | "decorators": [] |
| | } |
| | |
| | result["functions"].append(function_info) |
| | |
| | |
| | class_pattern = r'(public|private|protected)?\s*class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?\s*\{' |
| | for match in re.finditer(class_pattern, code): |
| | class_name = match.group(2) |
| | |
| | |
| | start = match.end() |
| | brace_count = 1 |
| | end = start |
| | |
| | for i in range(start, len(code)): |
| | if code[i] == '{': |
| | brace_count += 1 |
| | elif code[i] == '}': |
| | brace_count -= 1 |
| | if brace_count == 0: |
| | end = i |
| | break |
| | |
| | class_body = code[start:end] |
| | |
| | |
| | class_methods = [] |
| | for method_match in re.finditer(method_pattern, class_body): |
| | method_name = method_match.group(1) |
| | |
| | |
| | m_start = method_match.start() |
| | m_end = method_match.end() |
| | param_area = class_body[m_start:m_end] |
| | param_match = re.search(r'\((.*?)\)', param_area) |
| | params = [] |
| | |
| | if param_match: |
| | param_str = param_match.group(1).strip() |
| | if param_str: |
| | param_items = param_str.split(',') |
| | for param in param_items: |
| | param = param.strip() |
| | if param: |
| | parts = param.split() |
| | if len(parts) >= 2: |
| | param_type = parts[0] |
| | param_name = parts[1] |
| | params.append({"name": param_name, "type": param_type}) |
| | |
| | |
| | return_match = re.search(r'(?:public|protected|private|static|\s) +([\w<>\[\]]+)\s+' + re.escape(method_name), param_area) |
| | return_type = return_match.group(1) if return_match else "void" |
| | |
| | method_info = { |
| | "name": method_name, |
| | "args": params, |
| | "returns": return_type, |
| | "docstring": None, |
| | "is_method": True, |
| | "decorators": [] |
| | } |
| | |
| | class_methods.append(method_info) |
| | |
| | |
| | attribute_pattern = r'(?:public|protected|private|static|\s) +(?!class|void)[\w<>\[\]]+\s+(\w+)\s*;' |
| | attributes = [] |
| | |
| | for attr_match in re.finditer(attribute_pattern, class_body): |
| | attr_name = attr_match.group(1) |
| | attributes.append(attr_name) |
| | |
| | class_info = { |
| | "name": class_name, |
| | "methods": class_methods, |
| | "attributes": attributes, |
| | "docstring": None |
| | } |
| | |
| | result["classes"].append(class_info) |
| | |
| | def _analyze_cpp_code(self, code: str, result: Dict[str, Any]) -> None: |
| | """C++コードの解析を行う(正規表現ベースの簡易解析)""" |
| | |
| | function_pattern = r'(?!if|for|while|switch)\b(\w+(?:<[\w\s,]+>)?)\s+(\w+)\s*\((.*?)\)\s*(?:const)?\s*(?:noexcept)?\s*(?:=\s*delete)?\s*(?:=\s*default)?\s*(?:override)?\s*(?:final)?\s*(?:{\|;)' |
| | for match in re.finditer(function_pattern, code): |
| | return_type = match.group(1) |
| | function_name = match.group(2) |
| | params_str = match.group(3).strip() |
| | |
| | |
| | preceding_code = code[:match.start()] |
| | class_definition = re.search(r'class\s+\w+\s*(?::\s*(?:public|protected|private)\s*\w+(?:\s*,\s*(?:public|protected|private)\s*\w+)*)?\s*{', preceding_code) |
| | |
| | if class_definition: |
| | |
| | struct_end = preceding_code.rfind('}') |
| | if struct_end == -1 or struct_end < class_definition.start(): |
| | continue |
| | |
| | |
| | params = [] |
| | if params_str: |
| | param_list = [] |
| | paren_level = 0 |
| | current_param = "" |
| | |
| | for char in params_str: |
| | if char == ',' and paren_level == 0: |
| | param_list.append(current_param.strip()) |
| | current_param = "" |
| | else: |
| | if char == '(': |
| | paren_level += 1 |
| | elif char == ')': |
| | paren_level -= 1 |
| | current_param += char |
| | |
| | if current_param: |
| | param_list.append(current_param.strip()) |
| | |
| | for param in param_list: |
| | param_parts = param.split() |
| | if len(param_parts) >= 2: |
| | param_type = ' '.join(param_parts[:-1]) |
| | param_name = param_parts[-1].replace('&', '').replace('*', '') |
| | params.append({"name": param_name, "type": param_type}) |
| | |
| | function_info = { |
| | "name": function_name, |
| | "args": params, |
| | "returns": return_type, |
| | "docstring": None, |
| | "is_method": False, |
| | "decorators": [] |
| | } |
| | |
| | result["functions"].append(function_info) |
| | |
| | |
| | class_pattern = r'(?:class|struct)\s+(\w+)(?:\s*:\s*(?:public|protected|private)\s*(\w+)(?:\s*,\s*(?:public|protected|private)\s*\w+)*)?\s*\{' |
| | for match in re.finditer(class_pattern, code): |
| | class_name = match.group(1) |
| | |
| | |
| | start = match.end() |
| | brace_count = 1 |
| | end = start |
| | |
| | for i in range(start, len(code)): |
| | if code[i] == '{': |
| | brace_count += 1 |
| | elif code[i] == '}': |
| | brace_count -= 1 |
| | if brace_count == 0: |
| | end = i |
| | break |
| | |
| | class_body = code[start:end] |
| | |
| | |
| | method_pattern = r'(?:public|protected|private|virtual|static|\s)*\s+(\w+(?:<[\w\s,]+>)?)\s+(\w+)\s*\((.*?)\)\s*(?:const)?\s*(?:noexcept)?\s*(?:=\s*delete)?\s*(?:=\s*default)?\s*(?:override)?\s*(?:final)?\s*(?:{\|;)' |
| | class_methods = [] |
| | |
| | for method_match in re.finditer(method_pattern, class_body): |
| | return_type = method_match.group(1) |
| | method_name = method_match.group(2) |
| | |
| | if method_name == class_name or method_name == f"~{class_name}": |
| | continue |
| | |
| | params_str = method_match.group(3).strip() |
| | |
| | |
| | params = [] |
| | if params_str: |
| | param_list = [] |
| | paren_level = 0 |
| | current_param = "" |
| | |
| | for char in params_str: |
| | if char == ',' and paren_level == 0: |
| | param_list.append(current_param.strip()) |
| | current_param = "" |
| | else: |
| | if char == '(': |
| | paren_level += 1 |
| | elif char == ')': |
| | paren_level -= 1 |
| | current_param += char |
| | |
| | if current_param: |
| | param_list.append(current_param.strip()) |
| | |
| | for param in param_list: |
| | param_parts = param.split() |
| | if len(param_parts) >= 2: |
| | param_type = ' '.join(param_parts[:-1]) |
| | param_name = param_parts[-1].replace('&', '').replace('*', '') |
| | params.append({"name": param_name, "type": param_type}) |
| | |
| | method_info = { |
| | "name": method_name, |
| | "args": params, |
| | "returns": return_type, |
| | "docstring": None, |
| | "is_method": True, |
| | "decorators": [] |
| | } |
| | |
| | class_methods.append(method_info) |
| | |
| | |
| | attribute_pattern = r'(?:public|protected|private|static|\s)*\s+(?!class|struct|void|return|if|for|while)[\w<>\[\]]+\s+(\w+)\s*;' |
| | attributes = [] |
| | |
| | for attr_match in re.finditer(attribute_pattern, class_body): |
| | attr_name = attr_match.group(1) |
| | attributes.append(attr_name) |
| | |
| | class_info = { |
| | "name": class_name, |
| | "methods": class_methods, |
| | "attributes": attributes, |
| | "docstring": None |
| | } |
| | |
| | result["classes"].append(class_info) |
| | |
| | def _analyze_code_complexity(self, ast_tree) -> Dict[str, Any]: |
| | """コードの複雑さを分析する""" |
| | complexity = { |
| | "cyclomatic_complexity": 0, |
| | "nested_blocks": 0, |
| | "max_nesting_level": 0, |
| | "line_count": 0 |
| | } |
| | |
| | |
| | current_nesting_level = 0 |
| | max_nesting_level = 0 |
| | |
| | for node in ast.walk(ast_tree): |
| | |
| | if isinstance(node, (ast.If, ast.For, ast.While, ast.Try, ast.With)): |
| | complexity["cyclomatic_complexity"] += 1 |
| | current_nesting_level += 1 |
| | max_nesting_level = max(max_nesting_level, current_nesting_level) |
| | complexity["nested_blocks"] += 1 |
| | |
| | |
| | if hasattr(node, 'body') and isinstance(node.body, list) and node.body: |
| | if isinstance(node, (ast.FunctionDef, ast.ClassDef)): |
| | |
| | current_nesting_level = 0 |
| | |
| | complexity["max_nesting_level"] = max_nesting_level |
| | |
| | return complexity |
| | |
| | def _identify_edge_cases_and_patterns(self, code: str, result: Dict[str, Any], language: str) -> None: |
| | """エッジケースとパターンを特定する""" |
| | |
| | patterns = { |
| | "division": (r'/\s*\w+', "ゼロ除算"), |
| | "array_access": (r'\[\s*\w+\s*\]', "配列の範囲外アクセス"), |
| | "null_check": (r'==\s*null|!=\s*null|==\s*None|!=\s*None', "NullPointerException/TypeError"), |
| | "file_operation": (r'open\s*\(|fopen\s*\(|readFile|writeFile', "ファイル操作エラー"), |
| | "network_request": (r'http|socket|fetch|request', "ネットワークエラー、タイムアウト"), |
| | "database_query": (r'query|sql|select|insert|update|delete|from\s+\w+', "データベースエラー"), |
| | "recursion": (r'def\s+(\w+).*\1\s*\(|function\s+(\w+).*\2\s*\(', "スタックオーバーフロー"), |
| | "float_comparison": (r'==\s*\d+\.\d+|!=\s*\d+\.\d+', "浮動小数点の比較精度問題"), |
| | "memory_allocation": (r'malloc|new\s+\w+|new\s+\[', "メモリ割り当て失敗"), |
| | "concurrent_access": (r'thread|mutex|lock|atomic|synchronized', "競合状態"), |
| | "user_input": (r'input|scanf|readline|gets', "不正な入力形式"), |
| | "physics_simulation": (r'force|acceleration|velocity|time\s*step|dt', "数値安定性の問題、精度の問題") |
| | } |
| | |
| | |
| | physics_patterns = { |
| | "time_integration": (r'dt|time\s*step|delta\s*t', "タイムステップの精度と安定性"), |
| | "boundary_condition": (r'boundary|edge|border', "境界条件の処理"), |
| | "collision_detection": (r'collision|intersect|overlap', "衝突検出の精度と効率"), |
| | "energy_conservation": (r'energy|conservation|保存', "エネルギー保存則の検証"), |
| | "floating_point_precision": (r'float|double|decimal', "浮動小数点の精度問題"), |
| | "vector_operations": (r'vector|cross|dot|normalize', "ベクトル演算の精度と効率"), |
| | "matrix_operations": (r'matrix|determinant|inverse', "行列演算の精度と安定性"), |
| | "numerical_stability": (r'stable|unstable|cfl|courant', "数値安定性の問題") |
| | } |
| | |
| | |
| | language_specific_patterns = { |
| | "python": { |
| | "list_comprehension": (r'\[\s*\w+\s+for\s+\w+\s+in', "大きなリストの処理効率"), |
| | "generator": (r'yield\s+\w+', "ジェネレータの再開可能性"), |
| | "context_manager": (r'with\s+\w+', "コンテキストマネージャのエラー処理") |
| | }, |
| | "javascript": { |
| | "async_await": (r'async|await', "非同期処理の例外処理"), |
| | "promise": (r'Promise|then|catch', "未処理のPromise拒否"), |
| | "event_listener": (r'addEventListener|on\w+\s*=', "イベントリスナの漏れ") |
| | }, |
| | "java": { |
| | "exception_handling": (r'try|catch|throws', "例外の伝播と処理"), |
| | "generics": (r'<\w+>', "型安全性と型消去"), |
| | "multi_threading": (r'Thread|Runnable|Callable', "スレッドセーフティ") |
| | }, |
| | "cpp": { |
| | "memory_management": (r'delete|free', "メモリリーク、二重解放"), |
| | "move_semantics": (r'std::move', "ムーブ後のオブジェクト状態"), |
| | "templates": (r'template\s*<', "テンプレート特殊化の選択") |
| | } |
| | } |
| | |
| | |
| | for pattern_name, (regex, edge_case) in patterns.items(): |
| | if re.search(regex, code, re.IGNORECASE): |
| | result["identified_patterns"].append(pattern_name) |
| | result["edge_cases"].append(edge_case) |
| | |
| | |
| | for pattern_name, (regex, edge_case) in physics_patterns.items(): |
| | if re.search(regex, code, re.IGNORECASE): |
| | result["identified_patterns"].append(f"physics_{pattern_name}") |
| | result["edge_cases"].append(edge_case) |
| | |
| | |
| | if language in language_specific_patterns: |
| | for pattern_name, (regex, edge_case) in language_specific_patterns[language].items(): |
| | if re.search(regex, code, re.IGNORECASE): |
| | result["identified_patterns"].append(f"{language}_{pattern_name}") |
| | result["edge_cases"].append(edge_case) |
| | |
| | def _build_test_generation_prompt(self, code: str, test_analysis: Dict[str, Any], requirements: str = None, language: str = "python") -> str: |
| | """テスト生成用のプロンプトを構築する""" |
| | functions_str = "" |
| | for func in test_analysis["functions"]: |
| | args_str = ", ".join([f"{arg['name']}: {arg['type'] or 'Any'}" for arg in func["args"]]) |
| | returns_str = f" -> {func['returns']}" if func["returns"] else "" |
| | |
| | functions_str += f"- {func['name']}({args_str}){returns_str}\n" |
| | if func["docstring"]: |
| | functions_str += f" Docstring: {func['docstring']}\n" |
| | |
| | classes_str = "" |
| | for cls in test_analysis["classes"]: |
| | classes_str += f"- Class: {cls['name']}\n" |
| | if cls["docstring"]: |
| | classes_str += f" Docstring: {cls['docstring']}\n" |
| | |
| | if cls["attributes"]: |
| | classes_str += f" Attributes: {', '.join(cls['attributes'])}\n" |
| | |
| | if cls["methods"]: |
| | classes_str += " Methods:\n" |
| | for method in cls["methods"]: |
| | args_str = ", ".join([f"{arg['name']}: {arg['type'] or 'Any'}" for arg in method["args"]]) |
| | returns_str = f" -> {method['returns']}" if method['returns'] else "" |
| | |
| | classes_str += f" - {method['name']}({args_str}){returns_str}\n" |
| | if method["docstring"]: |
| | classes_str += f" Docstring: {method['docstring']}\n" |
| | |
| | edge_cases_str = "\n".join([f"- {case}" for case in test_analysis["edge_cases"]]) |
| | |
| | patterns_str = "\n".join([f"- {pattern}" for pattern in test_analysis["identified_patterns"]]) |
| | |
| | |
| | prompt = f"""あなたは高品質なテストコードの専門家です。以下のコードに対して包括的なテストを生成してください。 |
| | |
| | ## コードの概要 |
| | ```{language} |
| | {code} |
| | ``` |
| | |
| | ## コード解析結果 |
| | 関数: |
| | {functions_str or "なし"} |
| | |
| | クラス: |
| | {classes_str or "なし"} |
| | |
| | 考慮すべきエッジケース: |
| | {edge_cases_str or "特になし"} |
| | |
| | 識別されたパターン: |
| | {patterns_str or "特になし"} |
| | |
| | """ |
| |
|
| | |
| | if requirements: |
| | prompt += f""" |
| | ## 機能要件 |
| | {requirements} |
| | """ |
| |
|
| | prompt += f""" |
| | ## テスト要件 |
| | 1. 全ての公開関数とメソッドのテストを作成してください |
| | 2. 基本的な機能テストだけでなく、境界値と異常系のテストも含めてください |
| | 3. モック/スタブを適切に使用して外部依存関係を排除してください |
| | 4. テストの可読性と保守性を確保してください |
| | 5. テストカバレッジを最大化してください |
| | 6. 設計によって特定されたエッジケースを確実に検証してください |
| | 7. 物理シミュレーションの場合は、数値的安定性と物理法則の保存をテストすること |
| | |
| | ## 必須テストケース |
| | """ |
| |
|
| | |
| | for edge_case in test_analysis["edge_cases"]: |
| | prompt += f"- {edge_case}に対するテスト\n" |
| | |
| | |
| | if any("physics_" in pattern for pattern in test_analysis["identified_patterns"]): |
| | prompt += """ |
| | ## 物理シミュレーション向け特別テスト |
| | 1. 保存則(エネルギー、運動量など)の検証テスト |
| | 2. 極端な入力値での安定性テスト |
| | 3. 長時間シミュレーションでの安定性テスト |
| | 4. 既知の解析解との比較テスト |
| | 5. 数値精度の検証テスト |
| | """ |
| |
|
| | |
| | test_frameworks = { |
| | "python": "unittest または pytest", |
| | "javascript": "Jest または Mocha", |
| | "java": "JUnit", |
| | "cpp": "Google Test または Catch2" |
| | } |
| | |
| | framework = test_frameworks.get(language, "適切なテストフレームワーク") |
| | |
| | prompt += f""" |
| | ## 出力形式 |
| | {framework}を使用したテストコードを生成してください。テストコードには以下を含めてください: |
| | 1. 適切なセットアップとティアダウン |
| | 2. 明確なテスト名と説明(テストが何をテストしているかを示す) |
| | 3. 予想される結果とその理由の説明 |
| | 4. エッジケースのテスト |
| | 5. 異常系のテスト(不正な入力、例外処理など) |
| | |
| | テストコードは実行可能で、元のコードに対して直接実行できるようにしてください。 |
| | """ |
| |
|
| | return prompt |