Cppunit
CppUnit HowTo
...This article is a STUB...
First of all, we need dev packages of cppunit library: see your distribution-specific package manager to obtain it. Example: for debian users, a simple apt-get install libcppunit-dev should work.
Let's go to define some behaviors to check:
Our client ask us to implement his online store, and he define some rules for his customers doing an order:
- guest client can do an order with a open state;
- a valid order must indicate a registered customer, at least a product, a shipping address and a payment method.
So, to implement this behaviors, we can define 5 test:
//// TestOrder.h ////
#ifndef TEST_ORDER_H_
#define TEST_ORDER_H_
#include <cppunit/extensions/HelperMacros.h>
class TestOrder : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestOrder);
CPPUNIT_TEST(testNewOrderStateOpen);
CPPUNIT_TEST(testValidOrderMustHaveCustomer);
CPPUNIT_TEST(testValidOrderMustHaveSomeProduct);
CPPUNIT_TEST(testValidOrderMustHaveShippingAddress);
CPPUNIT_TEST(testValidOrderMustHavePaymentMethod);
CPPUNIT_TEST_SUITE_END();
public:
void testNewOrderStateOpen();
void testValidOrderMustHaveCustomer();
void testValidOrderMustHaveSomeProduct();
void testValidOrderMustHaveShippingAddress();
void testValidOrderMustHavePaymentMethod();
};
#endif
//// END TestOrder.h ////
//// TestOrder.cpp ////
#include "TestOrder.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestOrder);
void TestOrder::testNewOrderStateOpen()
{
CPPUNIT_FAIL("Test not implemented");
}
void TestOrder::testValidOrderMustHaveCustomer()
{
CPPUNIT_FAIL("Test not implemented");
}
void TestOrder::testValidOrderMustHaveSomeProduct()
{
CPPUNIT_FAIL("Test not implemented");
}
void TestOrder::testValidOrderMustHaveShippingAddress()
{
CPPUNIT_FAIL("Test not implemented");
}
void TestOrder::testValidOrderMustHavePaymentMethod()
{
CPPUNIT_FAIL("Test not implemented");
}
//// END TestOrder.cpp ////
Now, to use our TestOrder class, we need set up a program to run the test
//// test_main.cpp ////
#include <cppunit/CompilerOutputter.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
int main(int nArgs, char* aryArgs[])
{
CppUnit::Test* pntCppTest = CppUnit::TestFactoryRegistry::getRegistry().makeTest();
CppUnit::TextUi::TestRunner objCppRunner;
objCppRunner.addTest(pntCppTest);
objCppRunner.setOutputter(new CppUnit::CompilerOutputter(&objCppRunner.result(), std::cerr));
bool bSuccess = objCppRunner.run();
return bSuccess ? 0 : 1;
}
//// END test_main.cpp ////
to compile and run our source we'll doing a simple script:
//// testAndRun.sh ////
#!/bin/bash g++ -c test_main.cpp -o test_main.o || exit -1 g++ -c TestOrder.cpp -o TestOrder.o || exit -2 g++ -lcppunit test_main.o TestOrder.o -o testMain || exit -3 ./testMain exit $?
//// END testAndRun.sh ////
if all was ok, executing our script with
sh testAndRun.sh
will report 5 failed tests of 5 total tests: wonderful!
Now, happy refactoring!
A basic example for an effective TestOrder class:
//// TestOrder.cpp ////
#include "TestOrder.h"
#include "Order.h"
#include "Customer.h"
#include "PaymentMethod.h"
#include "Product.h"
#include "ShippingAddress.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestOrder);
void TestOrder::testNewOrderStateOpen()
{
// Arrange, act
Order objOrder(123);
// Assert
CPPUNIT_ASSERT(objOrder.getState() == Order::StateOpened);
}
void TestOrder::testValidOrderMustHaveCustomer()
{
// Arrange
Customer objCustomer(123);
Order objOrder(123);
PaymentMethod objPaymentMethod(123);
Product objProduct(123);
ShippingAddress objShippingAddress(123);
// Act
objOrder.setPaymentMethod(objPaymentMethod);
objOrder.addProduct(objProduct);
objOrder.setShippingAddress(objShippingAddress);
// Assert
CPPUNIT_ASSERT(!(objOrder.isValid()));
objOrder.setCustomer(objCustomer);
CPPUNIT_ASSERT(objOrder.isValid());
}
void TestOrder::testValidOrderMustHaveSomeProduct()
{
// Arrange
Customer objCustomer(123);
Order objOrder(123);
PaymentMethod objPaymentMethod(123);
Product objProduct(123);
ShippingAddress objShippingAddress(123);
// Act
objOrder.setCustomer(objCustomer);
objOrder.setPaymentMethod(objPaymentMethod);
objOrder.setShippingAddress(objShippingAddress);
// Assert
CPPUNIT_ASSERT(!(objOrder.isValid()));
objOrder.addProduct(objProduct);
CPPUNIT_ASSERT(objOrder.isValid());
}
void TestOrder::testValidOrderMustHaveShippingAddress()
{
// Arrange
Customer objCustomer(123);
Order objOrder(123);
PaymentMethod objPaymentMethod(123);
Product objProduct(123);
ShippingAddress objShippingAddress(123);
// Act
objOrder.setCustomer(objCustomer);
objOrder.setPaymentMethod(objPaymentMethod);
objOrder.addProduct(objProduct);
// Assert
CPPUNIT_ASSERT(!(objOrder.isValid()));
objOrder.setShippingAddress(objShippingAddress);
CPPUNIT_ASSERT(objOrder.isValid());
}
void TestOrder::testValidOrderMustHavePaymentMethod()
{
// Arrange
Customer objCustomer(123);
Order objOrder(123);
PaymentMethod objPaymentMethod(123);
Product objProduct(123);
ShippingAddress objShippingAddress(123);
// Act
objOrder.setCustomer(objCustomer);
objOrder.addProduct(objProduct);
objOrder.setShippingAddress(objShippingAddress);
// Assert
CPPUNIT_ASSERT(!(objOrder.isValid()));
objOrder.setPaymentMethod(objPaymentMethod);
CPPUNIT_ASSERT(objOrder.isValid());
}
//// END TestOrder.cpp ////
we need an Order class
//// Order.h ////
#ifndef ORDER_H_
#define ORDER_H_
#include <list>
#include "Entity.h"
#include "EntityId.h"
class Customer;
class PaymentMethod;
class Product;
class ShippingAddress;
class Order: public Entity
{
public:
enum eOrderState
{
StateOpened,
StateValidated,
StateAccepted,
StatePayed,
StateShipped
};
public:
explicit Order(EntityId _id);
eOrderState getState();
bool isValid();
bool addProduct(Product& _objProdct);
bool setCustomer(Customer& _objCustomer);
bool setPaymentMethod(PaymentMethod& _objPaymentMethod);
bool setShippingAddress(ShippingAddress& _objShippingAddress);
private:
eOrderState m_state;
std::list<EntityId> m_lstIdProduct;
EntityId m_idCustomer;
EntityId m_idPaymentMethod;
EntityId m_idShippingAddress;
};
#endif
//// End Order.h ////
//// Order.cpp ////
#include "Order.h"
#include "Customer.h"
#include "PaymentMethod.h"
#include "Product.h"
#include "ShippingAddress.h"
Order::Order(EntityId _id)
: Entity(_id)
{
m_state = Order::StateOpened;
m_idCustomer = 0;
m_idShippingAddress = 0;
m_idPaymentMethod = 0;
}
Order::eOrderState Order::getState()
{
return m_state;
}
bool Order::isValid()
{
return (m_idCustomer != 0 &&
m_lstIdProduct.size() > 0 &&
m_idShippingAddress != 0 &&
m_idPaymentMethod != 0);
}
bool Order::addProduct(Product& _objProdct)
{
if (_objProdct.getId() == 0)
{
return false;
}
m_lstIdProduct.push_back(_objProdct.getId());
return true;
}
bool Order::setCustomer(Customer& _objCustomer)
{
if (_objCustomer.getId() == 0)
{
return false;
}
m_idCustomer = _objCustomer.getId();
return true;
}
bool Order::setPaymentMethod(PaymentMethod& _objPaymentMethod)
{
if (_objPaymentMethod.getId() == 0)
{
return false;
}
m_idPaymentMethod = _objPaymentMethod.getId();
return true;
}
bool Order::setShippingAddress(ShippingAddress& _objShippingAddress)
{
if (_objShippingAddress.getId() == 0)
{
return false;
}
m_idShippingAddress = _objShippingAddress.getId();
return true;
}
//// End Order.cpp ////
the Entity is common for all the serializable class, the implement an id member and a function to get the id.
//// Entity.h ////
#ifndef ENTITY_H_
#define ENTITY_H_
#include "EntityId.h"
class Entity
{
public:
explicit Entity(EntityId _id);
EntityId getId();
protected:
void setId(EntityId _id);
private:
EntityId m_id;
};
#endif
//// End Entity.h ////
//// Entity.cpp ////
#include "Entity.h"
Entity::Entity(EntityId _id)
: m_id(_id)
{
}
EntityId Entity::getId()
{
return m_id;
}
void Entity::setId(EntityId _id)
{
m_id = _id;
}
//// End Entity.cpp ////
The id type is defined as EntityId type, defined as a long to give a easy way to change all the id reference for another type:
//// EntityId.h ////
#ifndef ENTITY_ID_H_ #define ENTITY_ID_H_ typedef long EntityId; #endif
//// End EntityId.h ////
The other class are free of logic, for now:
//// Product.h ////
#ifndef PRODUCT_H_
#define PRODUCT_H_
#include "Entity.h"
#include "EntityId.h"
class Product : public Entity
{
public:
explicit Product(EntityId _id);
};
#endif
//// End Product.h ////
//// Product.cpp ////
#include "Product.h"
Product::Product(EntityId _id)
: Entity(_id)
{
}
//// End Product.cpp ////
//// Customer.h ////
#ifndef CUSTOMER_H_
#define CUSTOMER_H_
#include "Entity.h"
#include "EntityId.h"
class Customer: public Entity
{
public:
explicit Customer(EntityId _id);
};
#endif
//// End Customer.h ////
//// Customer.cpp ////
#include "Customer.h"
Customer::Customer(EntityId _id)
: Entity(_id)
{
}
//// End Customer.cpp ////
//// PaymentMethod.h ////
#ifndef PAYMENT_METHOD_H_
#define PAYMENT_METHOD_H_
#include "Entity.h"
#include "EntityId.h"
class PaymentMethod : public Entity
{
public:
explicit PaymentMethod(EntityId _id);
};
#endif
//// End PaymentMethod.h ////
//// PaymentMethod.cpp ////
#include "PaymentMethod.h"
PaymentMethod::PaymentMethod(EntityId _id)
: Entity(_id)
{
}
//// End PaymentMethod.cpp ////
//// ShippingAddress.h ////
#ifndef SHIPPING_ADDRESS_H_
#define SHIPPING_ADDRESS_H_
#include "Entity.h"
#include "EntityId.h"
class ShippingAddress : public Entity
{
public:
explicit ShippingAddress(EntityId _id);
};
#endif
//// End ShippingAddress.h ////
//// ShippingAddress.cpp ////
#include "ShippingAddress.h"
ShippingAddress::ShippingAddress(EntityId _id)
: Entity(_id)
{
}
//// End ShippingAddress.cpp ////