Cppunit

From campisano.org
Jump to navigation Jump to search

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


References