Pavlin Radoslavov

Refactoring in the unit test utility framework:

 * Moved unit test utilities to the onlab-junit package under utils/junit
   - ImmutableClassChecker
   - TestUtils and TestUtilsTest

 * Added/ported unit test utilities from the older code
   - UtilityClassChecker and UtilityClassCheckerTest
   - ImmutableClassCheckerTest

 * Updated/fixed some of the pom.xml files in the context of the
   onlab-junit package:
   - Added <scope>test</scope>
   - Replaced hard-coded "1.0.0-SNAPSHOT" with "${project.version}"

Change-Id: Ie5f51ba401ca1748340f38848ab6bfc251964adc
...@@ -45,6 +45,12 @@ ...@@ -45,6 +45,12 @@
45 45
46 <dependency> 46 <dependency>
47 <groupId>org.onlab.onos</groupId> 47 <groupId>org.onlab.onos</groupId>
48 + <artifactId>onlab-junit</artifactId>
49 + <scope>test</scope>
50 + </dependency>
51 +
52 + <dependency>
53 + <groupId>org.onlab.onos</groupId>
48 <artifactId>onos-cli</artifactId> 54 <artifactId>onos-cli</artifactId>
49 <version>${project.version}</version> 55 <version>${project.version}</version>
50 </dependency> 56 </dependency>
......
...@@ -17,6 +17,8 @@ import java.util.Set; ...@@ -17,6 +17,8 @@ import java.util.Set;
17 17
18 import org.junit.Before; 18 import org.junit.Before;
19 import org.junit.Test; 19 import org.junit.Test;
20 +import org.onlab.junit.TestUtils;
21 +import org.onlab.junit.TestUtils.TestUtilsException;
20 import org.onlab.onos.ApplicationId; 22 import org.onlab.onos.ApplicationId;
21 import org.onlab.onos.net.ConnectPoint; 23 import org.onlab.onos.net.ConnectPoint;
22 import org.onlab.onos.net.DefaultHost; 24 import org.onlab.onos.net.DefaultHost;
...@@ -42,8 +44,6 @@ import org.onlab.packet.IpAddress; ...@@ -42,8 +44,6 @@ import org.onlab.packet.IpAddress;
42 import org.onlab.packet.IpPrefix; 44 import org.onlab.packet.IpPrefix;
43 import org.onlab.packet.MacAddress; 45 import org.onlab.packet.MacAddress;
44 import org.onlab.packet.VlanId; 46 import org.onlab.packet.VlanId;
45 -import org.onlab.util.TestUtils;
46 -import org.onlab.util.TestUtils.TestUtilsException;
47 47
48 import com.google.common.collect.Sets; 48 import com.google.common.collect.Sets;
49 49
......
...@@ -26,12 +26,12 @@ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; ...@@ -26,12 +26,12 @@ import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
26 import org.junit.After; 26 import org.junit.After;
27 import org.junit.Before; 27 import org.junit.Before;
28 import org.junit.Test; 28 import org.junit.Test;
29 +import org.onlab.junit.TestUtils;
30 +import org.onlab.junit.TestUtils.TestUtilsException;
29 import org.onlab.onos.sdnip.RouteListener; 31 import org.onlab.onos.sdnip.RouteListener;
30 import org.onlab.onos.sdnip.RouteUpdate; 32 import org.onlab.onos.sdnip.RouteUpdate;
31 import org.onlab.packet.IpAddress; 33 import org.onlab.packet.IpAddress;
32 import org.onlab.packet.IpPrefix; 34 import org.onlab.packet.IpPrefix;
33 -import org.onlab.util.TestUtils;
34 -import org.onlab.util.TestUtils.TestUtilsException;
35 35
36 import com.google.common.net.InetAddresses; 36 import com.google.common.net.InetAddresses;
37 37
......
...@@ -6,6 +6,7 @@ import static org.hamcrest.Matchers.is; ...@@ -6,6 +6,7 @@ import static org.hamcrest.Matchers.is;
6 import static org.hamcrest.Matchers.not; 6 import static org.hamcrest.Matchers.not;
7 import static org.junit.Assert.assertEquals; 7 import static org.junit.Assert.assertEquals;
8 import static org.junit.Assert.assertThat; 8 import static org.junit.Assert.assertThat;
9 +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
9 10
10 /** 11 /**
11 * This class tests the immutability, equality, and non-equality of 12 * This class tests the immutability, equality, and non-equality of
...@@ -17,7 +18,7 @@ public class IntentIdTest { ...@@ -17,7 +18,7 @@ public class IntentIdTest {
17 */ 18 */
18 @Test 19 @Test
19 public void intentIdFollowsGuidelineForImmutableObject() { 20 public void intentIdFollowsGuidelineForImmutableObject() {
20 - ImmutableClassChecker.assertThatClassIsImmutable(IntentId.class); 21 + assertThatClassIsImmutable(IntentId.class);
21 } 22 }
22 23
23 /** 24 /**
......
...@@ -11,6 +11,7 @@ import static org.hamcrest.MatcherAssert.assertThat; ...@@ -11,6 +11,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
11 import static org.hamcrest.Matchers.equalTo; 11 import static org.hamcrest.Matchers.equalTo;
12 import static org.hamcrest.Matchers.is; 12 import static org.hamcrest.Matchers.is;
13 import static org.hamcrest.Matchers.not; 13 import static org.hamcrest.Matchers.not;
14 +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
14 import static org.onlab.onos.net.NetTestTools.hid; 15 import static org.onlab.onos.net.NetTestTools.hid;
15 16
16 /** 17 /**
...@@ -104,6 +105,6 @@ public class TestHostToHostIntent { ...@@ -104,6 +105,6 @@ public class TestHostToHostIntent {
104 */ 105 */
105 @Test 106 @Test
106 public void checkImmutability() { 107 public void checkImmutability() {
107 - ImmutableClassChecker.assertThatClassIsImmutable(HostToHostIntent.class); 108 + assertThatClassIsImmutable(HostToHostIntent.class);
108 } 109 }
109 } 110 }
......
...@@ -4,6 +4,7 @@ import static org.hamcrest.CoreMatchers.not; ...@@ -4,6 +4,7 @@ import static org.hamcrest.CoreMatchers.not;
4 import static org.hamcrest.MatcherAssert.assertThat; 4 import static org.hamcrest.MatcherAssert.assertThat;
5 import static org.hamcrest.Matchers.equalTo; 5 import static org.hamcrest.Matchers.equalTo;
6 import static org.hamcrest.Matchers.is; 6 import static org.hamcrest.Matchers.is;
7 +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
7 import static org.onlab.onos.net.NetTestTools.link; 8 import static org.onlab.onos.net.NetTestTools.link;
8 9
9 import java.util.HashSet; 10 import java.util.HashSet;
...@@ -154,6 +155,6 @@ public class TestLinkCollectionIntent { ...@@ -154,6 +155,6 @@ public class TestLinkCollectionIntent {
154 */ 155 */
155 @Test 156 @Test
156 public void checkImmutability() { 157 public void checkImmutability() {
157 - ImmutableClassChecker.assertThatClassIsImmutable(LinkCollectionIntent.class); 158 + assertThatClassIsImmutable(LinkCollectionIntent.class);
158 } 159 }
159 } 160 }
......
...@@ -15,6 +15,7 @@ import static org.hamcrest.CoreMatchers.not; ...@@ -15,6 +15,7 @@ import static org.hamcrest.CoreMatchers.not;
15 import static org.hamcrest.MatcherAssert.assertThat; 15 import static org.hamcrest.MatcherAssert.assertThat;
16 import static org.hamcrest.Matchers.equalTo; 16 import static org.hamcrest.Matchers.equalTo;
17 import static org.hamcrest.Matchers.is; 17 import static org.hamcrest.Matchers.is;
18 +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
18 import static org.onlab.onos.net.NetTestTools.connectPoint; 19 import static org.onlab.onos.net.NetTestTools.connectPoint;
19 20
20 /** 21 /**
...@@ -135,7 +136,6 @@ public class TestMultiPointToSinglePointIntent { ...@@ -135,7 +136,6 @@ public class TestMultiPointToSinglePointIntent {
135 */ 136 */
136 @Test 137 @Test
137 public void checkImmutability() { 138 public void checkImmutability() {
138 - ImmutableClassChecker. 139 + assertThatClassIsImmutable(MultiPointToSinglePointIntent.class);
139 - assertThatClassIsImmutable(MultiPointToSinglePointIntent.class);
140 } 140 }
141 } 141 }
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
35 <dependency> 35 <dependency>
36 <groupId>org.onlab.onos</groupId> 36 <groupId>org.onlab.onos</groupId>
37 <artifactId>onlab-junit</artifactId> 37 <artifactId>onlab-junit</artifactId>
38 + <scope>test</scope>
38 </dependency> 39 </dependency>
39 </dependencies> 40 </dependencies>
40 41
......
...@@ -248,7 +248,7 @@ ...@@ -248,7 +248,7 @@
248 <dependency> 248 <dependency>
249 <groupId>org.onlab.onos</groupId> 249 <groupId>org.onlab.onos</groupId>
250 <artifactId>onlab-junit</artifactId> 250 <artifactId>onlab-junit</artifactId>
251 - <version>1.0.0-SNAPSHOT</version> 251 + <version>${project.version}</version>
252 <scope>test</scope> 252 <scope>test</scope>
253 </dependency> 253 </dependency>
254 254
......
...@@ -27,6 +27,16 @@ ...@@ -27,6 +27,16 @@
27 <artifactId>guava-testlib</artifactId> 27 <artifactId>guava-testlib</artifactId>
28 <scope>compile</scope> 28 <scope>compile</scope>
29 </dependency> 29 </dependency>
30 + <dependency>
31 + <groupId>org.hamcrest</groupId>
32 + <artifactId>hamcrest-core</artifactId>
33 + <scope>compile</scope>
34 + </dependency>
35 + <dependency>
36 + <groupId>org.hamcrest</groupId>
37 + <artifactId>hamcrest-library</artifactId>
38 + <scope>compile</scope>
39 + </dependency>
30 </dependencies> 40 </dependencies>
31 41
32 </project> 42 </project>
......
1 -package org.onlab.onos.net.intent; 1 +package org.onlab.junit;
2 -//TODO is this the right package?
3 2
4 import org.hamcrest.Description; 3 import org.hamcrest.Description;
5 import org.hamcrest.StringDescription; 4 import org.hamcrest.StringDescription;
......
1 -package org.onlab.util; 1 +package org.onlab.junit;
2 2
3 import java.lang.reflect.Constructor; 3 import java.lang.reflect.Constructor;
4 import java.lang.reflect.Field; 4 import java.lang.reflect.Field;
......
1 +package org.onlab.junit;
2 +
3 +import org.hamcrest.Description;
4 +import org.hamcrest.StringDescription;
5 +import org.onlab.junit.TestUtils.TestUtilsException;
6 +
7 +import java.lang.reflect.Constructor;
8 +import java.lang.reflect.Method;
9 +import java.lang.reflect.Modifier;
10 +
11 +
12 +/**
13 + * Hamcrest style class for verifying that a class follows the
14 + * accepted rules for utility classes.
15 + *
16 + * The rules that are enforced for utility classes:
17 + * - the class must be declared final
18 + * - the class must have only one constructor
19 + * - the constructor must be private and inaccessible to callers
20 + * - the class must have only static methods
21 + */
22 +
23 +public class UtilityClassChecker {
24 +
25 + private String failureReason = "";
26 +
27 + /**
28 + * Method to determine if a given class is a properly specified
29 + * utility class. In addition to checking that the class meets the criteria
30 + * for utility classes, an object of the class type is allocated to force
31 + * test code coverage onto the class constructor.
32 + *
33 + * @param clazz the class to check
34 + * @return true if the given class is a properly specified utility class.
35 + */
36 + private boolean isProperlyDefinedUtilityClass(Class<?> clazz) {
37 + // class must be declared final
38 + if (!Modifier.isFinal(clazz.getModifiers())) {
39 + failureReason = "a class that is not final";
40 + return false;
41 + }
42 +
43 + // class must have only one constructor
44 + final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
45 + if (constructors.length != 1) {
46 + failureReason = "a class with more than one constructor";
47 + return false;
48 + }
49 +
50 + // constructor must not be accessible outside of the class
51 + final Constructor<?> constructor = constructors[0];
52 + if (constructor.isAccessible()) {
53 + failureReason = "a class with an accessible default constructor";
54 + return false;
55 + }
56 +
57 + // constructor must be private
58 + if (!Modifier.isPrivate(constructor.getModifiers())) {
59 + failureReason = "a class with a default constructor that is not private";
60 + return false;
61 + }
62 +
63 + // class must have only static methods
64 + for (final Method method : clazz.getMethods()) {
65 + if (method.getDeclaringClass().equals(clazz)) {
66 + if (!Modifier.isStatic(method.getModifiers())) {
67 + failureReason = "a class with one or more non-static methods";
68 + return false;
69 + }
70 + }
71 +
72 + }
73 +
74 + try {
75 + final Object newObject = TestUtils.callConstructor(constructor);
76 + if (newObject == null) {
77 + failureReason = "could not instantiate a new object";
78 + return false;
79 + }
80 + } catch (TestUtilsException e) {
81 + failureReason = "could not instantiate a new object";
82 + return false;
83 + }
84 + return true;
85 + }
86 +
87 + /**
88 + * Describe why an error was reported. Uses Hamcrest style Description
89 + * interfaces.
90 + *
91 + * @param description the Description object to use for reporting the
92 + * mismatch
93 + */
94 + public void describeMismatch(Description description) {
95 + description.appendText(failureReason);
96 + }
97 +
98 + /**
99 + * Describe the source object that caused an error, using a Hamcrest
100 + * Matcher style interface. In this case, it always returns
101 + * that we are looking for a properly defined utility class.
102 + *
103 + * @param description the Description object to use to report the "to"
104 + * object
105 + */
106 + public void describeTo(Description description) {
107 + description.appendText("a properly defined utility class");
108 + }
109 +
110 + /**
111 + * Assert that the given class adheres to the utility class rules.
112 + *
113 + * @param clazz the class to check
114 + *
115 + * @throws java.lang.AssertionError if the class is not a valid
116 + * utility class
117 + */
118 + public static void assertThatClassIsUtility(Class<?> clazz) {
119 + final UtilityClassChecker checker = new UtilityClassChecker();
120 + if (!checker.isProperlyDefinedUtilityClass(clazz)) {
121 + final Description toDescription = new StringDescription();
122 + final Description mismatchDescription = new StringDescription();
123 +
124 + checker.describeTo(toDescription);
125 + checker.describeMismatch(mismatchDescription);
126 + final String reason =
127 + "\n" +
128 + "Expected: is \"" + toDescription.toString() + "\"\n" +
129 + " but : was \"" + mismatchDescription.toString() + "\"";
130 +
131 + throw new AssertionError(reason);
132 + }
133 + }
134 +}
1 +package org.onlab.junit;
2 +
3 +import org.junit.Test;
4 +
5 +import static org.hamcrest.MatcherAssert.assertThat;
6 +import static org.hamcrest.Matchers.containsString;
7 +import static org.hamcrest.Matchers.is;
8 +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
9 +
10 +/**
11 + * Set of unit tests to check the implementation of the immutable class
12 + * checker.
13 + */
14 +public class ImmutableClassCheckerTest {
15 + /**
16 + * Test class for non final class check.
17 + */
18 + // CHECKSTYLE IGNORE FinalClass FOR NEXT 1 LINES
19 + static class NonFinal {
20 + private NonFinal() { }
21 + }
22 +
23 + /**
24 + * Check that a non final class correctly produces an error.
25 + * @throws Exception if any of the reflection lookups fail.
26 + */
27 + @Test
28 + public void testNonFinalClass() throws Exception {
29 + boolean gotException = false;
30 + try {
31 + assertThatClassIsImmutable(NonFinal.class);
32 + } catch (AssertionError assertion) {
33 + assertThat(assertion.getMessage(),
34 + containsString("is not final"));
35 + gotException = true;
36 + }
37 + assertThat(gotException, is(true));
38 + }
39 +
40 + /**
41 + * Test class for non private member class check.
42 + */
43 + static final class FinalProtectedMember {
44 + protected final int x = 0;
45 + }
46 +
47 + /**
48 + * Check that a final class with a non-private member is properly detected.
49 + *
50 + * @throws Exception if any of the reflection lookups fail.
51 + */
52 + @Test
53 + public void testFinalProtectedMember() throws Exception {
54 + boolean gotException = false;
55 + try {
56 + assertThatClassIsImmutable(FinalProtectedMember.class);
57 + } catch (AssertionError assertion) {
58 + assertThat(assertion.getMessage(),
59 + containsString("a field named 'x' that is not private"));
60 + gotException = true;
61 + }
62 + assertThat(gotException, is(true));
63 + }
64 +
65 + /**
66 + * Test class for non private member class check.
67 + */
68 + static final class NotFinalPrivateMember {
69 + private int x = 0;
70 + }
71 +
72 + /**
73 + * Check that a final class with a non-final private
74 + * member is properly detected.
75 + *
76 + * @throws Exception if any of the reflection lookups fail.
77 + */
78 + @Test
79 + public void testNotFinalPrivateMember() throws Exception {
80 + boolean gotException = false;
81 + try {
82 + assertThatClassIsImmutable(NotFinalPrivateMember.class);
83 + } catch (AssertionError assertion) {
84 + assertThat(assertion.getMessage(),
85 + containsString("a field named 'x' that is not final"));
86 + gotException = true;
87 + }
88 + assertThat(gotException, is(true));
89 + }
90 +
91 + /**
92 + * Test class for non private member class check.
93 + */
94 + static final class ClassWithSetter {
95 + private final int x = 0;
96 + public void setX(int newX) {
97 + }
98 + }
99 +
100 + /**
101 + * Check that a final class with a final private
102 + * member that is modifyable by a setter is properly detected.
103 + *
104 + * @throws Exception if any of the reflection lookups fail.
105 + */
106 + @Test
107 + public void testClassWithSetter() throws Exception {
108 + boolean gotException = false;
109 + try {
110 + assertThatClassIsImmutable(ClassWithSetter.class);
111 + } catch (AssertionError assertion) {
112 + assertThat(assertion.getMessage(),
113 + containsString("a class with a setter named 'setX'"));
114 + gotException = true;
115 + }
116 + assertThat(gotException, is(true));
117 + }
118 +
119 +}
120 +
1 -package org.onlab.util; 1 +package org.onlab.junit;
2 2
3 import static org.junit.Assert.assertArrayEquals; 3 import static org.junit.Assert.assertArrayEquals;
4 import static org.junit.Assert.assertEquals; 4 import static org.junit.Assert.assertEquals;
...@@ -6,7 +6,7 @@ import static org.junit.Assert.assertNull; ...@@ -6,7 +6,7 @@ import static org.junit.Assert.assertNull;
6 6
7 import org.junit.Before; 7 import org.junit.Before;
8 import org.junit.Test; 8 import org.junit.Test;
9 -import org.onlab.util.TestUtils.TestUtilsException; 9 +import org.onlab.junit.TestUtils.TestUtilsException;
10 10
11 /** 11 /**
12 * Test and usage examples for TestUtils. 12 * Test and usage examples for TestUtils.
......
1 +package org.onlab.junit;
2 +
3 +import org.junit.Test;
4 +
5 +import static org.hamcrest.MatcherAssert.assertThat;
6 +import static org.hamcrest.Matchers.containsString;
7 +import static org.hamcrest.Matchers.is;
8 +import static org.onlab.junit.UtilityClassChecker.assertThatClassIsUtility;
9 +
10 +/**
11 + * Set of unit tests to check the implementation of the utility class
12 + * checker.
13 + */
14 +public class UtilityClassCheckerTest {
15 +
16 + // CHECKSTYLE:OFF test data intentionally not final
17 + /**
18 + * Test class for non final class check.
19 + */
20 + static class NonFinal {
21 + private NonFinal() { }
22 + }
23 + // CHECKSTYLE:ON
24 +
25 + /**
26 + * Check that a non final class correctly produces an error.
27 + * @throws Exception if any of the reflection lookups fail.
28 + */
29 + @Test
30 + public void testNonFinalClass() throws Exception {
31 + boolean gotException = false;
32 + try {
33 + assertThatClassIsUtility(NonFinal.class);
34 + } catch (AssertionError assertion) {
35 + assertThat(assertion.getMessage(),
36 + containsString("is not final"));
37 + gotException = true;
38 + }
39 + assertThat(gotException, is(true));
40 + }
41 +
42 + /**
43 + * Test class for final no constructor class check.
44 + */
45 + static final class FinalNoConstructor {
46 + }
47 +
48 + /**
49 + * Check that a final class with no declared constructor correctly produces
50 + * an error. In this case, the compiler generates a default constructor
51 + * for you, but the constructor is 'protected' and will fail the check.
52 + *
53 + * @throws Exception if any of the reflection lookups fail.
54 + */
55 + @Test
56 + public void testFinalNoConstructorClass() throws Exception {
57 + boolean gotException = false;
58 + try {
59 + assertThatClassIsUtility(FinalNoConstructor.class);
60 + } catch (AssertionError assertion) {
61 + assertThat(assertion.getMessage(),
62 + containsString("class with a default constructor that " +
63 + "is not private"));
64 + gotException = true;
65 + }
66 + assertThat(gotException, is(true));
67 + }
68 +
69 + /**
70 + * Test class for class with more than one constructor check.
71 + */
72 + static final class TwoConstructors {
73 + private TwoConstructors() { }
74 + private TwoConstructors(int x) { }
75 + }
76 +
77 + /**
78 + * Check that a non static class correctly produces an error.
79 + * @throws Exception if any of the reflection lookups fail.
80 + */
81 + @Test
82 + public void testOnlyOneConstructor() throws Exception {
83 + boolean gotException = false;
84 + try {
85 + assertThatClassIsUtility(TwoConstructors.class);
86 + } catch (AssertionError assertion) {
87 + assertThat(assertion.getMessage(),
88 + containsString("more than one constructor"));
89 + gotException = true;
90 + }
91 + assertThat(gotException, is(true));
92 + }
93 +
94 + /**
95 + * Test class with a non private constructor.
96 + */
97 + static final class NonPrivateConstructor {
98 + protected NonPrivateConstructor() { }
99 + }
100 +
101 + /**
102 + * Check that a class with a non private constructor correctly
103 + * produces an error.
104 + * @throws Exception if any of the reflection lookups fail.
105 + */
106 + @Test
107 + public void testNonPrivateConstructor() throws Exception {
108 +
109 + boolean gotException = false;
110 + try {
111 + assertThatClassIsUtility(NonPrivateConstructor.class);
112 + } catch (AssertionError assertion) {
113 + assertThat(assertion.getMessage(),
114 + containsString("constructor that is not private"));
115 + gotException = true;
116 + }
117 + assertThat(gotException, is(true));
118 + }
119 +
120 + /**
121 + * Test class with a non static method.
122 + */
123 + static final class NonStaticMethod {
124 + private NonStaticMethod() { }
125 + public void aPublicMethod() { }
126 + }
127 +
128 + /**
129 + * Check that a class with a non static method correctly produces an error.
130 + * @throws Exception if any of the reflection lookups fail.
131 + */
132 + @Test
133 + public void testNonStaticMethod() throws Exception {
134 +
135 + boolean gotException = false;
136 + try {
137 + assertThatClassIsUtility(NonStaticMethod.class);
138 + } catch (AssertionError assertion) {
139 + assertThat(assertion.getMessage(),
140 + containsString("one or more non-static methods"));
141 + gotException = true;
142 + }
143 + assertThat(gotException, is(true));
144 + }
145 +}
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
24 <dependency> 24 <dependency>
25 <groupId>org.onlab.onos</groupId> 25 <groupId>org.onlab.onos</groupId>
26 <artifactId>onlab-junit</artifactId> 26 <artifactId>onlab-junit</artifactId>
27 + <scope>test</scope>
27 </dependency> 28 </dependency>
28 <dependency> 29 <dependency>
29 <groupId>io.netty</groupId> 30 <groupId>io.netty</groupId>
......