All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.meanbean.test.BeanTester Maven / Gradle / Ivy

Go to download

Mean Bean is an open source Java test library that tests equals and hashCode contract compliance, as well as JavaBean/POJO getter and setter methods.

There is a newer version: 2.0.3
Show newest version
package org.meanbean.test;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.meanbean.bean.info.BeanInformation;
import org.meanbean.bean.info.BeanInformationFactory;
import org.meanbean.bean.info.JavaBeanInformationFactory;
import org.meanbean.bean.info.PropertyInformation;
import org.meanbean.bean.util.PropertyInformationFilter;
import org.meanbean.bean.util.PropertyInformationFilter.PropertyVisibility;
import org.meanbean.factories.BasicNewObjectInstanceFactory;
import org.meanbean.factories.FactoryCollection;
import org.meanbean.factories.FactoryRepository;
import org.meanbean.factories.util.BasicFactoryLookupStrategy;
import org.meanbean.factories.util.FactoryLookupStrategy;
import org.meanbean.lang.Factory;
import org.meanbean.util.RandomValueGenerator;
import org.meanbean.util.SimpleRandomValueGenerator;
import org.meanbean.util.SimpleValidationHelper;
import org.meanbean.util.ValidationHelper;

/**
 * 

* Concrete BeanTester that affords a means of testing JavaBean objects with respect to: *

* *
    *
  • the correct functioning of the object's public getter and setter methods
  • *
* *

* Each property is tested by: *

* *
    *
  1. generating a random test value for the specific property type
  2. * *
  3. invoking the property setter method, passing the generated test value
  4. * *
  5. invoking the property getter method and obtaining the return value
  6. * *
  7. verifying that the value obtained from the getter method matches the value passed to the setter method
  8. *
* *

* Each property of a type is tested in turn. Each type is tested multiple times to reduce the risk of hard-coded values * within a getter or setter matching the random test values generated and the test failing to detect a bug.
*

* *

* Testing can be configured as follows: *

* *
    *
  • the number of times each type is tested can be configured
  • * *
  • the properties to test can be configured by specifying properties to ignore on a type
  • * *
  • custom Factories can be registered to create test values during testing
  • *
* *

* See: *

* *
    *
  • setIterations(int) to set the number of times any type is tested
  • * *
  • addCustomConfiguration(Class,Configuration) to add a custom Configuration across all testing of * the specified type
  • * *
  • testBean(Class,Configuration) to specify a custom Configuration for a single test scenario
  • * *
  • getFactoryCollection().addFactory(Class,Factory) to add a custom Factory for a type across all * testing
  • *
* *

* The following example shows how to test a class MyClass: *

* *
 * BeanTester beanTester = new BeanTester();
 * beanTester.testBean(MyClass.class);
 * 
* *

* If the test fails, an AssertionError is thrown.
*

* *

* To ignore a property (say, lastName) when testing a class: *

* *
 * BeanTester beanTester = new BeanTester();
 * Configuration configuration = new ConfigurationBuilder().ignoreProperty("lastName").build();
 * beanTester.testBean(MyClass.class, configuration);
 * 
* * @author Graham Williamson */ public class BeanTester { /** Default number of times a bean should be tested. */ public static final int TEST_ITERATIONS_PER_BEAN = 100; /** The number of times each bean is tested, unless a custom Configuration overrides this global setting. */ private int iterations = TEST_ITERATIONS_PER_BEAN; /** Random number generator used by factories to randomly generate values. */ private final RandomValueGenerator randomValueGenerator = new SimpleRandomValueGenerator(); /** The collection of test data Factories. */ private final FactoryCollection factoryCollection = new FactoryRepository(randomValueGenerator); /** Provides a means of acquiring a suitable Factory. */ private final FactoryLookupStrategy factoryLookupStrategy = new BasicFactoryLookupStrategy(factoryCollection, randomValueGenerator); /** Custom Configurations that override standard testing behaviour on a per-type basis across all tests. */ private final Map, Configuration> customConfigurations = Collections .synchronizedMap(new HashMap, Configuration>()); /** Factory used to gather information about a given bean and store it in a BeanInformation object. */ private final BeanInformationFactory beanInformationFactory = new JavaBeanInformationFactory(); /** Object that tests the getters and setters of a Bean's property. */ private final BeanPropertyTester beanPropertyTester = new BeanPropertyTester(); /** Logging mechanism. */ private final Log log = LogFactory.getLog(BeanTester.class); /** Input validation helper. */ private final ValidationHelper validationHelper = new SimpleValidationHelper(log); /** * The collection of test data Factories with which you can register new Factories for custom Data Types. * * @return The collection of test data Factories. */ public FactoryCollection getFactoryCollection() { return factoryCollection; } /** * Get a RandomNumberGenerator. * * @return A RandomNumberGenerator. */ public RandomValueGenerator getRandomValueGenerator() { return randomValueGenerator; } /** *

* Set the number of times each bean should be tested, globally.
*

* *

* Note: A custom Configuration can override this global test setting. *

* * @param iterations * The number of times each bean should be tested. This value must be at least 1. * * @throws IllegalArgumentException * If the iterations parameter is deemed illegal. For example, if it is less than 1. */ public void setIterations(int iterations) throws IllegalArgumentException { log.debug("setIterations: entering with iterations=[" + iterations + "]."); if (iterations < 1) { log.debug("setIterations: Iterations must be at least 1. Throw IllegalArgumentException."); throw new IllegalArgumentException("Iterations must be at least 1."); } this.iterations = iterations; log.debug("setIterations: exiting."); } /** *

* Get the number of times each bean should be tested.
*

* *

* Note: A custom Configuration can override this global setting. *

* * @return The number of times each bean should be tested. This value will be at least 1. */ public int getIterations() { return iterations; } /** * Add the specified Configuration as a custom Configuration to be used as an override to any global configuration * settings when testing the type specified by the beanClass parameter. * * @param beanClass * The type that the Configuration should be used for during testing. * @param configuration * The custom Configuration, to be used only when testing the beanClass type. * * @throws IllegalArgumentException * If either parameter is deemed illegal. For example, if either parameter is null. */ public void addCustomConfiguration(Class beanClass, Configuration configuration) throws IllegalArgumentException { log.debug("addCustomConfiguration: entering with beanClass=[" + beanClass + "], configuration=[" + configuration + "]."); validationHelper.ensureExists("beanClass", "add custom configuration", beanClass); validationHelper.ensureExists("configuration", "add custom configuration", configuration); customConfigurations.put(beanClass, configuration); log.debug("addCustomConfiguration: exiting."); } /** * Does the specified type have a custom Configuration registered? * * @param beanClass * The type for which a Configuration is sought. * * @return true if a custom Configuration has been registered for the type specified by the beanClass * parameter; false otherwise. * * @throws IllegalArgumentException * If the beanClass parameter is deemed illegal. For example, if it is null. */ protected boolean hasCustomConfiguration(Class beanClass) throws IllegalArgumentException { log.debug("hasCustomConfiguration: entering with beanClass=[" + beanClass + "]."); validationHelper.ensureExists("beanClass", "check for custom configuration", beanClass); boolean result = customConfigurations.containsKey(beanClass); log.debug("hasCustomConfiguration: exiting returning [" + result + "]."); return result; } /** * Get the custom Configuration registered against the specified type. * * @param beanClass * The type for which a Configuration is sought. * * @return A custom Configuration registered against the type specified by the beanClass parameter, if one exists; * null otherwise. * * @throws IllegalArgumentException * If the beanClass parameter is deemed illegal. For example, if it is null. */ protected Configuration getCustomConfiguration(Class beanClass) throws IllegalArgumentException { log.debug("getCustomConfiguration: entering with beanClass=[" + beanClass + "]."); validationHelper.ensureExists("beanClass", "get custom configuration", beanClass); Configuration result = customConfigurations.get(beanClass); log.debug("getCustomConfiguration: exiting returning [" + result + "]."); return result; } /** *

* Test the type specified by the beanClass parameter. *

* *

* Testing will test each publicly readable and writable property of the specified beanClass to ensure that the * getters and setters function correctly. *

* *

* The test is performed repeatedly using random data for each scenario to prevent salient getter/setter failures. *

* *

* When a test is failed, an AssertionError is thrown. *

* * @param beanClass * The type to be tested. * * @throws IllegalArgumentException * If the beanClass is deemed illegal. For example, if it is null. * @throws AssertionError * If the bean fails the test. * @throws BeanTestException * If an unexpected exception occurs during testing. */ public void testBean(Class beanClass) throws IllegalArgumentException, AssertionError, BeanTestException { log.debug("testBean: entering with beanClass=[" + beanClass + "]."); validationHelper.ensureExists("beanClass", "test bean", beanClass); Configuration customConfiguration = null; if (hasCustomConfiguration(beanClass)) { customConfiguration = getCustomConfiguration(beanClass); } testBean(beanClass, customConfiguration); log.debug("testBean: exiting."); } /** *

* Test the type specified by the beanClass parameter, using the custom Configuration provided as an override to any * global configuration settings.
*

* *

* Testing will test each publicly readable and writable property of the specified beanClass to ensure that the * getters and setters function correctly.
*

* *

* The test is performed repeatedly using random data for each scenario to prevent salient getter/setter failures.
*

* *

* When a test is failed, an AssertionError is thrown. *

* * @param beanClass * The type to be tested. * @param customConfiguration * The custom Configuration to be used when testing the specified beanClass type. This Configuration is * only used for this individual test and will not be retained for future testing of this or any other * type. To register a custom Configuration across multiple tests, use * addCustomConfiguration(). If no custom Configuration is required, pass null * or use testBean(Class) instead. * * @throws IllegalArgumentException * If the beanClass is deemed illegal. For example, if it is null. * @throws AssertionError * If the bean fails the test. * @throws BeanTestException * If an unexpected exception occurs during testing. */ public void testBean(Class beanClass, Configuration customConfiguration) throws IllegalArgumentException, AssertionError, BeanTestException { log.debug("testBean: entering with beanClass=[" + beanClass + "], customConfiguration=[" + customConfiguration + "]."); validationHelper.ensureExists("beanClass", "test bean", beanClass); // Override the standard number of iterations if need be int iterations = this.iterations; if ((customConfiguration != null) && (customConfiguration.hasIterationsOverride())) { iterations = customConfiguration.getIterations(); } // Get all information about a potential JavaBean class BeanInformation beanInformation = beanInformationFactory.create(beanClass); // Test the JavaBean 'iterations' times for (int idx = 0; idx < iterations; idx++) { log.debug("testBean: Iteration [" + idx + "]."); testBean(beanInformation, customConfiguration); } log.debug("testBean: exiting."); } /** *

* Test the type specified by the beanInformation parameter using the specified Configuration.
*

* *

* Testing will test each publicly readable and writable property of the specified beanClass to ensure that the * getters and setters function correctly.
*

* *

* The test is performed repeatedly using random data for each scenario to prevent salient getter/setter failures.
*

* *

* When a test is failed, an AssertionError is thrown. *

* * @param beanInformation * Information about the type to be tested. * @param configuration * The custom Configuration to be used when testing the specified beanClass type. If no custom * Configuration is required, pass null or use testBean(Class) instead. * * @throws IllegalArgumentException * If the beanInformation is deemed illegal. For example, if it is null. * @throws AssertionError * If the bean fails the test. * @throws BeanTestException * If an unexpected exception occurs during testing. */ protected void testBean(BeanInformation beanInformation, Configuration configuration) throws IllegalArgumentException, AssertionError, BeanTestException { log.debug("testBean: entering with beanInformation=[" + beanInformation + "], configuration=[" + configuration + "]."); validationHelper.ensureExists("beanInformation", "test bean", beanInformation); // Get all properties of the bean Collection properties = beanInformation.getProperties(); // Get just the properties of the bean that are readable and writable Collection readableWritableProperties = PropertyInformationFilter.filter(properties, PropertyVisibility.READABLE_WRITABLE); // Instantiate BasicNewObjectInstanceFactory beanFactory = new BasicNewObjectInstanceFactory(beanInformation.getBeanClass()); Object bean; try { bean = beanFactory.create(); } catch (Exception e) { String message = "Cannot test bean [" + beanInformation.getBeanClass().getName() + "]. Failed to instantiate an instance of the bean."; log.error("testBean: " + message + " Throw BeanTestException.", e); throw new BeanTestException(message, e); } // Test each property for (PropertyInformation property : readableWritableProperties) { // Skip testing any 'ignored' properties if ((configuration == null) || (!configuration.isIgnoredProperty(property.getName()))) { EqualityTest equalityTest = EqualityTest.LOGICAL; Object testValue = null; try { Factory valueFactory = factoryLookupStrategy.getFactory(beanInformation, property.getName(), property.getWriteMethodParameterType(), configuration); testValue = valueFactory.create(); if (valueFactory instanceof BasicNewObjectInstanceFactory) { equalityTest = EqualityTest.ABSOLUTE; } } catch (Exception e) { String message = "Cannot test bean [" + beanInformation.getBeanClass().getName() + "]. Failed to instantiate a test value for property [" + property.getName() + "]."; log.error("testBean: " + message + " Throw BeanTestException.", e); throw new BeanTestException(message, e); } beanPropertyTester.testProperty(bean, property, testValue, equalityTest); } } log.debug("testBean: exiting."); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy