The Apache Commons Collections package contains types that extend and augment the Java Collections Framework.
The tools we are testing is Apache Commons Collections package. The package contains types including new interfaces, implementations and utilities that extend and augment the Java Collections Framework. We can use mutiple data structures it provides and create more efficient arithmetic. It allows programmer to develop applications in a way that ensures performance and also greatly simplifying the code.
This is our github link (https://github.com/Jimmykoki/commons-collections) Team Name: Whatever Team Member: Jianlin Li and Yixuan Li
In order to better understand the internal structure of the Collections, we need to count the number of code lines. In this case, we use a command and the line of code is 130157 .
find . -name '*.java' | xargs wc -l
Meanwhile, we have counted number of java files, and the result is 590.
find . -name '*.java' | wc -l
As we have introduced, Apache Commons Collections4 is a liabrary written by Java.
we need to fork and clone the program in author's github(https://github.com/apache/commons-collections), and pull it in our local repositories
IntelliJ IDEA would be a best choice for us to set up running environment for this project. We use Maven to build, and excute mvn commnd to start the project. Before that, we also have to config and invoke relative dependencies and plugins in pom.xml
In the entire project directory, we can see that all previous test cases created by author have stored in test folder, test results are shown in following figure#1
Figure#1 Test resultFirst ,we could find the existing testing case is black-box testing. When testing, the program is regarded as a black box that cannot be opened. Without considering the internal structure and internal characteristics of the program, we just conducts direct tests.
After that, we need to use Junit to run these test cases. Junit can support more different styles of testing. When we want to use Junit, we need to add Junit to our project. Because Junit is already built in the pom.xml file, we can open it directly in IntelliJ IDEA without additional installation.
Test cases are located in the src/test folder. This project uses the Junit framework to test all test files. Each subfolder is related to a package in src. ./test/builds contains builder package tests, I take some screenshots figure#2 as follows:
in org/apache/commons/collections4/QueueUtilsTest.java
@Test
public void testSynchronizedQueue() {
final Queue<Object> queue = QueueUtils.synchronizedQueue(new LinkedList<>());
assertTrue(queue instanceof SynchronizedQueue, "Returned object should be a SynchronizedQueue.");
try {
QueueUtils.synchronizedQueue(null);
fail("Expecting NullPointerException for null queue.");
} catch (final NullPointerException ex) {
// expected
}
}This test is mainly to test the type of the object. it can judge whether the queue created by synchronizedQueue method is a synchronous queue, and We could get the result (true or false) by using assertTrue.
in `org/apache/commons/collections4/MapUtilsTest.java
@Test
public void testPredicatedMap() {
final Predicate<Object> p = getPredicate();
final Map<Object, Object> map = MapUtils.predicatedMap(new HashMap<>(), p, p);
assertTrue(map instanceof PredicatedMap);
try {
MapUtils.predicatedMap(null, p, p);
fail("Expecting NullPointerException for null map.");
} catch (final NullPointerException e) {
// expected
}
}This testing case is to test predicated map, we could use assertTrue method to judge whether a map created by predicatedMap method is an instance of PredicatedMap.
Functional testing, also known as black-box testing, is to treat the test object as a black-box, and perform functional testing on the software product without considering the internal structure and processing process of the software product. Functional testing focuses on the "functional requirements" of a software product.
We divide the entire space of the input into classes such that the union of these classes is the entire space. In most cases there will be few failures in most spaces, and only in very few areas will there be many failures. We can use the partition testing to test each category.
There are many classes In common-collections, we will choose the ContainsAny method in ConllectionUnit class to focus on partition testing.
we put test case in /src/test/java/org/apache/commons/collections4/TestCasePart2.java
@Test
public void containsAnyNull(){
final Collection<String> emptyArr = new ArrayList<>(0);
final Collection<String> emptyDeque = new ArrayDeque<>(0);
final Collection<String> emptyLink = new LinkedHashSet<>(0);
final String[] emptyStr = {};
final String[] oddsArr = {"1", "3"};
assertFalse(CollectionUtils.containsAny(emptyArr, emptyStr), "containsAny({},{}) should return false.");
assertFalse(CollectionUtils.containsAny(emptyDeque, emptyStr), "containsAny({},{}) should return false.");
assertFalse(CollectionUtils.containsAny(emptyLink, emptyStr), "containsAny({},{}) should return false.");
assertFalse(CollectionUtils.containsAny(emptyArr, oddsArr), "containsAny({},{1,3}) should return false.");
assertFalse(CollectionUtils.containsAny(emptyDeque, oddsArr), "containsAny({},{1,3}) should return false.");
assertFalse(CollectionUtils.containsAny(emptyLink, oddsArr), "containsAny({},{1,3}) should return false.");Finite state machine is a mathematical model that represents a finite number of states and behavior such as transitions and actions between those states.
Finite state machines generally have the following characteristics:
- Things can be described by states, and at any time, things are always in a state;
- The total number of states a thing has is limited;
- By triggering certain behaviors of things, it can cause things to transition from one state to another;
- The state of things changes regularly. A state can be transformed to B, B can be transformed to C, but A can not necessarily be transformed to C;
- The same behavior can change things from multiple states to the same state, but cannot change from the same state to multiple states.
In our project, we created a feature to operate on any two collections given, we deduplicate and merge them, and finally got a deduplicated list.
The specific operation process of this method is as follows:
- First, the
collate()method will merge and sort the twocollectionsto generate asorted collection - At this point we will get a
sorted collection, but there may be duplicate elements in it - Finally, use the
uniqueSet()method to deduplicate thesorted collection, so that we can get a mergedlistafter deduplication.
The following State Transform Diagram shows the transition process between the states of the finite state machine:
Figure#1 State Transform DiagramThe test cases called TestCases_part2.java are created in src/test/java/org/apache/commons/collections4/TestCases_part2.java
For each of the state, we write a test case to check the edge for income data and outcome data:
public class TestCases_part2 extends MockTestCase{
private List<Integer> collectionA;
private List<Integer> collectionB;
List<Integer> resultList1 = Arrays.asList(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3);
List<Integer> reslutList3 = Arrays.asList(1, 2, 3);
private List<String> collectionC;
private List<String> collectionD;
List<String> resultList2 = Arrays.asList("a", "a", "a", "a", "b", "b", "b", "b", "c", "c", "c", "c");
List<String> resultList4 = Arrays.asList("a", "b", "c");
@Test
public void testCollateInt(){
collectionA = Arrays.asList(1, 2, 2, 3, 3, 3);
collectionB = Arrays.asList(1, 1, 1, 2, 2, 3);
int resultSize = 12;
List<Integer> combinedList = CollectionUtils.collate(collectionA, collectionB);
assertEquals(resultSize, combinedList.size());
assertEquals(resultList1, combinedList);
}
@Test
public void testCollateString(){
collectionC = Arrays.asList("a", "b", "b", "c", "c", "c");
collectionD = Arrays.asList("a", "a", "a", "b", "b", "c");
List<String> combinedList = CollectionUtils.collate(collectionC, collectionD);
int resultSize = 12;
assertEquals(resultSize, combinedList.size());
assertEquals(resultList2, combinedList);
}
@Test
public void testuniSetInt(){
Bag<Integer> testBag = new HashBag<>(resultList1);
assertEquals(4, testBag.getCount(1));
assertEquals(4, testBag.getCount(2));
assertEquals(4, testBag.getCount(3));
List<Integer> fnialList = new ArrayList<>();
fnialList.addAll(testBag.uniqueSet());
assertEquals(fnialList, reslutList3, "All elements keep only one");
}
@Test
public void testuniSetString(){
Bag<String> testBag = new HashBag<>(resultList2);
assertEquals(4, testBag.getCount("a"));
assertEquals(4, testBag.getCount("b"));
assertEquals(4, testBag.getCount("c"));
List<String> fnialList = new ArrayList<>();
fnialList.addAll(testBag.uniqueSet());
assertEquals(fnialList, resultList4, "All elements keep only one");
}All unit tests passed, as shown in the following Figure#2
Figure#2 Test resultWhite-box testing is also known as Structural testing. This testing implement coverage testing to inspect every logic path and branch in software. It's able to help developer comprehend the internal logic of the program and reveal error in hidden corners.
Jacoco can provide code coverage statistics for JVM-based code, and expects to provide lightweight, scalable, and well-documented library files to integrate various build and development tools.
We notice that org.apache.commons.collection4.map has the 87% coverage for class, 82% coverage for method, 87% coverage for line. Thus we add new test cases to cover some of its functionalities.
In this report, we will show you how to use the JaCoCo Maven plugin to generate code coverage reports for Java projects. Declare JaCoCo plugin in the pom.xmlfile.
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<!-- attached to Maven test phase -->
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
We want to focus on org.apache.commons.collections.map, which has the following coverage, such as line, branch, method coverage. We put it on a table.
| measures | missed | total | coverage |
|---|---|---|---|
| line | 274 | 3162 | 91% |
| branch | N/A | N/A | 78% |
| method | 71 | 898 | 92% |
We can also view the coverage of the mentioned lines and methods in the IntelliJ IDEA file as shown in the image below:
For map folder, we focus on FixedSizeMap, which only has 76% methods, 70% lines covered. SingletonMap only has 86% methods, 71% lines covered.Flat3Map has 90% methods,86% lines covered. Therefore, we design new test cases to improve the coverages of methods and lines in these two files.
We put our code in /src/test/java/org/apache/commons/collections4/TestCases_part3.java/ ,and we could see the coverage has improved a lot compared to the previous data.
We still query its coverage in IntelliJ IDEA, and compare it with the previous data as shown in the following picture and table:
| measures | Method before | Method after | line before | line after |
|---|---|---|---|---|
| FixedSizeMap | 76% | 92% | 70% | 79% |
| SingletonMap | 86% | 88% | 71% | 76% |
| Flat3Map | 94% | 94% | 86% | 90% |
We write 5 methods to improve these 3 java files.
- First, we improve
isFull(),maxSize()inFixedSizeMap.
@Test
public void FixedSizeMapisFullTest(){
Map<String,Integer> testMap = new HashMap<>();
testMap.put("Test", 1);
FixedSizeMap fixedSizeMap = FixedSizeMap.fixedSizeMap(testMap);
assertTrue(fixedSizeMap.isFull());
}
@Test
public void FixedSizeMapMaxSizeTest(){
Map<String,Integer> testMap = new HashMap<>();
testMap.put("Test1", 1);
testMap.put("Test2", 2);
testMap.put("Test3", 3);
FixedSizeMap fixedSizeMap = FixedSizeMap.fixedSizeMap(testMap);
assertEquals(3,fixedSizeMap.maxSize());
}- Second, we improve
SingletonMapinSingletonMap.
@Test
public void SingletonMapTest1(){
Map<String,Integer> testMap = new HashMap<>();
testMap.put("Test1", 1);
Map<String,Integer> result = new SingletonMap<>(testMap);
assertEquals(testMap, result);
}- Third, we improve
removeinFlat3Map.
@Test
public void Flat3MapTest1(){
final Flat3Map<Integer, Integer> m = new Flat3Map<>();
Object obj;
Integer ONE = Integer.valueOf(1);
Integer TWO = Integer.valueOf(2);
Integer THREE = Integer.valueOf(3);
m.put(ONE, ONE);
m.put(null, TWO);
m.put(THREE, THREE);
obj = m.remove(null);
assertSame(TWO, obj);
obj = m.get(ONE);
assertSame(ONE, obj);
obj = m.get(THREE);
assertSame(THREE, obj);
obj = m.get(null);
assertNull(obj);
}
@Test
public void Flat3MapTest2(){
final Flat3Map<Integer, Integer> m = new Flat3Map<>();
Object obj;
Integer ONE = Integer.valueOf(1);
Integer TWO = Integer.valueOf(2);
Integer THREE = Integer.valueOf(3);
m.put(null, ONE);
m.put(TWO, TWO);
m.put(THREE, THREE);
obj = m.remove(null);
assertSame(ONE, obj);
obj = m.get(TWO);
assertSame(TWO, obj);
obj = m.get(THREE);
assertSame(THREE, obj);
obj = m.get(null);
assertNull(obj);
}Continuous integration (CI) ,was proposed by Grady Booch first on his 1991 method, is the practice of automating the integration of code changes from multiple contributors into a single software project.
CI emphasizes that build unit tests immediately after the developer commits the new code, test developers can determine whether the new code and the original code are properly integrated according to the results of the tests.
Continuous integration- Located errors and problems in a timely manner during testing sessions
- Avoided developers deviating the main branch by check-in to the main source repository frequently
- Reduce unnecessary cost and maintained rapid iteration
Travis CI is a continuous integration tool that test and deploy projects with ease. Sync the project we create with Travis CI, which is used to assert the new cpde's correctness before integration, can imporve work efficiency in testing session.
Travis CI only supports Github and no other code hosting services. This means, you must meet the following conditions in order to use Travis CI:
- Have a GitHub account
- There is an item under this account
- The project has executable code in it
- The project also contains build or test scripts
- If these conditions are all right, you can start using Travis CI.
When we log in to our Travis CI account, Travis will list all your repositories on Github and the organization you belong to. At this point, select the repository you need Travis to help you build, and turn on the switch next to the repository. Once a repository is activated, Travis listens for all changes in that repository.
We should first register a Travis CI, we can test this in sync with our project, we created the project and named it Jimmykoki/commons-collections, when we register, we can go and choose the project we need to test.
Travis requires that there must be a .travis.yml file in the root directory of the project. This is the configuration file that specifies the behavior of Travis. This file must be saved in the Github repository. Once the code repository has a new Commit, Travis will find this file and execute the commands in it. Therefore, we need to create a .travis.yml file in my project. The file contains the following information:
language: java
cache:
directories:
- ~/.m2
jdk:
- openjdk8
- openjdk11
- openjdk15
script:
- mvn test
after_success:
- mvn -V --no-transfer-progress clean test jacoco:report coveralls:report -Ptravis-jacoco javadoc:javadoc -Ddoclint=allIn the above code, two fields are set. The language field specifies the default runtime environment, which is set to use the Java environment. The script field specifies the script to run.
When we add .travis.yml, we submit it to GitHub again. After a few minutes, the following page will be displayed on our Travis CI, which means we build passes.
We will add the extension to GitHub, and the extension will appear in the account settings, as shown in the image below:
Finally we can find that build passing appears in our repository
https://www.mindtheproduct.com/what-the-hell-are-ci-cd-and-devops-a-cheatsheet-for-the-rest-of-us/
Testable Design is the ability to test code better. To quote Roy Osherove, author of The Art of Unit Testing with Examples in .NET (Manning Publications, 2009), “a given piece of code should be easy and quick to write a unit test against.”
- Avoided complex
privatemethods. Especially,privatemethod that would make a certain kind of testing difficult during testing process. - Avoided the
Singleton. TheSingletonenhances the ambiguity of API, and these API mask real dependencies and bring a couple of unnecessary tight coupling between class. - Avoid
staticmethods. Implement ofstaticmethod will be easier to introduce a series of tight coupling, and result in unstable code structure. - Avoid
newstatement. Instantiating class with new will also cause unclear API and reduce the testability of the code.
In this commons-collections library, we found a method called forEach in IterableUtils class, we notice that it's kind of static method. According to above aspects, we could create a new method called forEachV2 and make it as public method for testable design.
Orgin method is shown as following code
static <E> void forEach(final Iterable<E> iterable, final Closure<? super E> closure) {
IteratorUtils.forEach(emptyIteratorIfNull(iterable), closure);
}and new method
public <E> void forEachV2(final Iterable<E> iterable, final Closure<? super E> closure) {
IteratorUtils.forEach(emptyIteratorIfNull(iterable), closure);
}we ceate a new unit test and make new method available for testable design, and the test case is stored in /src/test/java/org/apache/commons/collections4/TestCasePart5.java
@Test
public void testIterableUnitForeach(){
final List<Integer> listA = new ArrayList<>();
listA.add(1);
final List<Integer> listB = new ArrayList<>();
listB.add(2);
final Closure<List<Integer>> testClosure = ClosureUtils.invokerClosure("clear");
final Collection<List<Integer>> col = new ArrayList<>();
col.add(listA);
col.add(listB);
IterableUtils iterableUtils = new IterableUtils();
iterableUtils.forEachV2(col, testClosure);
assertTrue(listA.isEmpty() && listB.isEmpty());
try {
IterableUtils.forEach(col, null);
fail("expecting NullPointerException");
} catch (final NullPointerException npe) {
// expected
}
IterableUtils.forEach(null, testClosure);
// null should be OK
col.add(null);
IterableUtils.forEach(col, testClosure);
}and the test result is shown in following Figure#1
Figure#1 Test resultThe concept of mock is actually very simple. We have also introduced it before: the so-called mock is to create a false object of a class, and in the test environment, it is used to replace the real object to achieve two purposes:
- Verify that some methods of this object are called, how many times are called, what are the parameters, etc.
- Specifies the behavior of certain methods of this object, returning a specific value, or performing a specific action
To use Mock, We will use the Mockito framework, which is the most widely used mock framework in the Java world.
Mockitois a mocking framework that allows you to test with a concise API. AndMockitois easy to learn, it's readable and the validation syntax is concise.- In object-oriented programming, a mock object is a fake object that simulates the behavior of a real object in a controllable way.
- Programmers often create mock objects to test the behavior of other objects, much like car designers use crash test dummies to simulate the dynamic behavior of people in vehicle crashes.
Here we choose to mock hashCode() which is in AbstractLinkedList, the original code is as follows:
public int hashCode() {
int hashCode = 1;
for (final E e : this) {
hashCode = 31 * hashCode + (e == null ? 0 : e.hashCode());
}
return hashCode;
}The reason we choose this is, firstly, because this feature can be mocked, but it has not been mocked yet, so we think about whether we can try to mock this feature. Secondly, this method involves hashcode, and we hope to mock more cases to verify whether it can run successfully.
First, we go to the pom.xml file to configure the maven required for mocking, as follows, here we use Mockito (version 4.3.1), Junit (version 5.8.2).
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
</dependency>Then the test starts, let's set up and use the mock function first.
@Before
public void setup(){
MockitoAnnotations.openMocks(this);
}Next, we use Mockito to test the function. We specifically use the verify() method, which can verify that we get the result we need.
@Test
void testHashCode() {
Element element = Mockito.mock(Element.class);
System.out.println(element.hashCode());
Mockito.verify(element).hashCode();
}https://livebook.manning.com/book/effective-unit-testing/chapter-7/ https://search.maven.org/artifact/org.mockito/mockito-all/2.0.2-beta/jar https://search.maven.org/artifact/org.junit.jupiter/junit-jupiter/5.8.2/jar
More information can be found on the Apache Commons Collections homepage. The Javadoc can be browsed. Questions related to the usage of Apache Commons Collections should be posted to the user mailing list.
You can download source and binaries from our download page.
Alternatively you can pull it from the central Maven repositories:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>We accept Pull Requests via GitHub. The developer mailing list is the main channel of communication for contributors. There are some guidelines which will make applying PRs easier for us:
- No tabs! Please use spaces for indentation.
- Respect the code style.
- Create minimal diffs - disable on save actions like reformat source code or organize imports. If you feel the source code should be reformatted create a separate PR for this change.
- Provide JUnit tests for your changes and make sure your changes don't break any existing tests by running
mvn clean test.
If you plan to contribute on a regular basis, please consider filing a contributor license agreement. You can learn more about contributing via GitHub in our contribution guidelines.
This code is under the Apache License v2.
See the NOTICE.txt file for required notices and attributions.
You like Apache Commons Collections? Then donate back to the ASF to support the development.
- Apache Commons Homepage
- Apache Issue Tracker (JIRA)
- Apache Commons Twitter Account
#apache-commonsIRC channel onirc.freenode.org












