Best Practices when writing Tests for loklak Server
Why do we write unit-tests? We write them to ensure that developers’ implementation doesn’t change the behaviour of parts of the project. If there is a change in the behaviour, unit-tests throw errors. This keep developers in ease during integration of the software and ensure lower chances of unexpected bugs.
After setting up the tests in Loklak Server, we were able to check whether there is any error or not in the test. Test failures didn’t mention the error and the exact test case at which they failed. It was YoutubeScraperTest that brought some of the best practices in the project. We modified the tests according to it.
The following are some of the best practices in 5 points that we shall follow while writing unit tests:
-
Assert the assertions
There are many assert methods which we can use like assertNull, assertEquals etc. But we should use one which describes the error well (being more descriptive) so that developer’s effort is reduced while debugging.
Using these assertions related preferences help in getting to the exact errors on test fails, thus helping in easier debugging of the code.
Some examples can be:-
- Using assertThat() over assertTrue
assertThat() give more descriptive errors over assertTrue(). Like:-
When assertTrue() is used:
java.lang.AssertionError: Expected: is <true> but: was <false> at org.loklak.harvester.TwitterScraperTest.testSimpleSearch(TwitterScraperTest.java:142) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at org.hamcr..........
When assertThat() is used:
java.lang.AssertionError: Expected: is <true> but: was <false> at org.loklak.harvester.TwitterScraperTest.testSimpleSearch(TwitterScraperTest.java:142) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at org.hamcr...........
NOTE:- In many cases, assertThat() is preferred over other assert method (read this), but in some cases other methods are used to give better descriptive output (like in next examples)
- Using assertEquals() over assertThat()
For assertThat()
java.lang.AssertionError: Expected: is "ar photo #test #car https://pic.twitter.com/vd1itvy8Mx" but: was "car photo #test #car https://pic.twitter.com/vd1itvy8Mx" at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20) at org.junit.Assert.assertThat(Ass........
For assertEquals()
org.junit.ComparisonFailure: expected:<[c]ar photo #test #car ...> but was:<[]ar photo #test #car ...> at org.junit.Assert.assertEquals(Assert.java:115) at org.junit.Assert.assertEquals(Assert.java:144) at org.loklak.harvester.Twitter.........
We can clearly see that second example gives better error description than the first one.(An SO link)
-
One Test per Behaviour
Each test shall be independent of other with none having mutual dependencies. It shall test only a specific behaviour of the module that is tested.
Have a look of this snippet. This test checks the method that creates the twitter url by comparing the output url method with the expected output url.
@Test public void testPrepareSearchURL() { String url; String[] query = { "fossasia", "from:loklak_test", "spacex since:2017-04-03 until:2017-04-05" }; String[] filter = {"video", "image", "video,image", "abc,video"}; String[] out_url = { "https://twitter.com/search?f=tweets&vertical=default&q=fossasia&src=typd", "https://twitter.com/search?f=tweets&vertical=default&q=fossasia&src=typd", }; // checking simple urls for (int i = 0; i < query.length; i++) { url = TwitterScraper.prepareSearchURL(query[i], ""); //compare urls with urls created assertThat(out_url[i], is(url)); } }
This unit-test tests whether the method-under-test is able to create twitter link according to query or not.
-
Selecting test cases for the test
We shall remember that testing is a very costly task in terms of processing. It takes time to execute. That is why, we need to keep the test cases precise and limited. In loklak server, most of the tests are based on connection to the respective websites and this step is very costly. That is why, in implementation, we must use least number of test cases so that all possible corner cases are covered.
-
Test names
Descriptive test names that are short but give hint about their task which are very helpful. A comment describing what it does is a plus point. The following example is from YoutubeScraperTest. I added this point to my ‘best practices queue’ after reviewing the code (when this module was in review process).
/** * When try parse video from input stream should check that video parsed. * @throws IOException if some problem with open stream for reading data. */ @Test public void whenTryParseVideoFromInputStreamShouldCheckThatJSONObjectGood() throws IOException { //Some tests related to method }
AND the last one, accessing methods
This point shall be kept in mind. In loklak server, there are some tests that use Reflection API to access private and protected methods. This is the best example for reflection API.
In general, such changes to access specifiers are not allowed, that is why we shall resolve this issue with the help of:-
- Setters and Getters (if available, use it or else create them)
- Else use Reflection
If the getter methods are not available, using Reflection API will be the last resort to access the private and protected members of the class. Hereunder is a simple example of how a private method can be accessed using Reflection:
void getPrivateMethod() throws Exception { A ret = new A(); Class<?> clazz = ret.getClass(); Method method = clazz.getDeclaredMethod("changeValue", Integer.TYPE); method.setAccessible(true); System.out.println(method.invoke(ret, 2)); //set null if method is static }
I should end here. Try applying these practices, go through the links and get sync with these ‘Best Practices’ 🙂
Resources:
- Loklak Server Tests directory: https://github.com/loklak/loklak_server/tree/development/test
- Reflection API: https://docs.oracle.com/javase/tutorial/reflect/
- Testing with hamcrest: http://www.vogella.com/tutorials/Hamcrest/article.html
- Assertions: http://docs.oracle.com/javase/7/docs/technotes/guides/language/assert.html