Task : Display stock lists (UC2)

Use case screenshots

config.properties in src/main/resources
app.base.dir=C:/tutorial-trading

uc-1-display-stock-lists (3)

 

uc-1-display-stock-lists (1)

uc-1-display-stock-lists (2)

Use case git URL
https://github.com/ebundy/jfreechart-bootstrap/tree/master/uc2

 

UC Analysis

The main user story for this UC
When I run my application, I want to see the stock list names.

The sequence diagram for the main user story
Displaying stock lists should be served by a service class dedicated to list management (list loading, retrieving, updating, etc…) Call it LocalListService class.
The « Local » term insists on the fact that lists handled by this service are on the locale post of the user.
Naming is important. By a simple read, it allows to give a precise meaning and context to our components.
getNamesOfLists() will be the method for retrieving stock lists name. Using the get prefix in the method naming is misleading when we refers models or DTO class and that our method is not a simple getter().
But for a service class, getXXX() should not be confused with a notation JavaBean.

uc2-diag-activity-main-story

Test and implementation for our UC

Allow to distinct data for tests from data for application

When you use an application, it always more practical when the application developers allow you to choose where the application or data application must be located.
So, we will allow it like we have seen here http://myjavaadventures.com/blog/2015/11/23/creer-son-application-de-trading-en-java-410-tache-identifier-les-services-a-creer-et-ecrire-des-implementations-vides-de-ces-services/
When we deal with unit tests, we have a near enough problematic.
Indeed, data used by a test should not be mixed with data used by the application. In our case, the list data for unit testing should be in a dedicated location. So, we will take advantage of Maven in order to perform it. We will create a new config.properties file in src/test/resources where we define the base application path for our tests.
We can position this path in a directory in the test resources, which seems to be a very suitable place to store test data.

app.base.dir=${basedir}/src/test/resources/tutorial-trading-test

The path contains the Maven variable ${basedir}. To allow that this variable to be translated in an effective value, we configure our maven pom.xml in order that the Maven process-test-resources phase filter our config.properties in our test resources directory :

	 <build>
		<testResources>
			<testResource>
				<directory>src/test/resources</directory>
				<filtering>true</filtering>
			</testResource>
		</testResources>		
	</build>

Wi will use two lists for our tests :

${basedir}/src/test/resources/tutorial-trading-test/liste1.csv

with for content file no data.

ISIN;name;ticker
BE0003470755;Solvay;SOLB
FR0000045072;Credit Agricole;ACA
FR0000073272;Safran;SAF


${basedir}/src/test/resources/tutorial-trading-test/liste2.csv

with for content file no data.

ISIN;name;ticker
US88579Y1010;3M Co.;MMM
US0258161092;American Express Co.;AXP
US0378331005;Apple Inc.;AAPL
US0970231058;Boeing Co.;BA
US1491231015;Caterpillar Inc.;CAT
US1667641005;Chevron;CVX
US17275R1023;Cisco Systems Inc.;CSCO
US1912161007;Coca-Cola Co.;KO

For this use case, files content is useless because it’s not used. We value it when necessary in next use cases.

We will have a configuration class (ApplicationDirs) which will enable our application to work in any environnement (normal running like test environnement).
In normal running, the config.properties in src/main/resources path will be used.
In test running, the config.properties in src/test/resources path will be used.
So, validate it by a unit test that it’s working in test environnement. If it’s ok in test environnement, it will be ok in normal running if config.properties is present at the proper place.
The Failled cases (file missing or property missing) are hard to test. It forces to use tricks to change the priority resource loading for the config.properties. So, we don’t handle them in this unit test.

ApplicationDirsTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package davidhxxx.teach.jfreecharttuto.util;
 
import org.junit.Test;
 
import junit.framework.Assert;
 
public class ApplicationDirsTest {
 
    @Test
    public void static_bloc_configuration_is_ok() throws Exception {
 
	String appBaseDir = ApplicationDirs.BASE_DIR;
 
	String expectedSubPath = "/src/test/resources/tutorial-trading-test";
	int lengthPath = expectedSubPath.length();
	String actualSubPath = appBaseDir.substring(appBaseDir.length() - lengthPath);
	Assert.assertEquals(expectedSubPath, actualSubPath);
    }
 
 
 
}

ApplicationDirs.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package davidhxxx.teach.jfreecharttuto.util;
 
import java.io.File;
 
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.StringUtils;
 
public class ApplicationDirs {
 
    static {
 
	Configuration config = null;
	try {
	    config = new PropertiesConfiguration("config.properties");
	}
	catch (ConfigurationException e) {
	    throw new IllegalArgumentException("Erreur lors du chargement du fichier de configuration");
	}
 
	String appBaseDir = config.getString("app.base.dir");
	if (StringUtils.isEmpty(appBaseDir)) {
	    throw new IllegalArgumentException("la propriété doit être renseignée dans le fichier config.properties");
	}
 
	File repertoireBase = new File(appBaseDir);
	if (!repertoireBase.exists()) {
	    throw new IllegalArgumentException("le repértoire " + repertoireBase + " n'existe pas");
	}
 
	if (!repertoireBase.isDirectory()) {
	    throw new IllegalArgumentException("le chemin " + repertoireBase + " n'est pas un répertoire");
	}
 
	BASE_DIR = appBaseDir;
    }
 
    public static final String BASE_DIR;
 
}

Remarks on the code :
– We rely on the ApplicationDirs.BASE_DIR constant to know where searching the application data base dir.
Behind the scenes, ApplicationDirs.BASE_DIR is valued from our property app.base.dir in config.properties as soon as the ApplicationDirs class is loaded by the classloader.
– Static bloc allows to initialize once our data base directory.
– Data base directory validity checks are importants because all application data and handling associated will rely on it.
We could go further by checking more things (list directory exists, quotation dir exists, etc…).

We will now write the unit test of the business method.

Build our service with a test acceptance approach

Our acceptance test is simple : I want getNamesOfList of the LocalListService class returns a List with all existing lists in my application. We have defined two of them in src/test/resources… : liste1 and liste2. So, I expect to have them.

LocalListServiceTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package davidhxxx.teach.jfreecharttuto.dataservice;
 
import java.util.Arrays;
import java.util.List;
 
import org.junit.Test;
import org.unitils.reflectionassert.ReflectionAssert;
import org.unitils.reflectionassert.ReflectionComparatorMode;
 
public class LocalListServiceTest {
 
    @Test
    public void getNamesOfLists_returns_list_ordered_by_name() throws Exception {
	List namesOfLists = LocalListService.getInstance().getNamesOfLists();
	List expectedNamesOfLists = Arrays.asList("liste1", "liste2");
	ReflectionAssert.assertReflectionEquals(expectedNamesOfLists, namesOfLists, ReflectionComparatorMode.LENIENT_ORDER);
    }
 
}

Three remarks on the test:

– Our service class should be a singleton.
Why ?
Because it has no sense to be instantiated multiples times by the application since it ‘s a kind of data referential.
Besides, instantiating it multiples times would cost memory for nothing.
At last, multiple instances would prevent the service to be consistent if later, the service provides methods for list modification. Which is very likely in a full trading application.
The projet doesn’t use Spring (a overload with no advantage in our bootstrap application). So, we will code our singleton in the old-fashion way : a getInstance() static method. That’s why our test method calls getInstance() before calling getNamesOfLists().

– Asserting a list with a classic JUnit assertEquals() is depending on elements order . The Unitils Testing API allows to go beyond from this limitation. ReflectionAssert.assertReflectionEquals(x,x) and the ReflectionComparatorMode.LENIENT_ORDER comparator do the job.

We could begin the implementation of the getNamesOfLists() method.

LocalListService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package davidhxxx.teach.jfreecharttuto.dataservice;
 
import java.io.File;
import java.io.FilenameFilter;
 
import java.util.List;
 
import davidhxxx.teach.jfreecharttuto.model.ListOfStockList;
import davidhxxx.teach.jfreecharttuto.util.ApplicationDirs;
 
public class LocalListService {
 
    private static LocalListService instance;
 
    private File listsPath;
 
    private ListOfStockList listOfStockList;
 
    private LocalListService() {
	setListsPath(new File(ApplicationDirs.BASE_DIR + "/list"));
	loadListOfStockListNames();
    }
 
    private void setListsPath(File path) {
	this.listsPath = path;
	listsPath.mkdirs();
    }
 
    public static LocalListService getInstance() {
	if (instance == null)
	    instance = new LocalListService();
 
	return instance;
    }
 
    public List getNamesOfLists() {
	return listOfStockList.getNames();
    }
 
    private void loadListOfStockListNames() {
	listOfStockList = new ListOfStockList();
 
	File[] files = listsPath.listFiles(new FilenameFilter() {
 
	    @Override
	    public boolean accept(File dir, String name) {
		if (name.contains(".csv"))
		    return true;
 
		return false;
	    }
	});
	listOfStockList.addListNames(files);
 
    }
 
}

Remarks on the code :
– We use the Bill Pugh singleton implementation. It’s excellent. More details in this article on singleton creation.
– All data needed (list names) are loaded once in the application life since it’s executed in the private constructor of our singleton class.
– for the moment, we don’t care for content of files with csv extensions in the expected directory (list). If it exists, we use its file simple name to guess the list name.

Using our model classes to store and retrieve data

Data initialization for lists will rely on our model classes ListOfStockList and ListOfStock which will be responsible for data store and retrieval  .
Our service class LocalListService collaborates with a ListOfStockList  instance in order to store list names during the stock lists discovery and init :

listOfStockList.addListNames(files)

In a second time, the LocalListService class collaborates again with ListOfStockList  to retrieve all list names :

public List getNamesOfLists() {
return listOfStockList.getNames();
}

In the same way that we did for LocalListService  implementation, we will have a TDD approach.
Our acceptance test is simple : I want addListNames of the ListOfStockList class uses the Files array param to guess the list names (filename minus the file extension) to create as many as ListOfStock instances as needed with no stocks but a list name.
And to check my requirement is fulfilled, I will assert that listOfStockList.getNames() return a List of String with the expected list names.

ListOfStockListTest

package davidhxxx.teach.jfreecharttuto.model;
 
import java.io.File;
 
import java.util.Arrays;
import java.util.List;
 
import org.junit.Test;
import org.unitils.reflectionassert.ReflectionAssert;
import org.unitils.reflectionassert.ReflectionComparatorMode;
 
public class ListOfStockListTest {
 
    /*
     * tested:
     * - addListNames()
     * - getNames()
     * - isStocksLoaded() -> false case
     */
 
    @Test
    public void addListNames_add_files_less_extension_without_adding_stocks_AND_getNames_return_them() throws Exception {
	ListOfStockList listOfStockList = new ListOfStockList();
	File[] fileNamesWithExtension = new File[2];
	fileNamesWithExtension[0] = new File("listFake1.csv");
	fileNamesWithExtension[1] = new File("listFake2.csv");
	// action
	listOfStockList.addListNames(fileNamesWithExtension);
	// assertion
	List<String> actualNames = listOfStockList.getNames();
	List<String> expectedNames = Arrays.asList("listFake1", "listFake2");
	ReflectionAssert.assertReflectionEquals(expectedNames, actualNames, ReflectionComparatorMode.LENIENT_ORDER);
    }
 
}

ListOfStockList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package davidhxxx.teach.jfreecharttuto.model;
 
import java.io.File;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
public class ListOfStockList {
 
    private Map<String, ListOfStock> listOfStockByListName = new HashMap<String, ListOfStock>();
 
    public void addListNames(File[] filenamesWithExtention) {
 
	for (File filenameWithExtention : filenamesWithExtention) {
	    String fileNameWithoutExtension = filenameWithExtention.getName().replace(".csv", "");
	    listOfStockByListName.put(fileNameWithoutExtension, new ListOfStock());
	}
    }
 
    public List<String> getNames() {
	return new ArrayList<>(listOfStockByListName.keySet());
    }
 
}

Remarks on the code :
– ListOfStockList should be in theory a singleton because there is once ListOfStockList in the application life. But it’s not a very serious problem in this case because LocalListService instantiates once ListOfStockList (in its constructor) .
– Storing the StockList in an inner map (name-ListOfStock

1
2
3
4
5
6
7
8
9
10
package davidhxxx.teach.jfreecharttuto.model;
 
import java.util.Map;
 
public class ListOfStock {
 
     private List<Stock> stocks;
 
 
}

Remarks on the code :
– This class has not been updated from our model class diagram because we don’t need more for our use case.

Graphical components

We have build a simple JFrame with only data needed for our use case.

MainFrame.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package davidhxxx.teach.jfreecharttuto.ui.layout;
 
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
 
import java.util.List;
import java.util.Locale;
 
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToolBar;
import javax.swing.JToolBar.Separator;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
 
import davidhxxx.teach.jfreecharttuto.dataservice.LocalListService;
import davidhxxx.teach.jfreecharttuto.util.WidgetUtil;
 
@SuppressWarnings("serial")
public class MainFrame extends JFrame {
 
    // buttons
    private JComboBox comboListOfStockList;
 
    // containers
    private JSplitPane hSplitPaneGeneral;
 
    private JToolBar toolBar;
 
    private JScrollPane scrollPaneForReferenceChart;
 
    public static void main(String[] args) {
	SwingUtilities.invokeLater(new Runnable() {
 
	    @Override
	    public void run() {
		new MainFrame("trading bootstrap app");
	    }
 
	});
    }
 
    public MainFrame(String name) {
 
	super(name);
	Locale.setDefault(new Locale("fr"));
	UIManager.put("ToolTip.font", new Font("Serif", Font.BOLD, 18));
 
	setVisible(true);
	setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
	hSplitPaneGeneral = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
	hSplitPaneGeneral.setDividerLocation(150);
 
	add(hSplitPaneGeneral, BorderLayout.CENTER);
 
	initToolBar();
	hSplitPaneGeneral.setLeftComponent(toolBar);
	hSplitPaneGeneral.setRightComponent(scrollPaneForReferenceChart);
	setExtendedState(Frame.MAXIMIZED_BOTH);
    }
 
    private void initToolBar() {
	toolBar = new JToolBar("Still draggable");
	toolBar.setOrientation(SwingConstants.VERTICAL);
 
	comboListOfStockList = createComboListOfListOfStock();
	toolBar.add(comboListOfStockList);
	createSperationMoyenne(toolBar);
    }
 
    private void createSperationMoyenne(JToolBar toolBar) {
	toolBar.add(new Separator(new Dimension(1, 30)));
    }
 
    private JComboBox createComboListOfListOfStock() {
	final JComboBox comboListOfStockList = new JComboBox() {
	    @Override
	    public Dimension getMaximumSize() {
		return getPreferredSize();
	    }
	};
 
	WidgetUtil.setUniqueSizeAndCenter(comboListOfStockList, 120, 30);
	List listOfListOfStockName = LocalListService.getInstance().getNamesOfLists();
	for (String listOfStockName : listOfListOfStockName) {
	    comboListOfStockList.addItem(listOfStockName);
	}
 
	((JLabel) comboListOfStockList.getRenderer()).setHorizontalAlignment(SwingConstants.CENTER);
 
	return comboListOfStockList;
    }
 
}
Ce contenu a été publié dans trading. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *