Human Factors of XR: Using Human Factors to Design XR Systems
Unit Testing Standards - Recommended Best Practices
1. Unit Testing Standards # Recommended Best Practices
Definition of Unit Testing
Unit testing is the execution and validation of a block of code in isolation that a developer created. Because an object’s behavior is determined by its
method logic, we are going identifies a unit as a method within a class. Don’t write tests, write examples.
Recommended Guideline
• Write tests for methods that have the fewest dependencies first, and work your way up. If you start by testing a high-level method, your test
may fail because a subordinate method may return an incorrect value to the method under test. This increases the time spent finding the source
of the problem, and you will still have to test the subordinate method anyway to be sure it does not contain other bugs.
• Tests should be logically simple as possible, preferably with no decisions at all. Every decision added to a test method increases the number
of possible ways that a test method can be executed. Testing is meant to be a controlled environment, the only thing you want to change is the
input to the method under test, not the test method itself.
• Where possible, use constant expected values in your assertions instead of computed values. Consider these two assertions:
returnVal = methodUnderTest(input);
assertEquals(returnVal, computeExpectedReturnValue(input));
assertEquals(returnVal, 12);
In order for computeExpectedReturnValue() to return the proper result it probably has to implement the same logic as the method under test,
which is what you are trying to test for in the first place! Further, suppose you fixed a defect in the method under test. Now you have to change
computeExpectedReturnValue() in the same manner. The second assertion is easier to understand and maintain.
• Each unit test should be independent of all other tests. A unit test should execute one specific behavior for a single method. Validating
behavior of multiple methods is problematic as such coupling can increase refactoring time and effort. Consider the following example:
void shouldAdd(){
int return1 = myClass.add(1,2);
int return2 = myclass.add(-1,-2);
assertTrue (return1 - return2 == 0);
}
If the assertions fails in this case, it will be difficult to determine which invocation of add() caused the problem.
• Each unit test should be clearly named and documented. The name of the test method should clearly indicate which method is being tested
because improperly named tests increase maintenance and refactoring efforts. Comments should also be used to describe the test or any special
conditions.
1 | Unit Testing Standards # Recommended Best Practices
2. • All methods, regardless of visibility, should have appropriate unit tests. Public, private and protected methods that contain data and
decisions should be unit tested in isolation.
• All unit tests should use appropriate coverage techniques to measure their effectiveness. Isolating coverage for each unit test
independently of all other unit test is the only way to verify actual results. It is not appropriate to look at coverage numbers by themselves. Think
of the situation where a unit test for Method A may increase the coverage for Method B as well. Even if Method B has coverage, unless the specific
behavior was verified via an assertion, it should not be considered unit tested.
• Strive for one assertion per test case. Multiple assertions in one test case can cause performance issues and bottlenecks. For example, if a test
case with five assertions fails on the first assertion, then the remaining four are not executed. Only when the first assertion is corrected will the
other assertions execute. If assertion two fails, then that also must be corrected before the remaining three assertions are verified.
One major benefit of unit testing is to find the root cause of defects. Unit tests should be designed with this in mind, thus it is more economical to
have one assertion per test case. Although the unit test file may be large, it will be easier to find and fix defects.
• Create unit tests that target exceptions. Exceptions are thrown when a method finds itself in a state it cannot handle. These cases should be
tested just like a method’s normal state of operations.
If a method is declared to throw one or more exceptions, then unit tests must be create that simulate the circumstances under which those
exceptions could be thrown. The unit tests should assert that the exceptions are thrown as expected.
• Ultimate test template. The linguistic relativity principle (also known as the Sapir-Whorf Hypothesis) is the idea that the varying cultural
concepts and categories inherent in different languages affect the cognitive classification of the experienced world in such a way that speakers of
different languages think and behave differently because of it.
@Test
public void should...() throws Exception {
// given
...
// when
...
// then
…
}
If you have more then one statement in when, you may need a new example. If you have assertions outside then, you may need a new example
too.
• Stable test data as the Golden Rule. No matter how many times you run the test it should always give the same results. You must avoid using
random test data and loops. Never use conditionals, you definitely need a new example or your example is broken.
2 | Unit Testing Standards # Recommended Best Practices
3. Appendix 1. Summary Examples.
Figure 1. Figure 3.
@Test @Test
public void shouldSum() throws Exception { public void shouldPow() throws Exception {
// given // given
final String arg1 = "2"; final String arg1 = "2";
final String arg2 = "3"; final String arg2 = "3";
final String expectedResult = "5"; final String expectedResult = "8";
// when final StringNumber mockStringNumber = Mockito.mock(StringNumber.class);
final String actualResult = new StringNumber().sum(arg1, arg2); Mockito.when(mockStringNumber.multiply("2", "2")).thenReturn("4");
Mockito.when(mockStringNumber.multiply("4", "2")).thenReturn("8");
// then
Assertions.assertThat(actualResult).isNotEmpty().isEqualTo(expectedResult); // when
} final String actualResult = new StringNumber().pow(arg1, arg2);
// then
Figure 2. Assertions.assertThat(actualResult).isEqualTo(expectedResult);
@Test }
public void shouldThrowArithmeticException() throws Exception {
// given
final String arg1 = "2"; Figure 4.
final String arg2 = "0"; @Test
final String expectedMessage = "/ by zero"; public void shouldZero() throws Exception {
// given
// when final String arg1 = "-0";
ArithmeticException actualException = null; final String arg2 = "+0";
try { final String zeroNumber = "0";
new StringNumber().div(arg1, arg2);
} catch (final ArithmeticException e) { // when
actualException = e; final String actualResult = new StringNumber().sum(arg1, arg2);
}
// then
// then Assertions.assertThat(actualResult).isNotEmpty().isNotSameAs(arg1)
Assertions.assertThat(actualException).as("Should raise an .isNotSameAs(arg2).isEqualTo(zeroNumber);
ArithmeticException:").hasMessage(expectedMessage); }
}
3 | Unit Testing Standards # Recommended Best Practices
4. Appendix 2. Class Under Test
public class StringNumber {
public String sum(final String arg1, final String arg2) {
return String.valueOf(Integer.valueOf(arg1).intValue() + Integer.valueOf(arg2).intValue());
}
public String div(final String arg1, final String arg2) {
return String.valueOf(Integer.valueOf(arg1).intValue() / Integer.valueOf(arg2).intValue());
}
public String multiply(final String arg1, final String arg2) {
return String.valueOf(Integer.valueOf(arg1).intValue() * Integer.valueOf(arg2).intValue());
}
public String pow(final String arg1, final String arg2) {
String result = arg1;
for (int i = 1; i <= Integer.valueOf(arg2).intValue() - 1; i++) {
result = this.multiply(result, arg1);
}
return String.valueOf(result);
}
}
Appendix 3. FEST-Assert Usage Examples.
• int removed = employees.removeFired(); • String[] newHires = employees.newHiresNames();
assertThat(removed).isZero(); assertThat(newHires).containsOnly("Gandalf", "Arwen", "Gimli");
• List<Employee> newEmployees = employees.hired(TODAY); • assertThat(yoda).isInstanceOf(Jedi.class)
assertThat(newEmployees).hasSize(6).contains(frodo, sam); .isEqualTo(foundJedi)
.isNotEqualTo(foundSith);
4 | Unit Testing Standards # Recommended Best Practices
5. Appendix 4. FEST-Reflect in Reflection usage.
One of the problems with Reflection is that its API is not very intuitive and quite verbose. For example, to call the method:
String name = names.get(8);
using reflection, we need the following code:
Method method = Names.class.getMethod("get", int.class);
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
method.setAccessible(true);
return null;
}
});
String name = (String) method.invoke(names, 8);
and with FEST-Reflect:
String name = method("get").withReturnType(String.class)
.withParameterTypes(int.class)
.in(names)
.invoke(8);
which, in my opinion, is more compact, readable and type safe.
The following examples demonstrate how easy is to access constructors, methods and fields using FEST-Reflect:
• Person person = constructor().withParameterTypes(String.class) • field("name").ofType(String.class)
.in(Person.class) .in(person)
.newInstance("Yoda"); .set("Anakin");
• method("setName").withParameterTypes(String.class) • List<String> powers = field("powers").ofType(new TypeRef<List<String>>() {})
.in(person) .in(jedi)
.invoke("Luke"); .get();
5 | Unit Testing Standards # Recommended Best Practices
6. Appendix 4. Mockito vs EasyMock
EasyMock Differences
• only one kind of mock (no nice/default/strict mocks). Using EasyMock
import static org.easymock.classextension.EasyMock.*; metaphor: Mockito mocks can be nice or strict - but it depends on
List mock = createNiceMock(List.class); how you verify mocks, not how you create them. Because there is
only one kind of mock - it is easier to share setup (situations where
expect(mock.get(0)).andStubReturn("one"); mocks are fields in test class but different test methods require
expect(mock.get(1)).andStubReturn("two"); different kinds of mocks).
mock.clear(); • no record/replay modes - no need for them. There only 2 things you
can do with Mockito mocks - verify or stub. Stubbing goes before
replay(mock); execution and verification afterwards.
• all mocks are 'nice' (even nicer, because collection-returning methods
someCodeThatInteractsWithMock(); return empty collections instead of nulls). Even though mocks are
nice, you can verify them as strictly as you want and detect any
verify(mock); unwanted interaction.
• explicit language for better readability: verify() and stub() instead of
mixture of expect() and ordinary method calls on mocks.
Mockito
• simplified stubbing model - stubbed methods replay all the time with
import static org.mockito.Mockito.*; stubbed value no matter how many times they are called. Works
exactly like EasyMock's andStubReturn(), andStubThrow(). Also, you
List mock = mock(List.class); can stub with different return values for different arguments (like in
EasyMock).
when(mock.get(0)).thenReturn("one"); • Verification of stubbed methods is optional because usually it's more
when(mock.get(1)).thenReturn("two"); important to test if the stubbed value is used correctly rather than
where's it come from.
someCodeThatInteractsWithMock(); • verification is explicit - verification errors point at line of code
showing what interaction failed.
verify(mock).clear(); • only one way of creating mocks (no MockControl object).
• verification in order is flexible and doesn't require to verify every
single interaction.
Similarities • custom argument matchers use hamcrest matchers, so you can use
• allow the same level verification as EasyMock (unexpected your existing hamcrest matchers. (EasyMock also integrates with
invocations, redundant invocations, verification in order) hamcrest, see the documentation of hamcrest).
• argument matchers (anyInt(), anyObject(), etc.)
6 | Unit Testing Standards # Recommended Best Practices
7. Verification in order Stubbing void methods
EasyMock EasyMock
Control control = createStrictControl(); List mock = createNiceMock(List.class);
List one = control.createMock(List.class); mock.clear();
List two = control.createMock(List.class); expectLastCall().andThrow(new RuntimeException());
expect(one.add("one")).andReturn(true); replay(mock);
expect(two.add("two")).andReturn(true);
control.replay(); Mockito
someCodeThatInteractsWithMocks(); List mock = mock(List.class);
control.verify(); doThrow(new RuntimeException()).when(mock).clear();
Exact number of times verification and argument matchers
Mockito
EasyMock
List one = mock(List.class);
List two = mock(List.class); List mock = createNiceMock(List.class);
someCodeThatInteractsWithMocks(); mock.clear();
expectLastCall().times(3);
InOrder inOrder = inOrder(one, two); expect(mock.add(anyObject())).andReturn(true).atLeastOnce();
inOrder.verify(one).add("one"); someCodeThatInteractsWithMock();
inOrder.verify(two).add("two");
replay(mock);
Mockito
List mock = mock(List.class);
someCodeThatInteractsWithMock();
verify(mock, times(3)).clear();
verify(mock, atLeastOnce()).add(anyObject());
7 | Unit Testing Standards # Recommended Best Practices