Anlr and StringTemplate instances: automatically generate unit test classes,
Anlr and StringTemplate instances: automatically generated unit test Class 1. anlr syntax
To automatically generate a unit test, the first step is to analyze the tested class. Here we use Java code as an example to analyze Java code with anlr. It is hard to imagine the complexity of writing anlr syntax files in a language by hand. It is very considerate to list the syntax files in many common languages on anlr's GitHub website, such as Java, SQL Syntax of Sqlite and MySQL.
With the. g4 syntax file, you can automatically generate the parser code by following the steps in the anlr v4 getting started tutorial and instance.
2. StringTemplate Basics
StringTemplate (ST for short) is also a very useful tool provided by anlr. Its functions are similar to template engines such as Velocity and FreeMaker. You can customize templates to render different webpages, emails, and codes based on different values. But we can see from the String in the name that it is relatively lightweight. This is true after a trial. It supports hard-coded simple templates in Java.
However, I still encountered a lot of problems during the trial. I always felt that its template syntax was somewhat complicated! In addition, the latest ST4 API and previous versions have greatly changed, and many examples found on the Internet are not easy to use. For details, refer to the tutorials on the official website and the CheatSheet table. Of course, it takes a lot of time to debug the sample code in this article!
3. Unit Test Generator
First, let's look at the Main method. The input text is a Java class represented by the code variable, which has two methods. Then, use the automatically generated anlr code to construct the processing chain of the semantic analyzer and parser, and pass inUnitTestGenerator
The listener traverses the input text.
public class JavaCodeParseTest { public static void main(String[] args) { String code = "package com.jcache.store;" + "public class CacheStore {" + "Object getCache(int a) {" + "if (a == 1)" + "return 1;" + "else " + "return 2;" + "}" + "void setCache(int a) {" + "return;" + "}" + "}"; // 1.Lexical analysis JavaLexer lexer = new JavaLexer(new ANTLRInputStream(code)); CommonTokenStream tokens = new CommonTokenStream(lexer); // 2.Syntax analysis JavaParser parser = new JavaParser(tokens); ParseTree tree = parser.compilationUnit(); // 3.Application based on Syntax Tree ParseTreeWalker walker = new ParseTreeWalker(); walker.walk(new UnitTestGenerator(), tree); }}
Here is a core codeUnitTestGenerator
.
Here are some key points in the Code:
- ST template group: it is simple to define a template in a template file. If you want to define a template in Java, you must refer to the static initialization block method in this example.
- Nested sub-templates and Multi-valued: to automatically generate a method for each method name based on the list of method names, follow
/** * Simple unit test generator. */public class UnitTestGenerator extends JavaBaseListener { /** Constants: template name, placeholder name, generated code name */ private static final String CLASS_ST_NAME = "classST"; private static final String METHOD_ST_NAME = "methodST"; private static final String TEST_PKG_NAME = "testPkgName"; private static final String TEST_CLASS_NAME = "testClassName"; private static final String TEST_METHOD_NAME = "testMethodName"; private static final String CLASS_NAME_SUFFIX = "Test"; private static final String METHOD_NAME_PREFIX = "test"; /** Template for Java */ private static final String METHOD_ST = t("@Test") + t("public void " + $(TEST_METHOD_NAME) + "() throws Exception {") + tt("//body...") + t("}") + n(""); private static final String CLASS_ST = n("package " + $(TEST_PKG_NAME) + ";") + n("") + n("import org.junit.*;") + n("") + n("public class " + $(TEST_CLASS_NAME) + " {") + n("") + /** * Apply nested template 'methodST' to multi-valued attributes 'testMethodName'. * NOTE: <attribute:template(argument-list)> * Apply template to attribute with optional argument-list. * Example: <name:bold()> applies bold() to name's value. * The first argument of the template gets the iterated value. */ n($(TEST_METHOD_NAME + ":" + METHOD_ST_NAME +"();separator=\"\n\"")) + n("}"); /** ST group to nest template */ private static STGroup group; static { group = new STGroup('$', '$'); CompiledST classST = group.defineTemplate(CLASS_ST_NAME, CLASS_ST); classST.addArg(new FormalArgument(TEST_PKG_NAME)); classST.addArg(new FormalArgument(TEST_CLASS_NAME)); classST.addArg(new FormalArgument(TEST_METHOD_NAME)); CompiledST methodST = group.defineTemplate(METHOD_ST_NAME, METHOD_ST); methodST.addArg(new FormalArgument(TEST_METHOD_NAME)); } /** Attributes. NOTE: group.getInstanceOf() return new ST */ private Map<String, Object> attributeMap = new HashMap<>(); @Override public void enterPackageDeclaration(@NotNull JavaParser.PackageDeclarationContext ctx) { attributeMap.put(TEST_PKG_NAME, ctx.qualifiedName().getText()); } @Override public void enterClassDeclaration(@NotNull ClassDeclarationContext ctx) { String orgClassName = ctx.Identifier().getText(); String testClassName = orgClassName + CLASS_NAME_SUFFIX; attributeMap.put(TEST_CLASS_NAME, testClassName); } @Override public void enterMethodDeclaration(@NotNull MethodDeclarationContext ctx) { String orgMethodName = ctx.Identifier().getText(); String testMethodName = METHOD_NAME_PREFIX + orgMethodName.substring(0, 1).toUpperCase() + orgMethodName.substring(1); // Multi-valued attribute List<String> methodNames = (List<String>) attributeMap.get(TEST_METHOD_NAME); if (methodNames == null) { methodNames = new ArrayList<>(); attributeMap.put(TEST_METHOD_NAME, methodNames); } methodNames.add(testMethodName); } @Override public void enterStatement(@NotNull StatementContext ctx) { // If/else/switch, for/while, try-catch switch (ctx.getStart().getText()) { case "if": break; case "for": break; case "try": break; default: break; } } @Override public void exitCompilationUnit(@NotNull JavaParser.CompilationUnitContext ctx) { ST template = group.getInstanceOf(CLASS_ST_NAME); attributeMap.entrySet().forEach(e -> template.add(e.getKey(), e.getValue())); System.out.println(template.render()); // Cleanup attributeMap.clear(); } // ======================================= // String Utility // ======================================= private static String $(String attrName) { return "$" + attrName + "$"; } private static String n(String str) { return str + "\n"; } private static String t(String str) { return "\t" + n(str); } private static String tt(String str) { return "\t\t" + n(str); }}