CHAPTER 6
Creating scripts with Groovy is both easy and beneficial, but don't limit your use of this versatile language to just scripts! The best way to build larger applications with Groovy (and Java) is the Gradle build tool. Gradle will help you manage your project's dependencies, compilation, testing, and deployment. In this chapter, we'll walk through a small Gradle project and get a feel for how it works.
You’ll find the codebase described in this chapter in the gradle subdirectory of the sample code. We won't go through Gradle installation because the sample code for this chapter will sort that out for you. However, eventually you'll most likely want to install Gradle, so head over to the comprehensive documentation and you'll find the installation instructions.
Note: In the code listing for this chapter, you'll find some syntax not discussed in this e-book. I won't describe all of the syntax here, but it should be reasonably self-evident, and I'll recommend some resources for you to check out in the next chapter.
Take a look at Code Listing 82: The build.gradle File. What does it look like? That's right—Gradle uses Groovy to define the build file. This makes it quite readable (much nicer than XML) and also lets you use Groovy code to extend and customize your more complex builds.
Code Listing 82: The build.gradle File
apply plugin: 'groovy' |
Code Listing 82 performs the following sequence:
Take a look at the files for the project and you'll see the structure illustrated in Figure 3: Gradle Project Layout:
The src directory contains the project's source code and test code. Using the standard configuration, Groovy files are located in src/main/groovy and the Spock tests are located in src/test/groovy. I've set up my source code into a demo package, hence you see src/main/groovy/demo. I've also got some resource files for my testing, so I’ve placed them into src/test/resources.
In order to build the project, just run ./gradlew build (Unix) or gradlew.bat build (Windows). This may take a little while on your first run because Gradle will fetch a variety of dependencies.
├── README.md ├── build.gradle ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main │ └── groovy │ └── demo │ ├── App.groovy │ └── DataSummary.groovy └── test ├── groovy │ └── demo │ ├── AppTest.groovy │ └── DataSummaryTest.groovy └── resources ├── sample-data-1.txt ├── sample-data-2.txt ├── sample-data-3.txt ├── sample-data-4.txt └── sample-data-5.txt |
Figure 3: Gradle Project Layout
Once the build has completed, you'll find that a new build subdirectory has been created. Take a look in this directory and you'll see a reports subdirectory containing an index.html file. Open this file in your favorite browser and you'll see the results of the Spock tests—I trust that they all passed. You'll also find a distributions subdirectory—this contains two archive files (zip and tar). These archives contain the distribution items needed to run our application, including start scripts (in the bin directory) and the required libraries (in the lib directory).
Extract one of the archives into a directory and open a new terminal/command prompt at the base of this directory. Run bin/gradle-demo –h (Unix) or bin\gradle-demo.bat –h and you'll see the usage information for the application.
The sample application provided for this book will read in a file containing a list of numbers (one number per line) and will display a basic summary—for an example, see Figure 4: Sample Application Output. In order to read in a file, we use the –f flag followed by a file path. Copy the test files found in src/test/resources to the directory into which you extracted the distribution. You can then run the application with bin/gradle-demo -f sample-data-1.txt (Unix) or bin/gradle-demo -f sample-data-1.txt (Windows).
Count: 6 |
Figure 4: Sample Application Output
The codebase for the project consists of two classes, DataSummary and App. Both of these are in the demo package.
Tip: If you'd like to "decode" some of the code provided here, check out the Groovy documentation for object-oriented programming.
Code Listing 83: The DataSummary Class is reasonably simple, and the constructor does the heavy lifting of determining the summary for the provided list of numbers. I'll draw your attention to one line of code, data = sourceData.asImmutable(), as it demonstrates the very handy asImmutable() method of creating a copy of the sourceData parameter and prevents the contents of the copied list from being changed. It's such as useful tool for defensive coding that I'd feel bad if I didn’t point it out!
Code Listing 83: The DataSummary Class
package demo |
Code Listing 85: The App Class provides the primary application entry point. The main class method is the standard entry point for Groovy (and Java) applications. In the case of the Groovy scripts in the previous chapter, the main method is created for us, but we need to create an entry point for classes. The App class provides a main method, and our Gradle configuration indicates that this is the entry point for our application through the mainClassName = 'demo.App' setting in the build.gradle file.
The App class's main method has a single parameter called args that will hold a list of all of the command-line arguments passed to the application. The choice of this parameter name stems from common Java usage and can be found in Groovy scripts as well. None of the scripts we’ve seen previously have used the command line, so I'll provide a basic demonstration in Code Listing 84: A Basic Script that Lists Args. If you were to call this script with something like groovy cli.groovy -f test.txt, you'd see the two command-line arguments (-f and test.txt) displayed each on a separate line. Essentially, the args variable is a built-in script variable.
Code Listing 84: A Basic Script that Lists Args
args.each { |
We could prepare code to parse the command-line arguments, but Groovy provides us with the CliBuilder class, which is built on the Apache Commons CLI library. In order to set up a CliBuilder object, we pass it a string that provides the usage statement for the application—for the App class I've used new CliBuilder(usage: 'App [options]'). Next, let’s use a static initializer block (static {..}) to prepare the cli class property. Within the initializer block, I've used that handy with method and prepared the list of acceptable command-line arguments. For each entry, we can provide the short form of the option (e.g., f for –f) and a number of additional items:
This sets up the command-line interface for our application. By calling cli.usage(), we can have our usage displayed in a common format, as demonstrated in Figure 5: Usage Display.
usage: App [options] -f,--file <inputFile> The input file with one number per line -h,--help Displays the usage information |
Within the main method, we can have the command-line arguments parsed by simply calling def options = cli.parse args. When this finishes, we can access the arguments using options.h and options.f. You'll see in Code Listing 85: The App Class that I check if the application was passed the –h flag and, if so, displays the usage information. Otherwise, we expect that the application was called with –f and a file path. At various points marking the end of the application, System.exit is called with a number to indicate the exit status—usually 0 if there were no problems, or a number if there was a problem. I also use System.err.println to output error messages to standard error.
Tip: While Commons CLI library is available by default to Groovy scripts, you'll recall that I've added it as an explicit dependency for the Gradle project. This is required because the groovy-all library doesn't include commons-cli.
There are a few other syntax aspects in Code Listing 85: The App Class that I haven't previously covered. I've used the basic form of the if statement to check the command-line arguments. This consists of the if keyword, a conditional expression within parentheses—(options.h)—and a statement that is evaluated if the conditional expression resolves as true. Groovy evaluates options.h to be false if there is no value, so we don't have to check it against something. In fact, values such as 0 (zero), an empty string (''), and null are all seen as false to Groovy, which reduces the syntax needed in this type of check.
The last syntax element you'll notice is the throwing of exceptions—throw new FileNotFoundException("Path does not exist: $fileName")—and exception handling with the try-catch block. However, I'll leave you to pursue other resources to find out more about this.
Code Listing 85: The App Class
package demo |
The Spock Framework provides facilities for writing unit, behavior-driven, integration, and functional tests for Java and Groovy code. That's right, even if you have to write Java for your application code, you can use Spock to test it. And what are Spock tests written in? Yep, Groovy.
To get started with Spock in our Gradle project, we must declare the dependency for the testCompile configuration: testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'. This means that the Spock library will be used to run tests, but it will not be included in the distribution. In order to set up our tests, we need only to add the test code to the src/test/groovy directory. The tests will be performed when you run gradlew build, but you can specifically run the tests by calling gradlew test.
Spock leverages Groovy to provide an easily readable approach to writing tests. To see how far this goes, check out Code Listing 86: Spock Test for the DataSummary Class. In order to declare the test class, we extend Spock's Specification class, then provide one or more tests, each defined as a method. There's a high level of human readability in the code:
Each block can be accompanied with a piece of text to explain what's going on. You'll see that the tests provided here use a mix of blocks, depending on what action needs to be performed.
Code Listing 86: Spock Test for the DataSummary Class
package demo |
To my mind, Spock's approach to data-driven testing is a key selling point for the framework. In both of the test classes, I've prepared a data table in the where block that allows me to easily provide a series of test inputs alongside the desired result. The format of the data table is elementary:
Each (nonheader) row represents an iteration of the test. Therefore, a table with three (nonheader) rows will cause the test method to be run three times. Take a look at the data table in Code Listing 86: Spock Test for the DataSummary Class and you'll notice that we can even declare lists (and other objects) within the data table. The data variables are then accessed as variables within the test.
The @Unroll annotation is used to report each test iteration independently. This is useful in determining a problem, especially because we can use data variable names in the test's descriptor. For example, @Unroll("Number list: #numbers") results in a single test iteration being named Number list: [10, 12, 14] in the test report. By prefixing the hash/pound symbol (#) to the data variable name, we've advised Spock to perform a substitution. In fact, we don't need to provide this within the @Unroll annotation and could have used the data variable names in the test's method declaration (e.g., def "Trivial DataSummary Test with number list: #numbers"()).
In both of the example test classes, I've also demonstrated our ability to check that an exception was thrown when running the test. For example, in Code Listing 87: Spock Test for the App Class, I make sure that the call to App.loadNumberListFromFile('') causes an exception by checking for thrown(FileNotFoundException).
Code Listing 87: Spock Test for the App Class
package demo |
Gradle is a great step forward for build systems. It's more readable than XML-based approaches and easily customized and extended with Groovy. Investing some time in exploring Gradle and the range of community-submitted plugins will be worthwhile.
This chapter also introduced you to the Spock Framework. Spock provides an extremely versatile approach to testing that blends technical and narrative aspects. I’ve only scratched the surface here of what's possible with Spock, and I encourage you to look into it further.
Both Gradle and Spock employ the strengths of Groovy and can be utilized whether or not you use Groovy in your application code.