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 ////