averbraeck / opentrafficsim

Open Source Multi-Level Traffic Simulator
BSD 3-Clause "New" or "Revised" License
29 stars 10 forks source link

Generic Exception test code and test code dependency #157

Open WJSchakel opened 1 day ago

WJSchakel commented 1 day ago

For any exception class that we have, we run the same test code using the same constructors. We can implement all this once using reflection based on a Class<? extends OtsException>. Test code from one project is however not available in another project. There are 3 possible solutions:

  1. Provide this test in production code. This is misuse of production code; no test code should end up in published jars.
  2. Copy the generic code. This requires manual maintenance.
  3. Make test code available by the method described below.

At https://maven.apache.org/guides/mini/guide-attached-tests.html it is described how test code from one project, can be made available in another project. In summary:

Property <maven.jar.version>3.4.2</maven.jar.version> is made available in the main pom.

In the main pom the following plugin is added:

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>${maven.jar.version}</version>
        <executions>
          <execution>
            <goals>
              <goal>test-jar</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

In the pom of any project that needs the generic code (assumed to be in ots-base) the following can be added:

    <dependency>
      <groupId>org.opentrafficsim</groupId>
      <artifactId>ots-base</artifactId>
      <version>${ots.version}</version>
      <classifier>tests</classifier>
      <type>test-jar</type>
      <scope>test</scope>
    </dependency>

This dependency is not forwarded to further dependent projects. Each individual project that needs it, needs to define this dependency.

Projects that a project depends on are listed under maven dependencies in the package explorer. Normally this is followed by [without test code]. This disappears for ots-base with the above addition. Test code in ots-base is now available only in test code of the depending project.

I'm not sure what this does in the background. If it does not affect published jars, this would be a convenient way to do it.

Should we do this?

WJSchakel commented 1 day ago

The following code implements a generic test for exceptions. Note that we have to use Class<? extends Exception> rather than Class<? extends OtsException> because OtsRuntimeException has to extend RuntimeException and therefore cannot extend OtsException.

    /**
     * Test exception class on constructors: empty, only message, only Throwable, message and Throwable.
     * @param clazz exception class to test.
     */
    private void testExceptionClass(final Class<? extends Exception> clazz)
    {
        try
        {
            Constructor<? extends Exception> constructor = clazz.getConstructor();
            try
            {
                throw constructor.newInstance();
            }
            catch (Exception e)
            {
                checkRightException(e, clazz);
                assertNull(e.getMessage());
            }

            constructor = clazz.getConstructor(String.class);
            try
            {
                throw constructor.newInstance("abc");
            }
            catch (Exception e)
            {
                checkRightException(e, clazz);
                assertEquals("abc", e.getMessage());
            }

            constructor = clazz.getConstructor(Throwable.class);
            try
            {
                throw constructor.newInstance(new IllegalArgumentException());
            }
            catch (Exception e)
            {
                checkRightException(e, clazz);
                assertTrue(e.getMessage().contains("IllegalArgumentException"));
                assertTrue(e.getCause() instanceof IllegalArgumentException);
            }

            constructor = clazz.getConstructor(String.class, Throwable.class);
            try
            {
                throw constructor.newInstance("abc", new IllegalArgumentException("def"));
            }
            catch (Exception e)
            {
                checkRightException(e, clazz);
                assertEquals("abc", e.getMessage());
                assertTrue(e.getCause() instanceof IllegalArgumentException);
                assertEquals("def", e.getCause().getMessage());
            }
        }
        catch (SecurityException | NoSuchMethodException ex)
        {
            fail("Class " + clazz + " is missing standard and accessible exception constructor.");
        }
    }

    /**
     * Test exception class type.
     * @param e thrown exception
     * @param clazz class of exception that should have been thrown
     */
    private void checkRightException(final Exception e, final Class<? extends Exception> clazz)
    {
        if (!e.getClass().equals(clazz))
        {
            fail("Right exception not thrown");
        }
    }