Introduction
Test Strategy
Why do we need Unit Test?
1. To expose latent problems.
Writing an unit test for your function module, class or method properly will avoid some latent bugs which you may not expect at coding stage. Furthermore, a right unit test can point out some inconsistency among functions or modules after you refactor some code.
2. Can be a reference of API.
Once you covered all of public methods, functions, interfaces or modules by unit tests, you or others may refer to these elaborate unit tests as a guidance documentation. One hand they instruct you how to use methods, just like a snippet of sample code, on the other hand, the unit test class tells you what the class which is tested can do.
3. Easy to do regression test.
The unit test can be executed automatically by unit test framework. If you have got these unit tests ready and rational, you can easily go through all of these unit tests by one click. It will save a lot of time and make your work more efficiently.
4. To verify maintainability and scalability of your methods,functions.
If your methods, functions, interface are not so easy to be unit tested, I would say your methods may be hard-to-use. Even you are
not able to write an unit test on your method
1: For Package: package name. Such as de.com.medavis.
2: For Class?Test+class or class+Test name. Such as TestCalculator or CalculatorTest.
3: For Method: test+method name. Such as testAdd().
@Before
An initialization method for each test method must perform a (note the difference with BeforeClass, which is executed once for all method).
@After
The release of resources for each test method must perform a (note the difference with AfterClass, which is executed once for all method).
@Test
Test method, can expect to test abnormalities and timeout here.
@Test (expected=ArithmeticException.class)
Detect method will throw a ArithmeticException.
@Test (timeout=1000)
When run this method cost time>1000(ms) this test case is failed.
@Ignore
Test methods ignore.
@BeforeClass
For all test, only once, and must be static void.
@AfterClass
For all test, only once, and must be static void.
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
Sorts the test methods by the method name.
@FixMethodOrder(MethodSorters.NAME_DEFAULT)
Sorts the test methods in a deterministic, but not predictable.
@BeforeClass and @AfterClass for all tests, run only one times.Use of the database connection, the message queue initialization of some expensive resources.
/**
*When getConnection method have some exceptions.
*Test case will throw it ,then continue to excute the test method.
*/
@BeforeClass
public static void setUpBeforeClass() throws Exception{
con = ConDB.getConnection();//create connection
}
@AfterClass
public static void tearDownAfterClass() throws Exception{
con.close();//close connection
}
The most popular type of coverage:
1: The basic statement coverage to ensure that every statement is executed.
2: Decision coverage to ensure every decision is executed.
3: The condition coverage to ensure that each conditions are covered by the true and false (conditional statements, if while).
4: The path coverage to ensure that every path is executed.
Summary:
1: The coverage of data only represent what you test, These Can not represent the test code is a good unit test.
2: The path coverage > condition coverage > basic statement coverage.
3: Write test cases do not blind pursuit of code coverage. 80% time should be used in the important code.
int foo(int a, int b) {
return a / b;
}
TestCase
a=10 , b=5, Coverage=100%.
In fact we ignore one case when b=0, it will throw exception.
public testDemo(String a , String b) {
if (a > b) {
…
}
if (b>c) {
…
} else {
throw new Exception(……);
}
}
When we want write a test case for this method we need go through these three branches. So we need write four test method.
We need to test every important branches.
**
Class Calculator need test:
package de.com.medavis.cn.junit;
public class Calculator {
private static int result = 0;
public void add(int a, int b) {
result = a + b;
}
public void divide(int n) {
result = result / n;
}
public void square(int n) {
result = n * n;
}
//This method just for the timeout test case
public void loopAdd(int n) {
while(true) {
result = result + n;
}
}
public void multiply(int n) {
}
public void clear() {
result = 0;
}
public int getResult() {
return result;
}
}
This is test class:
public class TestCalculator {
Calculator ca = new Calculator();
@Before
public void setUp() throws Exception {
ca.clear();
}
@After
public void tearDown() throws Exception {
}
@Test
public void testAdd() {
Calculator ca = new Calculator();
ca.add(10, 50);
assertEquals(60, ca.getResult(), 0);
}
@Ignore("Multiply() Not yet implemented")
@Test
public void testMultiply() {
}
@Test
public void testDivide() {
ca.add(8, 6);
ca.divide(2);
assertEquals(7, ca.getResult());
}
@Test(expected = ArithmeticException.class)
public void testDivideByZero() {
ca.add(8, 6);
ca.divide(0);
assertEquals(7, ca.getResult());
}
/**
*When this method excute time>1000(ms)?
*This test case is failed?
*/
@Test(timeout = 1000)
public void testLoopToAdd() {
ca.loopAdd(10);
}
}
1: Import package
You need to add an important package “org.junit.Assert.*”.
2: The statement of test method
In the test class, not every method are used to test, you must use the “@” to clear what is the test method.
3: A simple test case
You need use @Test labeling, to show that this is a test method. There are requirements for the method statement: the return value must be void,
and should not have any parameters.
4: Runner
How Junit to run this test case?
Junit have a lot of runners , Each runner has its own special function.
import org.junit.internal.runners.TestClassRunner;
import org.junit.runner.RunWith;
//Default Runner is TestClassRunner
public class CalculatorTest ...{
...
}
//The code above the same function
@RunWith(TestClassRunner.class)
public class CalculatorTest ...{
...
}
Now use a parameter runner:
There are many special values of parameters, or that the arguments is divided into many regions, we can use this runner.
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class TestSquare {
private Calculator calculator = new Calculator();
private int param;
private int result;
@Parameters
public static Collection data() {
return Arrays.asList(new Object[][] { {2, 4}, {0, 0}, {-3, 9},});
}
public TestSquare(int param, int result) {
this.param = param;
this.result = result;
}
@Test
public void test() {
calculator.square(param);
assertEquals(result, calculator.getResult());
}
}
How to write the test cases for the complex method?
Example:
public static void initRandomMessageList(String parameter) {
Global.randomDictMap = new HashMap<String, List<String>>();
//This is a public method
readfile(parameter);
//This is a public method
loadTemplate();
Global.randomDictMap.clear();
}
There are some operation s of methods , as long as the test results of it, there is no need to return a value.
1:we need have test cases for the method readfile() and the method loadTemplate.
2:we need have test cases for this initRandomMessageList():
@Test
public void testInitRandomMessageList(){
MessageUtil.initRandomMessageList("parameter");
Assert.assertTrue(Global.randomDictMap.isEmpty());
Use stub and mock to test complex logic test.
What is stub and mock?
The same point: We usually focused on the test object function and behavior, mock or stub can replace the secondary object .
Different points: 1:stub has an explicit class, mock is usually the mock toolkit such as easymock to implicit implementation.
2:Mock usually rarely consider multiplexing, each mock object through the “just enough” is to follow the principles, generally applies only to the current test method.
And stub is generally more convenient to reuse, especially some common stub, such as JDBC connection etc..
3:For mock, we expect the method has not been called, looking forward to the appropriate parameters, the number to call to verify,
and expected in the testing process and after the end of the test. But for stub, usually do not pay attention to this.
This is a demo to the difference between stub and mock.
public interface UserService {
User query(int userId);
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
public User query(int userId) {
return userDao.getById(userId);
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
public interface UserDao {
User getById(int userId);
}
public class UserDaoStub implements UserDao {
public User getById(int id) {
User user = new User();
user.setId(id);
user.setUsername("admin");
user.setPassword("123");
user.setNickname("amdin");
return user;
}
}
public class TestStub {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testQuery() {
UserServiceImpl service = new UserServiceImpl();
UserDao userDao = new UserDaoStub();
service.setUserDao(userDao);
User user = service.query(1001);
assertEquals(user.getId(), 1001);
}
}
public class TestMock {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testQuery() {
UserDao dao = EasyMock.createMock(UserDao.class);
User user = new User(1,"admin","123","admin");
EasyMock.expect(dao.getById(1001)).andReturn(user);
EasyMock.replay(dao);
UserServiceImpl service = new UserServiceImpl();
service.setUserDao(dao);
User tu = service.query(1001);
assertEquals(tu.getId(),user.getId());
assertEquals(tu.getUsername(),user.getUsername());
assertEquals(tu.getPassword(),user.getPassword());
assertEquals(tu.getNickname(),user.getNickname());
EasyMock.verify(dao);
}
}
2.3.Write I/O test case
This is a demo to test file loading.
/**
*Two methods to test this template load
*/
@Test
public void testLoadTemplateFile() {
String existPath = "ADTTemplate.txt";
String messageTemplateContent = null;
try {
messageTemplateContent = MessageTemplateUtil.loadTemplateFile(existPath);
} catch(IOException e) {
fail();
}
assertNotNull(messageTemplateContent);
}
@Test(expected = IOException.class)
public void testLoadTemplateFileNotExistPath() throws IOException {
//This path is not exist, we can catch IOException
String notExistPath = "ADTTemplateNotExistPath.txt";
MessageTemplateUtil.loadTemplateFile(notExistPath);
}
2.4.Write queue test case
Sometimes we need to test the queue. Now This is a demo to show how to test?
This is a ServerSiteWorkStub to simulate a consumer, But the this is a asynchronous transmission and junit don not support this case,
we need use QueueBrower to avoid this asynchronous transmission.
Public class ServerSiteWorkerStub implements MessageListener {
private ClientToServerJob clientToServerJob;
@Override
public void onMessage(Message message) {
try{
ObjectMessage objectMessage = (ObjectMessage)message;
clientToServerJob = (ClientToServerJob)objectMessage.getObject();
this.setClientToServerJob(clientToServerJob);
} catch(Exception e) {
TestToolLogger.writeBackendLog(e1, EnumLogLevel.ERROR, Global.Log.SERVICE_TYPE_GENERAL);
}
}
public ClientToServerJob getClientToServerJob() {
return clientToServerJob;
}
public void setClientToServerJob(ClientToServerJob clientToServerJob) {
this.clientToServerJob = clientToServerJob;
}
}
QueueBrowser:
public class QueueBrowserDemo implements QueueBrowser {
private ClientToServerJob clientToServerJob;
private TransportConfiguration transportConfiguration;
private ConnectionFactory connectionFactory;
private Connection consumerQueueConnection;
private Session session;
private void initSession() throws JMSException {
transportConfiguration = new TransportConfiguration(NettyConnectorFactory.class.getName());
connectionFactory = (ConnectionFactory)HornetQJMSClient.createConnectionFactoryWithoutHA(JMSFactoryType.CF, transportConfiguration);
consumerQueueConnection = connectionFactory.createConnection();
session = consumerQueueConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
}
@Override
public Queue getQueue() throws JMSException {
Queue destination = session.createQueue(GlobalVariable.QUEUE_CLIENT_TO_SERVER_QUEUE);
return destination;
}
@Override
public String getMessageSelector() throws JMSException {
Enumeration> enumeration = getEnumeration();
while(enumeration.hasMoreElements()) {
ObjectMessage objectMessage = (ObjectMessage)enumeration.nextElement();
clientToServerJob = (ClientToServerJob)objectMessage.getObject();
this.setClientToServerJob(clientToServerJob);
}
close();
return clientToServerJob.getTableName();
}
@Override
public Enumeration getEnumeration() throws JMSException {
initSession();
QueueBrowser browser = session.createBrowser(getQueue());
Enumeration> enumeration = browser.getEnumeration();
return enumeration;
}
@Override
public void close() throws JMSException {
session.close();
}
public ClientToServerJob getClientToServerJob() {
return clientToServerJob;
}
public void setClientToServerJob(ClientToServerJob clientToServerJob) {
this.clientToServerJob = clientToServerJob;
}
}
public class TestClientToServerQueue {
private ClientToServerQueue clientToServer;
private ClientToServerJob clientToServerJob;
private ClientToServerJob clientToServerJobCompare;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
}
@Before
public void setUp() throws Exception {
EnvironmentVariable.initialise();
// First to initialize hornetQ
QueueManager.initialise();
clientToServer = new ClientToServerQueue();
clientToServerJob = new ClientToServerJob();
clientToServerJob.setTableName("Test");
clientToServerJob.setSiteID("RIS-A");
clientToServer.pushClientToServerJob(clientToServerJob);
}
@After
public void tearDown() throws Exception {
}
@Test
public void TestQueue() {
QueueBrowserDemo queue = new QueueBrowserDemo();
try {
queue.getMessageSelector();
} catch(JMSException e) {
fail();
}
clientToServerJobCompare = queue.getClientToServerJob();
Assert.assertEquals(clientToServerJob.getTableName(), clientToServerJobCompare.getTableName());
}
}