Unit tests for ONOS are built using the JUnit framework, version 4.1112. Unit tests are used to verify correctness of implementations and are run as part of every full build of ONOS. The nightly SonarQube build runs all of the unit tests and produces coverage data that can be seen here.
Table of Contents |
---|
How to Write Good Tests
Unit tests should be as short, fast, and reliable as they can be. Every developer on the project will be running your tests, they should be easy to run and produce accurate results. Here are some key ways to write tests that are not brittle:
- Avoid using sleep() whenever possible, since it often leads to brittle tests. If you find that you have to wait for an event or wait for some work to be done by another thread, prefer latches or thread notifications to sleeps.
- Try to keep individual tests small, and only test one thing per test.
- Use mocking when you need to include a complicated service to satisfy dependencies. ONOS uses the EasyMock framework, version 3.24.
- Maven Bazel may choose to run multiple tests in the same Java virtual machine. If you use static variables in classes, be sure to reset them to known starting values before each test runs.
- Do not use local resources like files and , ports, or IP Addresses in tests.
Basic Tests
...
A basic test for each one of your constructors and a check for the values returned by data access methods should always be included.
Code Block | ||||
---|---|---|---|---|
| ||||
/** * Checks the construction of a FlowId object. */ @Test public void testConstruction() { final long flowIdValue = 7777L; final FlowId flowId = FlowId.valueOf(flowIdValue); assertThat(flowId, is(notNullValue())); assertThat(flowId.value(), is(flowIdValue)); } |
Mocking
Mocking is a useful strategy for limiting the interaction between your code under test and other modules that are required to satisfy dependencies. There are several strategies that may be used for mocking, two that are used inside of ONOS are described here.
EasyMock Mocking Framework
EasyMock allows a test to create a mocked object directly from an interface, without having to define a class and mock every method defined by the API. The test writer can define only the methods that are required to execute the test. In this example, a mock is created for the HostService API which is passed in to the HostToHostIntentCompiler class.
Referencing Private Data
Sometimes when writing a test, you'll need to access data from a class that does not have a public API to access the data. In ONOS, we try to not add interfaces only used by test code. Instead, we have utility methods that use the Java reflection API to access this private data. In this example, the test verifies that the contents of the private intentsByLink
member are correct using the TestUtils.getField()
method to access the member.
Code Blockcode | ||||
---|---|---|---|---|
| ||||
import static org.easymockonlab.EasyMockjunit.*TestUtils; private@Test HostService mockHostService; public void privatetestLeaderEvents() staticthrows finalException String HOST_ONE_MAC = "00:00:00:00:00:01"; { private static final String HOST_TWO_MACObjectiveTracker tracker = "00:00:00:00:00:02"; new ObjectiveTracker(); private static final String HOST_ONE_VLAN = "-1";SetMultimap<LinkKey, IntentId> intentsByLink = private static final String HOST_TWO_VLAN = "-1"; private TestUtils.getField(tracker, "intentsByLink"); assertThat(intentsByLink.size(), is(0)); } |
Mocking
Mocking is a useful strategy for limiting the interaction between your code under test and other modules that are required to satisfy dependencies. There are several strategies that may be used for mocking, two that are used inside of ONOS are described here.
EasyMock Mocking Framework
EasyMock allows a test to create a mocked object directly from an interface, without having to define a class and mock every method defined by the API. The test writer can define only the methods that are required to execute the test. In this example, a mock is created for the HostService API which is passed in to the HostToHostIntentCompiler class.
Code Block | ||||
---|---|---|---|---|
| ||||
import static org.easymock.EasyMock.*; private HostService mockHostServicestatic final String HOST_ONE = HOST_ONE_MAC + "/" + HOST_ONE_VLAN; private static final String HOST_TWO = HOST_TWO_MAC + "/" + HOST_TWO_VLAN; private HostId hostOneId = HostId.hostId(HOST_ONE); private HostId hostTwoId = HostId.hostId(HOST_TWO); @Test public void testHostToHostIntentCompiler() throws Exception { mockHostService = createMock(HostService.class); expect(mockHostService.getHost(eq(hostOneId))).andReturn(hostOne).anyTimes(); expect(mockHostService.getHost(eq(hostTwoId))).andReturn(hostTwo).anyTimes(); replay(mockHostService); private static final HostToHostIntentCompiler compiler = new HostToHostIntentCompiler(); compiler.hostService = mockHostService; String HOST_ONE_MAC = "00:00:00:00:00:01"; private static final String HOST_TWO_MAC = "00:00:00:00:00:02"; private static final Intent intent = new HostToHostIntent(APPID, hid(oneIdString), hid(twoIdString),String HOST_ONE_VLAN = "-1"; private static final String HOST_TWO_VLAN = "-1"; private static final String HOST_ONE = HOST_ONE_MAC + "/" + HOST_ONE_VLAN; private static final String HOST_TWO = HOST_TWO_MAC + "/" + HOST_TWO_VLAN; private HostId hostOneId selector, treatment= HostId.hostId(HOST_ONE); private HostId hostTwoId final List<Intent> result = compiler.compile(intent, null, null); = HostId.hostId(HOST_TWO); @Test public void testHostToHostIntentCompiler() throws Exception { mockHostService assertThat(result, is(Matchers.notNullValue()))= EasyMock.createMock(HostService.class); assertThat(result, hasSize(2)); } |
Custom Mocks
Sometimes a test writer will want to implement their own complete mocked implementation of an API rather than use the mocking framework. This can be accomplished by creating a class that implements the API interface, and then implementing the methods to do whatever the test requires.
expect(mockHostService.getHost(eq(hostOneId))).andReturn(hostOne).anyTimes();
expect(mockHostService.getHost(eq(hostTwoId))).andReturn(hostTwo).anyTimes();
replay(mockHostService);
final HostToHostIntentCompiler compiler = new HostToHostIntentCompiler();
compiler.hostService = mockHostService;
final Intent intent = new HostToHostIntent(APPID, hid(oneIdString), hid(twoIdString),
selector, treatment);
final List<Intent> result = compiler.compile(intent, null, null);
assertThat(result, is(Matchers.notNullValue()));
assertThat(result, hasSize(2));
}
|
Custom Mocks
Sometimes a test writer will want to implement their own complete mocked implementation of an API rather than use the mocking framework. This can be accomplished by creating a class that implements the API interface, and then implementing the methods to do whatever the test requires.
Code Block | ||||
---|---|---|---|---|
| ||||
private static class TestIntentCompilerError implements IntentCompiler<Intent> {
@Override
public List<Intent> compile(Intent intent, List<Intent> installable,
Set<LinkResourceAllocations> resources) {
throw new IntentCompilationException("Compilation always fails");
}
}
/**
* Tests for proper behavior of installation of an intent that triggers
* a compilation error.
*/
@Test
public void errorIntentCompile() | ||||
Code Block | ||||
| ||||
private static class TestIntentCompilerError implements IntentCompiler<Intent> { @Override final TestIntentCompilerError errorCompiler = new TestIntentCompilerError(); public List<Intent> compile(Intent intent,final List<Intent>IntentManager installable, intentManager = new IntentManager(); final IntentExtensionService extensionService = intentManager; final IntentService intentService = intentManager; Set<LinkResourceAllocations> resources) {extensionService.registerCompiler(MyIntent.class, errorCompiler); MyIntent intent throw= new IntentCompilationException("Compilation always fails"MyIntent(); } } /** * Tests for proper behavior of installation of an intent that triggers * a compilation error. */ @Test public void errorIntentCompile() { // Invoke the error compiler intentService.submit(myIntent); } |
Hamcrest
Hamcrest is a powerful framework for defining matchers for data values in tests. ONOS uses version 1.3 of Hamcrest. Contributors are encouraged to learn about and use the Hamcrest framework, particularly when working with arrays and collections. Hamcrest also gives you the ability to write your own matchers, which will make your tests easier to read and easier to extend by other developers.
Here is an example of a custom Hamcrest matcher, that matches a Criterion type in a TrafficSelector object:
Code Block | ||||
---|---|---|---|---|
| ||||
/** * Hamcrest matcher finalto check TestIntentCompilerErrorthat errorCompilera =selector new TestIntentCompilerError();contains a * Criterion with finalthe IntentManagerspecified intentManagertype. = new IntentManager(); */ public static final IntentExtensionServiceclass extensionServiceCriterionExistsMatcher = intentManager; final IntentService intentServiceextends =TypeSafeMatcher<TrafficSelector> intentManager;{ private extensionService.registerCompiler(MyIntent.class, errorCompiler)final Criterion.Type type; MyIntent/** intent = new MyIntent(); // Invoke the error compiler intentService.submit(myIntent); } |
Hamcrest
Hamcrest is a powerful framework for defining matchers for data values in tests. ONOS uses version 1.3 of Hamcrest. Contributors are encouraged to learn about and use the Hamcrest framework, particularly when working with arrays and collections. Hamcrest also gives you the ability to write your own matchers, which will make your tests easier to read and easier to extend by other developers.
Here is an example of a custom Hamcrest matcher, that matches a Criterion type in a TrafficSelector object:
Code Block | ||||
---|---|---|---|---|
| ||||
/** * Constructs a matcher for the given criterion type. * * @param typeValue criterion type to match */ public CriterionExistsMatcher(Criterion.Type typeValue) { type = typeValue; * Hamcrest} matcher to check that a selector contains a@Override * Criterionpublic withboolean the specified type. matchesSafely(TrafficSelector selector) { */ public static final Set<Criterion> class CriterionExistsMatchercriteria = selector.criteria(); extends TypeSafeMatcher<TrafficSelector> { return notNullValue().matches(criteria) && private final Criterion.Type type; /** hasSize(1).matches(criteria) && * Constructs a matcher for the given criterion type. notNullValue().matches(selector.getCriterion(type)); * } @Override * @param typeValuepublic criterionvoid type to matchdescribeTo(Description description) { */ description.appendText("a criterion with type public CriterionExistsMatcher(Criterion.Type typeValue) {\" "). type = typeValue; appendText(type.toString()). } @Override public boolean matchesSafely(TrafficSelector selector) {appendText("\""); final Set<Criterion> criteria = selector.criteria(); } } /** * Creates a criterion returntype notNullValue().matches(criteria) && matcher. Returns a matcher * for a criterion with the given type. hasSize(1).matches(criteria) && * * @param type type of Criterion to match * @return notNullValue().matches(selector.getCriterion(type));Matcher object */ } @Factory public static Matcher<TrafficSelector> @OverridehasCriterionWithType(Criterion.Type type) { publicreturn voidnew describeToCriterionExistsMatcher(Description description) {type); } /** description.appendText("a criterion with type \" "). * Tests the builder functions that add specific criteria. */ @Test public void appendText(type.toString()).testCriteriaCreation() { TrafficSelector selector; selector = appendText("\"");DefaultTrafficSelector.builder() } } /**.matchInport(PortNumber.portNumber(11)).build(); * Creates a criterion type matcher. Returns a matcherassertThat(selector, hasCriterionWithType(Type.IN_PORT)); * for a criterion with the given type. * * @param type type of Criterion to match * @return Matcher object */ @Factory public static Matcher<TrafficSelector> hasCriterionWithType(Criterion.Type type) { return new CriterionExistsMatcher(type); } /**} |
Add library for Unit Test
If you are using a library for Unit Test other than the above, please add the library to the build path. You can modify the lib/deps.json
file where we keep track of external libraries:
Code Block | ||||
---|---|---|---|---|
| ||||
vi lib/deps.json |
Code Block | ||
---|---|---|
| ||
{ "libraries": { ・・・ , "TEST": [ "[new library name]", * Tests"junit", the builder functions that add specific criteria. "easymock", */ @Test "hamcrest-all", public void testCriteriaCreation() { "hamcrest-optional", "guava-testlib", TrafficSelector selector; "//utils/junit:onlab-junit" selector = DefaultTrafficSelector.builder()], ・・・ }, "artifacts": { ・・・ "jsch":"mvn:com.jcraft:jsch:0.1.53", "[new library name]":"mvn:[new library .matchInport(PortNumber.portNumber(11)).build();path]:[new library version]", "junit":"mvn:junit:junit:4.12", assertThat(selector, hasCriterionWithType(Type.IN_PORT)); } |
...
"junit-dep":"mvn:junit:junit:4.10",
・・・
}
} |
...and then run onos-lib-gen
tool to re-generate the Bazel workspace file.
Code Block | ||||
---|---|---|---|---|
| ||||
tools/build/onos-lib-gen |
...
Home : Contributing to the ONOS Codebase
...