Deductive Reasoning for Integration Test Combos

Published: 11 November, 2014

Consider the following argument:

a = "Socrates is a man."
b = "All men are mortal."
c = "Socrates is mortal."

Deductive reasoning can be useful for constructing unit/integration test combos that guarantee a class that works with one implementations of an interface will work for all implementations of that interface.

This example shows how to construct a group of tests that will verify a MessageQueue Consumer will work when using a remote MessageQueue service (i.e. Amazon SQS) without directly testing the class with the high-latency remote MessageQueue.

To understand this solution at the propositional logic level, consider these statements:

a = "ArrayMessageQueue and SqsMessageQueue are valid MessageQueue implementations"
b = "QueueConsumer works with ArrayMessageQueue implementation"
c = "QueueConsumer works with all MessageQueue implementations"

If a and b are true, then it follows that c is true.

Some example code demonstrating the implementations and tests are below:

    class Message {
        public Integer id;
        public String body;
    }

    interface MessageQueue {
        Message getMessage() throws EmptyQueueException;
        void deleteMessage(Integer id);
    }

    class SqsMessageQueue implements MessageQueue {
        private AmazonSQSClient client;
        public SqsMessageQueue(AmazonSQSClient client) {
            this.client = client;
        }
        public Message getMessage() throws EmptyQueueException {
            // Client calls to get message...
        }
        void deleteMessage(Integer id) {
            // Client calls to delete message...
        }
    }

    class ArrayMessageQueue implements MessageQueue {
        private ArrayList<Message> messages;
        public ArrayMessageQueue() {
            this.messages = new ArrayList();
        }
        public Message getMessage() throws EmptyQueueException {
            // Get a message from array
        }
        public void deleteMessage(Integer id) {
            // Delete a message from array
        }
    }
    
    class SqsMessageQueueTest {
        public testGetMessage() {
            // Set up queue and make sure we can get message
        }
        public testDeleteMessage() {
            // Set up queue and make sure we can delete messages
        }
    }

    class ArrayMessageQueueTest {
        public testGetMessage() {
            // Set up queue and make sure we can get message
        }
        public testDeleteMessage() {
            // Set up queue and make sure we can delete messages
        }
    }
    
    class QueueConsumer {
        private MessageQueue queue;
        private Integer messagesConsumed = 0;

        public QueueConsumer(MessageQueue queue) {
            this.queue = queue;
        }

        public void consume() {
            try {
                Message message = queue.getMessage();
                System.out.println("Consuming message: " + message.body);
                queue.delete(message);
                messagesConsumed++;
            }
            catch (EmptyQueueException e) {
                System.out.println("No messages to consume");
            }
        }

        public Integer getMessagesConsumed() {
            return messagesConsumed;
        }
    }
    
    class QueueConsumerTest {
        public testConsume() {
            MessageQueue queue = new ArrayMessageQueue();
            queue.addMessage("hello");
            queue.addMessage("world");

            QueueConsumer consumer = new QueueConsumer(queue);
            consumer.consume();
            consumer.consume();
            assertEquals(2, consumer.getMessagesConsumed());
        }
    }
    

Since we have tested QueueConsumer with ArrayMessageQueue, and have tested that ArrayMessageQueue and SqsMessageQueue implement the MessageQueue interface correctly, we can safely assume QueueConsumer will work with SqsMessageQueue without having to test against the external message queue directly. This technique allows you to swap local implementations for remote services during tests.

Comments