tags:

views:

957

answers:

6

Hi all,

I'm somewhat new to unit testing. One thing (for now) about proper testing confuses me.

For instance, how do you test a main method if it has no state and only console output? Like this, where myServer methods & state are private?

 public static void main(String[] args)
 {
     Server myServer = new Server()
     if(myServer.start())
          System.out.println("started");
     else
          System.out.println("failed"); 
 }

I don't want to have to change my code and expose my Server methods & state to make them public.

Note, I'm not asking how to test myServer.start(), I'm asking how to test main() itself.

Please let me know.

Thanks guys, jbu

+3  A: 

You (generally) don't unit test main() methods. In your case, you unit test the Server class (and probably others) ie testing it's public interface.

cletus
ok, but my Server Class only has the public constructor, and all of its methods are private since the Server takes care of itself and its methods aren't called by any outside classes. So then, would I only need to test the constructor? But if I do, there's no state to test, it's all private.
jbu
If your Server is just a black box with a public constructor then it's nothing more than a main() method in another form. Surely your Server class ses other classes that do have public interfaces? All your code isn't in one class is it? Rethink your black box Server.
cletus
"But if I do, there's no state to test, it's all private" is basically a poor, untestable design. Design for testability is hard. Everything private is a bad choice.
S.Lott
@S. Lott: "... Everything private is a bad choice.". I disagree: Most of the code I write is private and yet I'm usually able to get very good code coverage by only writing tests against the public interface.
Andreas Huber
@Andreas: the fact that you have a public interface means you're agreeing with S.Lott (and me for that matter). He's saying "everything private is bad" not "anything private is bad".
cletus
@cletus: If I interpret the comments by S.Lott *only* in the very narrow context of this question, then yes, I would agree. However, IMO his last two sentences have a very universal tone to them, which seems to go beyond this question.
Andreas Huber
A: 

If you would like to have this case - and many others - cleared up, may I suggest the Pragmatic Unit Testing series?

Mark Brittingham
A: 

Generally speaking, main() is not a "unit", and would be difficult to devise a unit test for. By convention, main() should return an int to indicate error state. Perhaps this is all you need?

Daniel Paull
+1  A: 

Since your method has information flow only in one direction - to the collaborator objects (in this example: the Server object), and doesn't otherwise return values or throw exceptions, you could supply a mock collaborator object (a mock Server in your example) that would have configured expectations about the method calls and their parameters received from your main() method.

You can use one of several mock object frameworks for that, e.g. Easymock.

During unit testing, the mock object would throw a proper exception when calls made on it by the main() method wouldn't match the configured expectations.

Of course, in order to supply a mock object, you'd have to modify the method a bit with regards to object instantiation - make the "Server" an interface, not a concrete class, and use a framework (like Spring) instead of manually instantiating your Server object, e.g. instead of:

Server myServer = new Server();

you'd have this:

Server myServer = applicationContext.getBean("server");

You'd create separate Spring application context configurations for production application and for unit tests, and the difference would be that application context for production would be configured using an XML file, while for unit tests would be programmatically created and populated with mock objects from your test classes' setUp() methods.

Aleksander Adamowski
A: 

wrap the code in a non-main method and test that ;-)

seriously, what everyone else said

but if you must...make a unit test run the console app in a DOS shell (see the Process object) with STDOUT redirected to a file, then read the contents of the output file to see if it says "started" or "failed", whichever you expect

Steven A. Lowe
+7  A: 

Main methods are supposed to be very thin and just get the ball rolling. Hence generally not tested. e.g. a .net Winforms app Main method may contain

static void Main()
{
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(false);
  Application.Run(new MainWindow());
}

If your main method is doing a lot of stuff, consider extracting a method (to a new class if need be). e.g. as shown below... (don't have a JavaIDE handy.. the foll .net code should be easy to translate)

static void Main()
{
  new MainApp().Run();
}

Now if Run can be tested.. Main too is covered. Lets look at MainApp. Refactored the 2 tasks of starting the server and logging the result as 2 methods.

public class MainApp
{
  private Server m_Server;
  public MainApp():this(new Server())
  {}
  public MainApp(Server s)
  {  m_Server = s;      }

  public void Run()
  {  Log(LaunchServer());  }

  private string LaunchServer()
  {  return (m_Server.start() ? "started" : "failed");        }

  protected virtual void Log(string sMessage)
  {  System.Console.WriteLine(sMessage);        }
}

Now lets look at the test code... I'll use the "subclass and override" trick to cache the result as shown below. (You could also use Mocks.. as per taste)

public class FakeApp : MainApp
{
  private string sLastLoggedMessage;

  public FakeApp(Server s) : base(s) { }

  public string getLastLoggedMessage()
  {  return sLastLoggedMessage;        }

  protected override void Log(string sMessage)
  {  sLastLoggedMessage = sMessage;        }
}

the test is now trivial

[TestFixture]
public class TestMainApp
{
  [Test]
  public void TestRun()
  {
    Server s = new Server();
    FakeApp f = new FakeApp(s);

    f.Run();

    Assert.AreEqual("started", f.getLastLoggedMessage());
  }
}

if you can't force Server.start() to succeed or fail as per your desire.. You can create a FakeServer.. subclass and override start() to return fixed values.

If a method does something, it can be tested. If it does not, it should cease to exist. Refactor it away. HTH

Gishu