Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

These are tests which test just how one object works. Typically test each method on an object for expected output in several situations. They are executed exclusively at the API level.

Specific Tools

Architecture

We can consider two types of classes when developing the unit tests: classes which have a dependency on the database and classes that don't. The classes that don't can be tested easily, using standard procedures and tests. Our main problem are classes tighly coupled with the database and its helper objects, like BitstreamFormat or the classes that inherit from DSpaceObject. This section focussing on unit tests means we don't have any database available, which in turn means we would not be able to test some of the methods. We have the following options:

* Ignore the methods

* Create some mocks for the database and helper objects

The second option is prefered, although the fact we are using mocks means we won't have errors as we will be getting a constant valid result. At this point there is a choice: we can create big standard mock objects (with fixed output) to be used in all our tests, or we can create custom mocks for every test. The first option means we won't be able to test error cases as the output will be always the same for a given mocked call in a mocked object. The second option ties our mocks to the current implementation of the method, as we would need to create specific mocks for the methods called from the method we are testing.

To preserve the encapsulation, and given we will have a suite of integrationt tests that will deal with the database-related methods properly, the first option has been implemented. As a result a set of objects (MockContext, MockDatabaseManager, MockResultSet and more) has been created and used for the tests. All the methods (like DatabaseManager.find() ) that depend on the database will return mock objects with a constant value, and the real testing will be deferred to the Integration Tests, keeping the unit test just as a thin verification layer against big errors in code. The other methods, which don't depend on the database, will be tested normally.

There is a base The cornerstone of our Unit Tests is a class called "AbstractUnitTest". This class contains a series of mocks and references which are necessary to run the tests in DSpace, due to the heavy dependency on Context and Database of the application. As JUnit (since 4.5) doesn't require you to extend any class, we can extend this class with our unit tests and centralise the scaffolding for the tests in one part of the code. The class is located under the package "org.dspace" in the test folder of DSpace-apilike mocks of the Context object. All Unit Tests should inherit this class, located under the package "org.dspace" in the test folder of DSpace-api.

About the implementation, several objects only offer a hidden constructor and a factory method to create an instance of the object. As this factory method depends on the database, we can't rely on it to create instances of our objects. The Reflection API has been used to create the required instances for the unit tests.

To summarise, the following issues have been detected in the code, which make Unit Testing harder and impact the maintability of the code:

* Hidden dependencies. Many objects require other objects (like DatabaseManager) but the dependency is never explicitely declared or set. These dependencies should be fulfilled as parameters in the constructors or factory methods.

* Hidden constructors. It would be advisable to have public constructors in the objects and provide a Factory class that manages the instantiation of all required entities. This would facilitate testing and provide a separation of concerns, as the inclusion of the factory methods inside objects usually adds hidden dependencies (see previous point).

Refactoring would be required to fix these issues.

Integration Tests

These tests work at the API level and test the interaction of components within the system. Some examples, placing an item into a collection, or creating a new metadata schema and adding some fields. Primarily these tests operate at the API level ignoring the interface components above it.

...