在本地 idea 中自动生成测试用例
在上篇文章中,我们已经生成了刷题列表。如下图所示:
详见:https://www.nowcoder.com/discuss/1009527
下面,我们将牛客题目中自带的测试用例,进行自动生成。方便更愉快的刷题、以及补充新的测试用例。
如果题目中自带多个示例,那么我们也就同时生成多个测试用例。
准备工作
为了生成测试用例,我们需要将牛客网题库自带的题目示例和方法体拷贝到前面生成的Java 类和 markdown 中。
具体做法如下:
(1)拷贝方法体到 Java 类中。
如下所示:
(3)将题目示例拷贝到 markdown 中,这里推荐使用 Typora 这个 markdown 编辑器,非常好用。
下面,我们要做的工作就是,解析 markdown 中示例的输入和返回值。然后自动生成测试用例。
生成测试参数
解析方法体
通过解析Java类中的方法体,确定好方法名称、参数类型、参数名称、返回值等信息。
以便我们后续调用。
public static ProblemInfo getJavaInfo(QuestionInfo questionInfo) { try { File file = new File(CreateJavaFileUtil.getJavaPath(questionInfo)); BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream(file))); String linestr; while ((linestr = br.readLine()) != null) { if (!linestr.contains("class") && linestr.contains("public")) { Matcher matcher = pattern.matcher(linestr); if (matcher.find()) { ProblemInfo info = new ProblemInfo(); info.setQuestionInfo(questionInfo); info.setProblemName(questionInfo.getQuestionTitle()); info.setClassName(questionInfo.getJavaName()); info.setReturnType(matcher.group(1).trim()); info.setMethodName(matcher.group(2).trim()); String[] strings = matcher.group(3).split(","); for (int i = 0; i < strings.length; i++) { info.addParam(strings[i].trim()); } return info; } } } br.close();//关闭IO } catch (Exception e) { e.printStackTrace(); } System.out.println("请检查" + questionInfo.getJavaName() + "类中是否有初始化方法!!"); return null; }
解析 markdown
通过解析Markdown ,解析好测试用例的相关参数,存储在上面的对象中。
public static void getMarkDown(ProblemInfo info) { try { String filePath = CreateJavaFileUtil.getMarkPath(info.getQuestionInfo()); File file = new File(filePath); BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream(file))); String linestr, key = "", value = "";//按行读取 将每次读取一行的结果赋值给linestr boolean inStart = false; boolean inEnd = false; while ((linestr = br.readLine()) != null) { if (linestr.contains("```") || linestr.contains("说明") || linestr.contains("解析") || linestr.contains("示例")) { inEnd = false; } if (!inEnd && value != "") { if (key.endsWith(",")) { key = key.substring(0, key.length() - 1); } info.addTest(key.replaceAll("`", ""), value); key = ""; value = ""; } if (linestr.contains("返回值:")) { br.readLine(); br.readLine(); linestr = br.readLine(); inStart = false; inEnd = true; value += linestr.trim(); } if (linestr.contains("输入:") || inStart) { linestr = br.readLine(); key += linestr.trim(); inStart = true; } } br.close();//关闭IO } catch (Exception e) { e.printStackTrace(); } }
生成测试类
下面就可以进行组装,生成测试类了。
public static void createTestFile(ProblemInfo info) { try { File file = new File(getTestPath(info.getQuestionInfo())); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } if (file.exists()) { System.out.println(info.getClassName() + "Test.java is exists."); return; } FileOutputStream out = new FileOutputStream(file, true); StringBuffer sb = new StringBuffer(); sb.append("package com.jueee.nowcoder." + CreateJavaFileUtil.CODE_TYPE + ";\n\n"); sb.append("import org.junit.jupiter.api.Test;\n\n"); for (String imports : info.getImportList()) { sb.append(imports + "\n"); } sb.append("import static org.junit.jupiter.api.Assertions.*;\n\n"); sb.append(CreateJavaFileUtil.getClassAnnotation(info.getQuestionInfo())); sb.append("public class " + info.getClassName() + "Test {\n\n"); sb.append("\t" + info.getClassName() + " solution = new " + info.getClassName() + "();\n\n"); int num = 1; for (String test : info.getTestMap().keySet()) { sb.append("\t@Test\n"); sb.append("\tpublic void test" + num++ + "() {\n"); for (String paramInfo : info.getTestParamInfo(test)) { sb.append("\t\t" + paramInfo + ";\n"); } for (String assertInfo : info.getAssertInfo(test)) { sb.append("\t\t" + assertInfo + "\n"); } sb.append("\t}\n"); } sb.append("\n\n}"); out.write(sb.toString().getBytes("utf-8")); out.close(); } catch (Exception e) { e.printStackTrace(); } }
生成断言
针对不同的返回类型,生成不同的断言。
public List<String> getAssertInfo(String in) { String out = getTestMap().get(in); List<String> list = new ArrayList<>(); if (Arrays.asList("void").contains(getReturnType())) { list.add("solution." + getMethodName() + "(" + getParamName() + ");"); String type = getParamType().get(0); list.add(getAssertType(type) + "(" + getParamList().get(0).replace(type, "").trim() + ", " + getObject(type, out) + ");"); } else if (Arrays.asList("ListNode").contains(getReturnType())) { list.add("ListNode actual = " + getObject(getReturnType(), out) + ";"); list.add("ListNodeUtil.print(actual);"); list.add("ListNode expected = solution." + getMethodName() + "(" + getParamName() + ");"); list.add("ListNodeUtil.print(expected);"); list.add("assertTrue(ListNodeUtil.isSameListNode(expected, actual));"); } else if (Arrays.asList("TreeNode").contains(getReturnType())) { list.add("TreeNode actual = " + getObject(getReturnType(), out) + ";"); list.add("TreeNodeShow.show(actual);"); list.add("TreeNode expected = solution." + getMethodName() + "(" + getParamName() + ");"); list.add("TreeNodeShow.show(expected);"); list.add("assertTrue(TreeNodeUtil.isSameTree(expected, actual));"); } else if (getReturnType().contains("List<TreeNode>")) { list.add("List<TreeNode> expected = solution." + getMethodName() + "(" + getParamName() + ");"); list.add("List<TreeNode> actual = " + getObject(getReturnType(), out) + ";"); list.add("AssertArrayPlus.assertListTreeNode(expected, actual);"); } else if (Arrays.asList("TreeLinkNode").contains(getReturnType())) { list.add("TreeLinkNode actual = " + getObject(getReturnType(), out) + ";"); list.add("TreeLinkNodeShow.show(actual);"); list.add("TreeLinkNode expected = solution." + getMethodName() + "(" + getParamName() + ");"); list.add("TreeLinkNodeShow.show(expected);"); list.add("assertTrue(TreeLinkNodeUtil.isSameTree(expected, actual));"); } else if (Arrays.asList("ArrayList<ArrayList<Integer>>").contains(getReturnType())) { list.add("ArrayList<ArrayList<Integer>> actual = new ArrayList<>();"); String[] infos = out.split("\\],\\["); for (int i = 0; i < infos.length; i++) { list.add("ArrayList<Integer> list"+i+" = new ArrayList<>();"); String tt = infos[i].replaceAll("\\[","").replaceAll("\\]",""); if (StringUtils.isNotBlank(tt)) { String[] nums = tt.split(","); for (String num : nums) { list.add("list" + i + ".add(" + num + ");"); } } list.add("actual.add(list"+i+");"); } list.add("assertEquals(solution." + getMethodName() + "(" + getParamName() + ").toString(), actual.toString());"); } else if (getReturnType().contains("List<")) { list.add(getAssertCommon("solution." + getMethodName() + "(" + getParamName() + ")", getObject(getReturnType(), out) + "")); } else if (getReturnType().contains("boolean")) { list.add(getAssertDefault(getObject(getReturnType(), out).toLowerCase())); } else { list.add(getAssertDefault(getObject(getReturnType(), out))); } return list; }
效果展示
最终,我们即可得到如下的测试用例:
对于链表,可以生成如下的测试用例:
https://www.nowcoder.com/practice/f9f78ca89ad643c99701a7142bd59f5d
对于复杂的二叉树,我们也可以生成复杂的测试用例:
https://www.nowcoder.com/practice/a9d0ecbacef9410ca97463e4a5c83be7
目前不足
(一)对于自定义类,由于情况比较复杂,所以生成的测试用例比较难判断,再次不进行考虑。好在这种情况也并不多。
(2)对于多个参数的情况,需要在markdown中手动补充下参数名称,方便分隔判断。
如 https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8 这个例子。
需要补充改造如下所示:
那么即可生成测试用例如下所示: