aboutsummaryrefslogtreecommitdiffstats
blob: 3feacf67a9004b1896df3243c70b1db08301588d (plain)
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
		"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter id="testing.greenpages">
	<title>Testing @greenpages@</title>

	<section id="testing.greenpages.introduction">
		<title>Introduction</title>
		<para>
			Testing is one of the most important aspects of software development. Without testing it would be difficult
			to determine if a piece of code worked properly, changes would have undetected consequences, and the quality
			of products would generally be lower.
		</para>
		<para>
			There are two major categories of testing generally recognised today: unit testing 
			and integration testing. In the context of the
			@greenpages@ application, <emphasis>unit testing</emphasis> means testing a single class in isolation from other application code.
			This type of testing does not change at all when developing for @webserv@.
		</para>
		<para>
			In our application <emphasis>integration testing</emphasis> means testing an application or
			portion of an application with other code. This kind of testing does look a bit different when developing
			for @webserv@. In most cases @webserv@ applications are made up of small bundles that consume services through the
			OSGi registry. In the following steps a single bundle and the entire @greenpages@
			application will be integration tested outside the container.
		</para>
	</section>

	<section id="testing.greenpages.single.bundle">
		<title>Single bundle integration testing</title>
		<para>
			One of the most common forms of integration testing is ensuring that the object relational mapping in an
			application is working properly. This kind of testing typically uses a data access object to retrieve data
			from a live database. In this step a test case for the <classname>JpaDirectory</classname>
			class is created.
		</para>
		
		<para>
			Before proceeding, stop any @webserv@ instance that was previously running.
		</para>

		<para>
			Open the <classname>greenpages.jpa.JpaDirectorySpringContextTests</classname> class in the
			<filename>src/test/java</filename> source folder of the <literal>greenpages.jpa</literal> project. This
			class contains a method that uses <emphasis>JUnit</emphasis> to test that a search completes 
			correctly. Rather than instantiate
			this class directly in the test, the Spring Test Framework is used to instantiate and inject a
			<classname>JpaDirectory</classname> based on the <literal>META-INF/spring/module-context.xml</literal> file.
		</para>
		
		<para>
			Add Spring Test Framework declarations to the test class. These declarations run the test with the
			<classname>SpringJunit4ClassRunner</classname> and configure the test with the
			<literal>classpath:/META-INF/spring/module-context.xml</literal> file:
<programlisting language="java"><![CDATA[@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/META-INF/spring/module-context.xml")
@TestExecutionListeners(value = DependencyInjectionTestExecutionListener.class)
public class JpaDirectorySpringContextTests {
…]]>
</programlisting>
			Use Eclipse to suggest the necessary imports until there are no errors.
		</para>
		
		<para>
			When this configuration is complete, click on the <emphasis>Run</emphasis> drop-down menu and select
			<emphasis>Run Configurations…</emphasis>. In the the dialog that opens select
			<menuchoice><guimenu>JUnit</guimenu><guimenuitem>JpaDirectorySpringContextTests</guimenuitem></menuchoice>
			and press <emphasis>Run</emphasis>.
		<mediaobject>
			<imageobject role="fo">
				<imagedata fileref="images/testing-greenpages/jpa-test-runner.png" format="PNG" align="center" width="18cm"/>
			</imageobject>
			<imageobject role="html">
				<imagedata fileref="images/testing-greenpages/jpa-test-runner.png" format="PNG" align="center"/>
			</imageobject>
		</mediaobject>
		</para>

		<para>
			This test run will fail because there is no <interfacename>DataSource</interfacename> bean to be injected;
			it is typically sourced from the OSGi service registry at runtime:
<programlisting><![CDATA[Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: 
    No bean named 'dataSource' is defined]]>
</programlisting>
			The next step will correct this error.
		</para>
	</section>

	<section id="testing.greenpages.contributing.osgi">
		<title>Contributing OSGi sourced dependencies</title>
		<para>
			In the previous step the <classname>JpaDirectorySpringContextTests</classname> test failed because it did
			not have a <interfacename>DataSource</interfacename> to be injected. In this step, you will instantiate an
			<quote>in-process</quote> database for testing and populate it with data.
		</para>

		<para>
			Open the <filename>test-context.xml</filename> file in the
			<literal>src/test/resources</literal> <filename>META-INF/spring</filename> folder. 
			In this file, define two beans; a
			<interfacename>DataSource</interfacename> and a <classname>TestDataPopulator</classname>. 
			These two beans
			will provide a test <interfacename>DataSource</interfacename> complete with test data.
<programlisting language="xml"><![CDATA[    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        p:driverClassName="org.h2.Driver" p:url="jdbc:h2:.~/greenpages-db/greenpages"
        p:username="greenpages" p:password="pass" init-method="createDataSource"
        destroy-method="close" />

    <bean class="greenpages.jpa.TestDataPopulator" init-method="populate">
        <constructor-arg ref="dataSource" />
        <constructor-arg value="file:../../db/db.sql" />
    </bean>]]>
</programlisting>
		</para>
		
		<para>
			Open the <classname>JpaDirectorySpringContextTests</classname> class and update the
			<interfacename>ContextConfiguration</interfacename> annotation to point at both the
			<filename>module-context.xml</filename> file and the <filename>test-context.xml</filename> file:
<programlisting language="java"><![CDATA[@ContextConfiguration(locations = {
        "classpath:/META-INF/spring/module-context.xml",
        "classpath:/META-INF/spring/test-context.xml" })]]>
</programlisting>
		</para>
		
		<para>
			Once again use the <literal>JpaDirectorySpringContextTests</literal> JUnit profile to run the test class.
			Now that there is a <interfacename>DataSource</interfacename> being contributed, the test will pass.
			If the test fails, check that the H2 database server is still running in the background.
		</para>
		
		<para>
			Proceed to the next step.
		</para>
	</section>

	<section id="testing.greenpages.application">
		<title>Multi bundle integration testing</title>
		<para>
			Earlier a single bundle was integration tested by providing a test implementation of its
			<interfacename>DataSource</interfacename> dependency.
			When integration testing it is often a good idea to
			test the entire application outside of the container.
			In this step you will create a test case for the
			entire @greenpages@ application starting with the <classname>@greenpages@Controller</classname> class
			and descending all the way to a database.
			It would be sensible to create this in a separate test bundle
			but as one of the bundles involved here is a web bundle the tests will have to go in there.
		</para>

		<para>
			Since this project will be testing the @greenpages@ application as a whole, it needs to depend on the bundles
			that make up the application.
			Open the <filename>pom.xml</filename> file for the <literal>greenpages.web</literal>
			project and add a dependency declaration for the <literal>greenpages.jpa</literal> bundle:
<programlisting language="xml"><![CDATA[        <dependency>
            <groupId>com.springsource.dmserver</groupId>
            <artifactId>greenpages.jpa</artifactId>
            <version>${project.version}</version>
            <scope>test</scope>
        </dependency>]]>
</programlisting>
			noting that the scope is <literal>test</literal>.
		</para>

		<para>
			Open the <classname>@greenpages@SpringContextTests</classname> class
			and add the Spring Test Framework declarations.
			These declarations should run the test with the
			<classname>SpringJunit4ClassRunner</classname> and configure the test with the
			<literal>classpath*:/META-INF/spring/module-context.xml</literal>,
			<literal>file:src/main/webapp/WEB-INF/greenpages-servlet.xml</literal> and
			<literal>classpath:/META-INF/spring/test-context.xml</literal> files. Note the use of
			<literal>classpath*:</literal> with respect to the <literal>module-context.xml</literal> path.
			This will
			cause Spring to look for files that match that path in all of the bundles on the classpath meaning that all
			the application beans will be instantiated.
			Also, as we do not want the <literal>WEB-INF</literal> folder
			on the classpath we must reference the servlet context for @greenpages@ with a full file path:
<programlisting language="java"><![CDATA[@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath*:/META-INF/spring/module-context.xml",
        "file:src/main/webapp/WEB-INF/greenpages-servlet.xml",
        "classpath:/META-INF/spring/test-context.xml" })
@TestExecutionListeners(value = DependencyInjectionTestExecutionListener.class)
public class @greenpages@SpringContextTests {
…]]>
</programlisting>
			It may be necessary to click on Update <literal>MANIFEST.MF</literal> on the template overview pane
			and <emphasis>Update Dependencies</emphasis> from the <emphasis>Maven</emphasis> menu,
			before Eclipse will suggest appropriate imports here.
		</para>

		<para>
			When this configuration is complete, click on the <emphasis>Run</emphasis> drop-down and select
			<emphasis>Run Configurations…</emphasis>.
			In the the dialog that opens select
			<menuchoice><guimenu>JUnit</guimenu><guimenuitem>@greenpages@SpringContextTests</guimenuitem></menuchoice>
			and press <emphasis>Run</emphasis>;
			<mediaobject>
				<imageobject role="fo">
					<imagedata fileref="images/testing-greenpages/integration-test-runner.png" format="PNG" align="center" width="18cm"/>
				</imageobject>
				<imageobject role="html">
					<imagedata fileref="images/testing-greenpages/integration-test-runner.png" format="PNG" align="center"/>
				</imageobject>
			</mediaobject>
		</para>
		
		<para>
			When this test is run, Spring creates an <interfacename>ApplicationContext</interfacename> that is built
			from the <filename>module-context.xml</filename> configuration files from all of the bundles. 
			Because of
			this all of the internal dependencies are satisfied by the beans created directly by the bundles.
		</para>
		
		<para>
			The test should pass. If it doesn't, try the usual Eclipse dance steps of opening and closing the project and doing a clean rebuild to clear the problem.
	    </para>
		
		<para>
			There are warnings output by this test concerning <literal>log4j</literal>:
<programlisting>log4j:WARN No appenders could be found for logger
                                (org.springframework.test.context.junit4.SpringJUnit4ClassRunner).
log4j:WARN Please initialize the log4j system properly.
</programlisting>
			These warnings are benign, and do not influence the tests in any way.
		</para>
		
		<para>
			The next chapter constructs an automated build system that might be used to build @greenpages@ 
			(and run its tests) outside of an interactive development environment.
		</para>
	</section>
</chapter>