简体   繁体   中英

Junit: Separation of test cases

This is more of a junit logic question than anything else.

The scenario I have is below:

I have a standalone java application that consumes data from a web service and saves data from that service on the client(where the application is running) machine.

This data is saved in the form of XML and is then read by another application to render that content on a thick client UI.

The flow is depicted in the diagram below:

在此处输入图片说明

I wish to write unit tests for this flow but I fail to understand how to unit test the logic I have for consuming the webservices and then verifying that what was saved on the client's machine is correct.

Another piece of the puzzle is how to verify(unit test) what was rendered on the UI with the XML that was saved on the client's machine.

I understand that each junit has to be as small as possible and should test the underlying functionality independently.

The application will be hosted in a continuous integration environment like Hudson and would probably not provide rights to the application to write anything to that machine. This complicates matters further.

Any help would be appreciated.

In the above scenario, for the sake of simplicity, I have shown the standalone application and the thick client as separate but essentially they can be one application.

The thick client that reads the XML data to display is coded in javafx.

You didn't specify what will serve as UI? Will this be a GUI app or a web one ? That has some impact on the general approach but just to give some hints I will share my experience. I did a lot gui apps using swing and that will be my baseline.

So the trend is to make your GUI as thin as possible and put there logic that is only responsible for displaying components. To achieve that you can use well known design patterns like Presentation Model( http://martinfowler.com/eaaDev/PresentationModel.html ) or Passive View ( http://martinfowler.com/eaaDev/PassiveScreen.html )

Then you start testing your business logic. General guidelines are:

  • you don't use external resources ( Files, DBs etc.) instead you replace them with Fakes or Mocks which provide some fixed input for your tests.
  • You test only the behaviour of the class not the behaviour of the dependencies
  • Try to avoid complex input
  • Using mentioned patterns you can mock your view layer and test only whether certain methods of view where called in synchrronization layer.

I hope I helped. If not let me know if something is a bit fuzzy. It would be also good if you could give an example how would you like to tackle the problem and what are you concerns regarding your approach.

@Example

I don't have experience with JavaFx so I will try to show how I would do it in Swing. This example assumes that you know what are mocks and what they are for.

First let's figure out what is the most important functionality in thin client. I would go for something like that. User opens xml file and app displays it in some form. (The form is not important it could be tree it could be a table or a grid whatever. Since it is view I don't care right now)

The basic scenario would be user selects a file, app opens that file and parses it and then the result is displayed. Let's call this scenario "Open Results".

First test:

class OpenResultsShould{
    @Test
    public void loadResults() {
        Data fake = mock (Data.class);
        ViewInterface view = mock(ViewInterface.class); // mocking view
        when(view.getFilename()).thenReturn("file.xml"); // we specify that when getFileName() method of view mock will be called "file.xml" string will be returned.

        ApplicationModelInterface appModel = mock(ApplicationModelInterface.class); // mocking app model
        when(appModel.getDataForView()).thenReturn(fake);

    OpenResultsAction openResults = new OpenResultsAction( view, appModel );

        openResults.actionPerformed(new ActionEvent());

        verify(view).getFileName();           // checks that view.getFileName was called within actionPerdormed()
        verify(appModel).load("file.xml");    // check that appModel.load( ) with file.xml as parameter was called within actionPerformed()
        verify(appModel).getDataForView();    // similar to above
        verify(view).loadDataFromModel( fake ); // finally I check if loadDataFromModel on view was called.
    }
}

The purpose of this test is to check if OpenResultsAction will do the job. Here we are not testing what is parsed and whether gui has the correct data. We tests if certain methods on certain objects were called. This tests also specifies a contract between action class, view and applicationModel. This is done via interfaces. So you can later provide concrete implementation which will be tested in next step. Then I would provide implementation which I will skip to make this example as short as possible.

So what is next. Since gui is not going to be tested at all I would go for ApplicationModel tests. In first test we specified that applicationModelInterface should have method load(String filename); And we will test if concrete implementation of it.

class ApplicationModelShould{
    @Test
    public void loadModelFromFile() {
        XMLDocument xml = new XMLDocumentFake();
        XMLFileLoader xFileLoader = mock(XMLFileLoader.class);
        when(xFileLoader.load("file.xml").thenReturn( xml );
        ApplicationModelInterface appModel = new ConcreteApplicationModel( new FileLoaderFake() );

        appModel.load("plik.xml"); // it should call xFileLoader and then parse returned xml document.
        doReturn(xml).when(xFileLoader).load("plik.xml"); // verifies if xFileLoader returned xml when appModel.load called it's load method. 
       Data expectedResult = populateExpectedResults();
       assertEquals( appModel.getDataForView().equals( expectedResult ) );
    }
}

What is XMLDocument ? It stores content of xml file. It can be represented as vector of file lines. Our AppModelLoader will parse it to objects. XFileLoader is another layer which allows me to get rid of file operations in my unit tests. Here it is mocked but in real app it should be replaced with somethind that will read in xml file and return XMLDocument. Data is a class that would be used to store parsed data. If content of xml is "<Person><name>Tom</name><age>34</age></Person> then Data would look like:

class Data{
    private Person person;
    Data(Person person){ this.person = person; }
    ...
};

class Person{
    private String name;
    private int age;
    .... setters, getters and constructors
}

And that is basically it. Of course more tests are required like for example if view.getFileName() will return empty String in OpenResultsAction ( user hit cancel on JFileChooser ) then I need to verify if nothing else was called. If I would have all the classes tested then I would write GUI part and combine it.

Let me know if that makes sense.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM