问题
I have Thread which runs while the program runs and polls a queue and check whether it has object and if yes then it calls method on the object
Here is the code :
while(isRunning){
synchronized (loginQueue) {
if(loginQueue.peek() != null) {
Object[] loginObjectWithConnection = loginQueue.poll();
tryLogin(loginObjectWithConnection);
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Here is the tryLogin method
private void tryLogin(Object[] loginObjectWithConnection) {
LoginPacket packet = (LoginPacket)loginObjectWithConnection[0];
Connection connection = (Connection)loginObjectWithConnection[1];
try {
if(playerDataService.arevalidCredentials(packet.getUserName(), packet.getPassword())) {
if(!playerDataService.isPlayerBanned(packet.getUserName())){ //Player exists in the system
communicationService.sendTCP(connection, packetFactory.makeLoginFailurePacket(StringConstants.PLAYER_BANNED));
} else{ //Player is not banned
}
} else { // Player does not exist
communicationService.sendTCP(connection, packetFactory.makeLoginFailurePacket(StringConstants.INVALID_USER));
}
} catch (SQLException e) {
communicationService.sendTCP(connection, packetFactory.makeLoginFailurePacket(StringConstants.SERVER_ERROR));
e.printStackTrace();
}
}
Now my problem is that I want to test the invocations of these service methods but when I run the unit tests they won't work as it takes time to reach the point of tryLogin and till then JUnit fails. I tries using Thread.sleep() but I know it is not the right way to do it as it fails sometimes and pass sometimes.
Here is what I have in my Unit Test
@Test
public void theExpectedMessageShouldBeSentIfUserIsBanned() throws InterruptedException, SQLException {
//Arrange
when(moqLoginQueue.peek()).thenReturn(object);
when(moqLoginQueue.poll()).thenReturn(object);
LoginFailurePacket packet = new LoginFailurePacket(StringConstants.PLAYER_BANNED);
when(moqPacketFactory.makeLoginFailurePacket(StringConstants.PLAYER_BANNED)).thenReturn(packet);
when(moqPlayerDataService.arevalidCredentials(anyString(), anyString())).thenReturn(true);
when(moqPlayerDataService.isPlayerBanned(anyString())).thenReturn(true);
//Act
loginManager.start();
Thread.sleep(10); //Dirty hack -.-
//Assert
verify(moqCommunicationService).sendTCP(any(Connection.class), eq(packet));
}
回答1:
The system is untestable in the current form: among good test qualities there are:
- easy for other programmers to undertsand
- hard for other programmers to break
- fast to run
The piece of logic you want to test is LoginManager.tryLogin, which is private in your snippet. If you want to publicly document it (tests are kind of documentation: they state how the system should behave), it has to be public.
I suggest to move all that logic to a method in a new class: Authentication.attempt() (I suggest an immutable object and a method that does not take any argument - someone says the optimal number of arguments in OO design is zero).
Now that testing is feasible, I also think you should get rid of all that code in LoginManager.start(): simply use an ExecutorService and submit authentication attempts - this way you'll have a faster program and less code to test, because the hard (and tricky) part is managed by Java.
来源:https://stackoverflow.com/questions/33426494/how-to-unit-test-thread-which-takes-time-to-perform-action