vendredi 4 septembre 2015

simplificatoin of a factory for unit testing

I've got a complex object tree that I need to instantiate. The object tree is a bunch of specialized classes that don't have a common root object (other than Object) and has generic objects (that I need to configure), and custom objects (all the "My" classes in the example code).

I've created the factory below to encapsulate the construction of this object tree, but this factory just seems too complex and hard to understand. Also when I'm trying to write a unit test for this factory it just seems very fragile because the test ends up trying to traverse the entire object tree to verify that all of the objects have been configured correctly, or the test ends up trying to test the combined behaviour of all the component objects which is a duplication of the unit tests of each individual component object.

How can I change this factory to simplify unit testing the factory that produces this complex object tree?

public final class MyWidgetFactory
{
public static Widget createMyWidget(
        final ServiceA serviceA,
        final Factory<Metrics> metricsFactory,
        final String schemeName,
        final Serializer serializer)
{
    return createMyWidget(serviceA, metricsFactory, schemeName, serializer, CONTENT_TYPE_TO_DESCRIPTOR_MAP);
}

public static Widget createMyWidget(
        final ServiceA serviceA,
        final Factory<Metrics> metricsFactory,
        final String schemeName,
        final Serializer serializer,
        final Map<ContentType, MyFieldDescriptor> descriptorMap)
{
    final Widget proxied = new FilteringWidgetDecorator(
            new SimpleWidget(
                    getMyDataSource(serviceA, metricsFactory, descriptorMap),
                    getMyTransformer(metricsFactory,
                            schemeName,
                            descriptorMap,
                            serializer),
                    new NoValidator()),
            ImmutableList.of(
                    new ContentTypeWhilelistFilter(MY_WHITELISTED_CONTENT_TYPES)));

    return ProfiledInvocationHandler.getProfiledInstance(
            Widget.class, proxied, "MyWidget", metricsFactory);
}

private static Transformer getMyTransformer(
        final Factory<Metrics> metricsFactory,
        final String schemeName,
        final Map<ContentType, MyFieldDescriptor> descriptorMap,
        final Serializer serializer)
{
    final Transformer proxied =
            new FilteringTransformerDecorator(
                    ImmutableList.of(
                            new MyContentTypeFilter(
                                    getTransformerFilterVerificationStrategies(descriptorMap))),
                    true,
                    metricsFactory,
                    new StreamTransformer(
                            new SimpleNameSupplier(schemeName),
                            getSerializer(metricsFactory, serializer)));

    return ProfiledInvocationHandler.getProfiledInstance(
            Transformer.class, proxied, "MyTransformer", metricsFactory);
}

private static Map<ContentType, VerificationStrategy> getTransformerFilterVerificationStrategies(
        final Map<ContentType, MyFieldDescriptor> descriptorMap)
{
    return descriptorMap
            .entrySet()
            .stream()
            .collect(Collectors.toMap(
                    (entry) -> {
                        return entry.getKey();
                    },
                    (entry) -> {
                        return entry.getValue().getDataStrategy().getVerificationStrategy();
                    }));
}

private static Serializer getSerializer(final Factory<Metrics> metricsFactory,
        final Serializer serializer)
{
    return ProfiledInvocationHandler.getProfiledInstance(
            Serializer.class, serializer, "serializer", metricsFactory);
}

private static DataSource getMyDataSource(final ServiceA serviceA,
        final Factory<Metrics> metricsFactory,
        final Map<ContentType, MyFieldDescriptor> descriptorMap)
{
    final DataSource proxied = new JoiningDataSource(
            descriptorMap
                    .entrySet()
                    .stream()
                    .map(
                            (entry) -> {
                                final ContentType contentType = entry.getKey();
                                final MyFieldDescriptor descriptor = entry.getValue();
                                final DataStrategy dataStrategy = descriptor.getDataStrategy();

                                final DataSource dataSource =
                                        dataStrategy.getDataSource(serviceA, contentType);
                                if (descriptor.isRequired())
                                {
                                    return dataSource;
                                }
                                return new OptionalDataSource(dataSource);
                            })
                    .collect(Collectors.toList()));

    return ProfiledInvocationHandler.getProfiledInstance(
            DataSource.class, proxied, "MyDataSource", metricsFactory);

}


static enum DataStrategy
{
    METADATA_ONLY
    {
        @Override
        public DataSource getDataSource(final ServiceA serviceA, final ContentType contentType)
        {
            return new MyDataSource(
                    serviceA,
                    contentType,
                    IS_EMPTY_OR_NUMERIC_PREDICATE,
                    (dataType) -> {
                        return DataType.METADATA.equals(dataType);
                    });
        }

        @Override
        public VerificationStrategy getVerificationStrategy()
        {
            return VerificationStrategy.METADATA_ONLY;
        }
    },
    DATA_AND_METADATA
    {
        @Override
        public DataSource getDataSource(final ServiceA serviceA, final ContentType contentType)
        {
            return new MyDataSource(
                    serviceA,
                    contentType,
                    IS_EMPTY_OR_NUMERIC_PREDICATE,
                    (dataType) -> {
                        return true;
                    });
        }

        @Override
        public VerificationStrategy getVerificationStrategy()
        {
            return VerificationStrategy.ALL;
        }
    };

    public abstract DataSource getDataSource(ServiceA serviceA, ContentType contentType);

    public abstract VerificationStrategy getVerificationStrategy();
}


static final class MyFieldDescriptor
{
    private MyFieldDescriptor(final DataStrategy dataStrategy, final boolean isRequired)
    {
        mDataStrategy = Preconditions.checkNotNull(dataStrategy, "dataStrategy must not be null");
        mIsRequired = isRequired;
    }

    public DataStrategy getDataStrategy()
    {
        return mDataStrategy;
    }

    public boolean isRequired()
    {
        return mIsRequired;
    }

    public static final class Builder
    {
        public Builder withDataStrategy(final DataStrategy dataStrategy)
        {
            mDataStrategy = dataStrategy;
            return this;
        }

        public Builder withIsRequired(final boolean isRequired)
        {
            mIsRequired = isRequired;
            return this;
        }

        public MyFieldDescriptor build()
        {
            return new MyFieldDescriptor(mDataStrategy, mIsRequired);
        }

        private DataStrategy mDataStrategy;
        private boolean mIsRequired;
    }

    private final DataStrategy mDataStrategy;
    private final boolean mIsRequired;
}

static Predicate<Identifier> IS_EMPTY_OR_NUMERIC_PREDICATE =
        (identifier) -> {
            final String resourceId = identifier.getResourceIdentifier();
            return resourceId.isEmpty() || StringUtils.isNumeric(resourceId);
        };

static final Set<ContentType> MY_WHITELISTED_CONTENT_TYPES = ImmutableSet.of(
        ContentType.HTML);

static final Map<ContentType, MyFieldDescriptor> CONTENT_TYPE_TO_DESCRIPTOR_MAP =
        ImmutableMap.<ContentType, MyFieldDescriptor> builder()
                .put(ContentType.HTML,
                        new MyFieldDescriptor.Builder()
                                .withDataStrategy(DataStrategy.DATA_AND_METADATA)
                                .withIsRequired(true)
                                .build())
                .put(ContentType.EMAIL,
                        new MyFieldDescriptor.Builder()
                                .withDataStrategy(DataStrategy.DATA_AND_METADATA)
                                .withIsRequired(false)
                                .build())
                .put(ContentType.IMAGE,
                        new MyFieldDescriptor.Builder()
                                .withDataStrategy(DataStrategy.METADATA_ONLY)
                                .withIsRequired(false)
                                .build())
                .put(ContentType.VIDEO,
                        new MyFieldDescriptor.Builder()
                                .withDataStrategy(DataStrategy.METADATA_ONLY)
                                .withIsRequired(false)
                                .build())
                .build();
}

Aucun commentaire:

Enregistrer un commentaire