-
Notifications
You must be signed in to change notification settings - Fork 108
Expand file tree
/
Copy pathunit-testing.html
More file actions
200 lines (177 loc) · 8.36 KB
/
unit-testing.html
File metadata and controls
200 lines (177 loc) · 8.36 KB
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
---
# Copyright Vespa.ai. All rights reserved.
title: "Unit testing"
redirect_from:
- /en/unit-testing.html
---
<p>This document describes how to test application functionality in a local Java vm.
See <a href="../operations/automated-deployments.html">automated deployments</a>
for how to create system, staging and production verification tests.</p>
<h2 id="unit-testing-using-application">Unit testing using Application</h2>
<p>
The <a href="https://javadoc.io/doc/com.yahoo.vespa/application/latest/com/yahoo/application/Application.html">
Application</a> class is useful when writing unit tests.
Application uses the application package configuration and set up a container instance for testing.
The <a href="https://javadoc.io/doc/com.yahoo.vespa/application/latest/com/yahoo/application/container/JDisc.html">
JDisc</a> class that is accessed by
the test through <a href="https://javadoc.io/page/com.yahoo.vespa/application/latest/com/yahoo/application/Application.html#getJDisc-java.lang.String-">app.getJDisc(clusterName)</a> -
this class has methods for using all common <a href="../reference/applications/components.html">component types</a>.
</p><p>
Refer to <a href="https://github.com/vespa-engine/sample-apps/blob/master/album-recommendation-java/app/src/test/java/ai/vespa/example/album/MetalSearcherTest.java">
MetalSearcherTest.java</a> for example use.
Notice how the test disables the network layer in order to run tests in parallel.
</p>
{% include note.html content="<code>Application</code> does not set up <em>content</em> nodes, only <em>container</em>.
It is hence fully stateless, and intended for unit testing the functionality of application components.
The <em>ClusterSearcher</em> will not find any content nodes and log errors if invoked.
Write a <em>System Test</em> to test end-to-end features like search."%}
<p>
For prototyping, enable the network interface,
instantiate the Container and run requests using a browser:
</p>
<pre>{% highlight java%}
public class ApplicationMain {
@Test
public static void main(String[] args) throws Exception {
try (com.yahoo.application.Application app = com.yahoo.application.Application.fromApplicationPackage(
FileSystems.getDefault().getPath("src/main/application"),
Networking.enable)) {
app.getClass();
Thread.sleep(Long.MAX_VALUE);
}
}
}
{% endhighlight %}</pre>
<h2 id="unit-testing-configurable-components">Unit Testing Configurable Components</h2>
<p>
How to programmatically build configuration instances for unit testing.
Read the <a href="developer-guide.html">Developer Guide</a> first.
</p><p>
To be able to write self-contained unit tests using configuration classes
generated from a schema, it is necessary to instantiate the configuration
without the use of for instance an external services file. Configuration classes
contain their own builders which are useful for solving exactly this problem. By
using builders, the configuration will be created as an immutable, type-safe
object, exactly the same as used during deployment.
</p>
<h3 id="definition">Configuration schema</h3>
<p>
Assume the config definition file <code>demo.def</code> with the following schema:
</p>
<pre>
package=com.mydomain.demo
toplevel[].term string
toplevel[].number int
toplevel[].largenumber long
toplevel[].secondlevel[].name string
toplevel[].secondlevel[].magnitude double
simplename string
simplenumber int
simplevaluearray[] string
coordinate.x double
coordinate.y double
coordinate.name string
</pre>
<p>
In other words, the configuration class will be <code>com.mydomain.demo.DemoConfig</code>,
and it will contain an array of structures,
a couple of top-level primitives (<em>simplename</em> and <em>simplenumber</em>),
an array of primitive values (<em>simplevaluearray</em>) and a structure (<em>coordinate</em>).
</p>
<h3 id="using-configuration-builders">Using configuration builders</h3>
<p>
All structured objects in the cloud configuration system have their own
Builder as a nested class. So, in the above example, one would get
<code>DemoConfig.Builder</code> for the complete configuration class,
<code>DemoConfig.Toplevel.Builder</code> for the top-level array,
<code>DemoConfig.Toplevel.Secondlevel.Builder</code> for the inner array, and
<code>DemoConfig.Coordinate.Builder</code> for the structure.
</p><p>
A configuration object, or substructure, is easiest instantiated using a
constructor accepting the corresponding <em>Builder</em> class, an array of structures
should use the constructor accepting an array of <em>Builder</em> instances, and an
array of primitive values simply accepts a java.util.Collection of the
corresponding primitive value class:
</p>
<pre>
package com.mydomain.demo;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import static com.mydomain.demo.DemoConfig.Toplevel;
import static com.mydomain.demo.DemoConfig.Toplevel.Secondlevel;
import static com.mydomain.demo.DemoConfig.Coordinate;
public class DemoTest {
/**
* An example showing how to build a relatively complex, mixed type
* configuration including arrays of primitive elements, nested arrays and
* arrays of structures and so on.
*/
@Test
public final void test() {
// We need use builders to safely create the graph of immutable
// configuration objects. Each generated configuration class contains
// the builder for creating an instance of itself. This pattern is
// repeated for structures. So, in our case, we have four structured
// levels. The complete configuration class, DemoConfig, the top-level,
// nested array, Toplevel, the contained array, Secondlevel and the
// structure Coordinate. This leaves us with using four distinct
// builder classes: DemoConfig.Builder, Toplevel.Builder,
// Secondlevel.Builder and Coordinate.Builder.
// Chained setters are the most used pattern for the builders:
DemoConfig forTesting = new DemoConfig(new DemoConfig.Builder()
.simplename("basic chained setter for the string simplename")
.simplenumber(42)
.toplevel(buildTopLevelArray())
.simplevaluearray(
Arrays.asList(new String[] { "primitive", "arrays",
"are", "easier", "to", "build", "than",
"arrays", "of", "structures" }))
.coordinate(
new Coordinate.Builder()
.name("have no idea what to call this one")
.x(1e300d).y(1e-300d)));
assertTrue(forTesting != null); // ;)
}
/**
* It is often the more readable solution to use helper methods to build
* configuration arrays.
*
* @return a list of Toplevel.Builder instances
*/
private List<Toplevel.Builder> buildTopLevelArray() {
// Note how the Builder classes tend to work on Collection classes and
// mutable objects, while the config ready for use is bolted down and
// immutable:
List<Toplevel.Builder> configArray = new ArrayList<Toplevel.Builder>(3);
String[] configStrings = new String[] { "a", "b", "c" };
int[] configNumbers = new int[] { 1, 2, 3 };
long[] configLargeNumbers = new long[] { 1L + (long) Integer.MAX_VALUE,
2L + (long) Integer.MAX_VALUE, 3L + (long) Integer.MAX_VALUE };
for (int i = 0; i < configStrings.length; ++i) {
configArray.add(new Toplevel.Builder().number(configNumbers[i])
.largenumber(configLargeNumbers[i]).term(configStrings[i])
.secondlevel(buildSecondLevelArray(2)));
}
return configArray;
}
/**
* Once again, the building of an array is delegated to a helper method
*
* @param subelements
* the length of the returned list
* @return a list of SecondLevel.Builder
*/
private List<Secondlevel.Builder> buildSecondLevelArray(int subelements) {
List<Secondlevel.Builder> builders = new ArrayList<Secondlevel.Builder>(
subelements);
for (int i = 0; i < subelements; ++i) {
builders.add(new Secondlevel.Builder().name(String.valueOf(i))
.magnitude((double) i));
}
return builders;
}
}
</pre>