At the recent OpenMRS Worldwide Summit #OMRS15, I was helping out with the Saptarshi’s introductory tutorial and realized that the OpenMRS Standalone requires Java 7, meaning that it fails to run on Java 8. Yikes! But my Mac runs Java 8. How do I get Java 7 on my laptop without making a mess of things?
jenv to the rescue! jenv provides an easy way to manage multiple Java versions. Not only can you easily switch between Java versions, but you can configure different folders to run specific versions of Java. So, for example, you can run Java 7 for the OpenMRS Standalone and run Java 8 everywhere else.
Step zero. Install Homebrew. I already had this installed. I used to use Macports to install utilities on my Mac, but it was fairly invasive. Homebrew installs most everything under your user account, so it doesn’t mess with Mac’s view of the world and rarely, if ever, requires the use of sudo.
Step one. Install jenv.
brew install jenv
Step two. Install Java 7.
Grab the latest version of Java 7 SDK from Oracle’s Java 7 archive. For me, this meant navigating to the Java SE Development Kit 7u80 downloads, accepting the license agreement, and then downloading and installing from the dmg package for Mac OS X x64.
Step three. Tell jenv about Java 7.
jenv add /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/
If your version number differs, then your command may differ slightly. You can always navigate into /Library/Java/JavaVirtualMachines/ to find your installed version folders.
Done!
Now you can navigate into your OpenMRS Standalone folder, set the preferred Java version with a command like:
jenv local oracle64-1.7.0.80
and then execute the standalone with:
jenv exec java -jar openmrs-standalone.jar
I recently described the @should taglet created by OpenMRS that helped the community adopt and sustain better testing practices. Mário asked a good question about test-driven development (TDD):
While I don’t think we’re doing much TDD in the OpenMRS Community at this point, it would be great to evolve this direction. The real question is: will the @should tags that helped us start testing our code become an impediment to TDD? I don’t think so.
Let’s try a simple example to see how we could be TDD-ish with @should tags. Imagine that we want to be able to get the age in years of a person:
class Person {
Integer getAge(Date onDate) {
return 0; // TODO: return age
}
}
Before we write any code, we describe the expected behavior. To keep the example brief, I’ll just describe a couple expected behaviors:
class Person {
/**
* Returns person's age in years.
* @should return null for date before birthdate
* @should not round up age
*/
Integer getAge(Date onDate) {
return 0; // TODO: return age
}
}
Next, we invoke the Behavior Test Generator plugin to automatically do the busy work of generating the skeleton for our unit tests.
class PersonTest {
void getAge_shouldReturnNullForDateBeforeBirthdate() {
// TODO: write unit test
}
void getAge_shouldNotRoundUpAge() {
// TODO: write unit test
}
}
So, now we can write our unit tests and see them fail, like any newborn tests in TDD would do. Granted, in this example, you don’t technically start with the test code, but you can start with describing behavior (using @should tags) prior to writing code and using those tests to drive development. So, yes, we start with @should tags; however, @should tags, can precede any actual code, since they are effectively shorthand for the tests we are writing before coding.
In 2008, when OpenMRS was struggling to adopt better test-driven development practices, I was lucky enough to read Dan North’s Introducing BDD. As Dan says:
How true! For example, it’s common to see something like this when you start creating unit tests:
public class PatientTest {
public void testPatient() {
// test stuff here
}
}
The next question is, what gets tested in a method called “testPatient”? I suppose the only wrong answer is “nothing.” But the problem is there are an infinite number of right answers… because “testPatient” doesn’t say anything about the behavior. As Dan points out, simply replacing the word “test” with the word “should” is a game changer. Let’s try again, except this time we will use “should” in our method name:
public class PatientTest {
public void addIdentifier_shouldNotAddIdentifierThatIsInListAlready() {
// make sure an identifier isn't duplicated
}
}
It’s much easier to guess what will be tested inside that unit test’s method. That’s good… but it gets better. Dan’s suggestion of “should” not only places the focus on behavior, it also automagically forces testing to be scoped to a specific behavior, since any developer who sees a method name wrapping onto its third line instantly knows she is going about testing the wrong way and will look for help. Dan gives a great justification for this approach… but he had me at should.
Given Dan’s insight into using “should” instead of “test” to drive BDD, the trick was figuring out how we could engrain this approach within the OpenMRS community. After some discussion, we came up with an idea that I’m still proud of today and I believe has helped us adopt a better testing culture. Here’s what we did…
Testing is often filled with cookie-cutter code and requires additional effort that is difficult to sustain. We wanted to find a way to overcome both of these challenges. What we needed was a trivially easy way to generate behavior-focused tests. So, we invented the @should Javadoc tag to allow developers to describe expected behaviors within the Javadoc and then we paid someone to develop an IDE plugin to auto-generate the test methods from existing method names.
Now that we have the @should tag, let’s take on more stab at testing. Imagine you are writing some code for the Patient object…
public class Patient {
public void addIdentifier(PatientIdentifier patientIdentifier) {
// ...
}
}
You know that an identifier shouldn’t be added twice for the same patient, so you simply state that behavior in the Javadoc:
public class Patient {
/**
* @should not add identifier that is in list already
*/
public void addIdentifier(PatientIdentifier patientIdentifier) {
// ...
}
}
That’s it. You’re already doing BDD! Now, you tell your IDE to generate any missing unit tests for Patient and it automatically generates this method stub for you in the appropriate location:
public class PatientTest {
public void addIdentifier_shouldNotAddIdentifierThatIsInListAlready) {
// write your test here
}
}
The IDE plugin automatically derives the proper location and method name from your @should tag and the associated method. Now you can focus on testing that specific behavior without having to worry about any cookie-cutter code and adopting BDD is as simple as writing a Javadoc comment.
Low threshold to start testing behavior: just write a brief sentence starting with @should in your Javadoc.We still have a long way to go down the road to full BDD, but I was very happy with our first step. Over the years, the @should tag has become a handy tool for establishing a behavior-driven culture of testing; in fact, it has helped us adopt testing in general. For any Java-shop that is wondering “How do we get our developers to start testing their code?”, I would strongly encourage you to read Dan North’s writings and consider adopting the @should Javadoc tag.
I’m very excited about the OpenMRS SDK and what it can mean for the OpenMRS Developer Community.
Chris Niesel (h3llborn) brought this beautiful creature to life during Google Summer of Code 2013 under the mentorship of Rafał Korytkowski. Strong work, guys! Looking forward to watching the SDK grow into both the first thing a new OpenMRS Developer touches and an invaluable tool for daily development of our most experienced developers!
Maybe there’s already a tool to do this, but a quick Google search only yield this tip. So, I made a quick little JAR reader to fetch the estimated version from the manifest:
import java.util.*;
import java.util.jar.*;
import java.io.*;
/*
* Sends the "estimated" JDK version (expected to be in the jar manifest "Created-By" attribute)
* to System.out.
*/
public class JarJDKVersion {
public static void main(String[] args) {
JarFile jarFile = null;
Manifest manifest = null;
String version = null;
// Report usage if no parameters given
if (args.length < 1) {
System.out.println("usage: java JarJDKVersion ");
System.exit(0);
}
// Open specified jar file
try {
jarFile = new JarFile(args[0]);
} catch (IOException e) {
System.err.println("Unable to read jar file: " + args[0]);
System.exit(1);
}
// Fetch manifest from jar file
try {
manifest = jarFile.getManifest();
} catch (IOException e) {
System.err.println("Unable to read manifest from jar file: " + args[0]);
System.exit(2);
}
// Display version from jar manifest
Attributes attributes = manifest.getMainAttributes();
version = attributes.getValue("Created-By").toString();
if (version != null)
System.out.println(version);
else {
System.err.println("Unable to determine version from jar manifest.");
System.exit(3);
}
}
}
Download the source code or the class file.