480 likes | 681 Vues
DO YOU…. TEST YOUR CODE?. Leandro Fernandez 21-February-2012. bugs. test. stress. Michael C. Feathers. WHOLE SYSTEM. INSOLATE COMPONENT. UNIT- TEST. Super fast. Easy to Identify what is wrong. No need of GDB. DO YOU “UNIT-TEST” YOUR CODE?. CODE WITHOUT UNIT-TEST
E N D
DO YOU… TESTYOUR CODE? Leandro Fernandez 21-February-2012
bugs test stress Michael C. Feathers
Easy to Identify what is wrong
No need of GDB
DO YOU “UNIT-TEST” YOUR CODE?
CODE WITHOUT UNIT-TEST IS NOT A GOOD CODE
CODE WITHOUT UNIT-TEST HAS NOT A GOOD DESIGN
TEST DRIVER CLASS UNDER TEST Stimulus Assertions
GOOGLE TEST class Rectangle { public: Rectangle(int a, int b): a_(a), b_(b){}; intgetArea(){return(a_*b_)}; intgetPerimeter(){return(2*a_+2*b_)}; } TEST(RectangleTest, TestGetArea) { Rectangle rectangle(2,3); EXPECT_EQ(6, rectangle.getArea()); } Test Test case Test program TEST(RectangleTest, TestGetPerimeter) { Rectangle rectangle(2,3); EXPECT_EQ(10, rectangle.getPerimeter()); } Test
GOOGLE TEST RICH ASSERTIONS ASSERT_* EXPECT_*
GOOGLE TEST TEST FIXTURES Class RectangleTest : public ::testing::Test { Rectangle rectangle_(2,3); } TEST_F(RectangleTest, TestGetArea) { EXPECT_EQ(6, rectangle_.getArea()); } TEST_F(TestRectangle, TestGetPerimeter) { EXPECT_EQ(10, rectangle.getPerimeter()); }
GOOGLE TEST ADVANCE FEATURES Value parameterized tests Type parameterized tests Death tests Regular expression Event listener (notifications of the progress of a test program and test failures, DB…) Xml report Integration with gdb And much more: extra logging info, running subset of tests, disabling tests…
DEPENDENCIES & COLLABORATORS OTHER CLASS OTHER CLASS OTHER CLASS OTHER CLASS TEST DRIVER CLASS UNDER TEST Stimulus OTHER CLASS OTHER CLASS Assertions OTHER CLASS
USE SEAMS
USE SEAMS TEST FRIEND CLASS OTHER CLASS TEST DRIVER CLASS UNDER TEST Stimulus SEAM OTHER CLASS TEST FRIEND CLASS Assertions
COLLABORATORS DO NOT USE THE REAL OBJECT Stubs, Dummies and Mocks
PREPROCESING SEAM #ifdef #define #if
class UserAccount { public: User(string userName): userName_(userName) { databaseRegisterUser(userName); }; #ifdef TEST #define databaseRegisterUser(userName) \ {if(userName.size() > 10) throw(exception) \ else return OK;} #endif class UserAccount { public: User(string userName): userName_(userName) { databaseRegisterUser(userName); };
LINK SEAM g++ -o test.bin test.cpp -ldatabase g++ -o test.bin test.cpp -lfake_database
POLYMORPHISM SEAM class Database + registerUser(string userName); class DatabaseFake + registerUser(string userName);
POLYMORPHISM SEAM class IDatabase {abstract} + registerUser(string userName) class DatabaseImpl + registerUser(string userName) class DatabaseFake + registerUser(string userName)
Class IDatabase{ virtual intgetUserID(string userName) = 0; } Class DatabaseImpl : public IDatabase{ intgetUserID(string userName){ “hell of code to access db”}; } Class DatabaseFake: public IDatabase{ intgetUserID(string userName){ if(userName.size() > 10) throw(exception) else return userName.size(); } class UserAccount { public: UserAccount(string& userName, IDatabase & database) { id_ = database.getUserID(userName); }; intgetID(){return id_;}; } TEST(UserAccountTest, TestValidUser) { DatabaseFakedb; EXPECT_NO_THROW(UserAccount account (“leandro”, db)); EXPECT_THROW(UserAccount account (“leandroLEANDRO”, db)); } TEST(UserAccountTest, TestGetID) { DatabaseFakedb; EXPECT_NO_THROW(UserAccount account (“leandro”, db)); EXPECT_EQ(7, account.getID()); }
GOOGLE TEST TEST FRIEND CLASS CLASS UNDER TEST TEST DRIVER ASK ANSWER
GOOGLE MOCK CLASS UNDER TEST ASK TEST DRIVER TEST FRIEND CLASS EXPECTATIONS
Class IDatabase{ virtual intgetUserID(string userName) = 0; } Class DatabaseImpl : public IDatabase{ intgetUserID(string userName){ “hell of code to access db”}; } Class DatabaseMock: public IDatabase{ MOCK_METHOD1(getUserID, int(string userName)); } class UserAccount { public: UserAccount(string& userName, IDatabase & database) { id_ = database.getUserID(userName); }; intgetID(){return id_;}; } TEST(UserAccountTest, TestValidUser) { MockDatabasedb; EXPECT_CALL(db, getUserID(“leandro”)); EXPECT_NO_THROW(UserAccount account (“leandro”, db));
“There is not secret to write tests, there are only secrets to write testable code.” MiskoHevery
WE ARE GOOD… AT DOING CODE HARD TO TEST
void RTController::createSchedulers(AbstractRTEquipment::RTLayerInfoCol& layerInfoCol, AbstractRTDeviceClass* pRTDeviceClass) { RTScheduler* currentScheduler = NULL; std::vector<RTScheduler*> currentSchedulersCol; // fill the collections std::vector<RTSchedulingUnitInfo*> schedulingUnitsInfoCol; AbstractRTDeviceClass::RTActionInfoColactionInfoCol; EventsMappingElementpEventsMapping; pRTDeviceClass->fillRTSchedulingUnitsInfo(schedulingUnitsInfoCol); pRTDeviceClass->fillRTActionInfo(actionInfoCol); pRTDeviceClass->fillEventsMappingInfo(pEventsMapping); // if the class does not provide an implementation for the events, then fill it // from the events loader EventsMappingLoader* eventsManager = NULL; if (pEventsMapping.getEventMap().size() == 0) { eventsManager = new EventsMappingLoader(pRTDeviceClass->getName()); pEventsMapping = eventsManager->loadEventsMapping(); } // for each scheduling-unit ==================================================== for (std::vector<RTSchedulingUnitInfo*>::iterator schedulingUnit = schedulingUnitsInfoCol.begin(); schedulingUnit != schedulingUnitsInfoCol.end(); schedulingUnit++) { AbstractRTActionFactory* pRTActionFactory = pRTDeviceClass->getRTActionFactory(); if (pRTActionFactory == NULL) { throw FesaException(__FILE__, __LINE__, FesaErrorRTFactoryNotFound.c_str(), (*schedulingUnit)->className_.c_str()); } // create RT action configuration RTActionConfigactionConfig; AbstractRTDeviceClass::RTActionInfoCol::iterator itActionInfo = actionInfoCol.find( (*schedulingUnit)->actionName_); actionConfig.actionName_ = (*schedulingUnit)->actionName_; actionConfig.automaticNotification_ = itActionInfo->second->automaticNotification_; actionConfig.notifiedPropertiesCol_ = itActionInfo->second->notifiedProperties_; actionConfig.isGlobalPropertyToBeNotified_ = itActionInfo->second->isGlobalPropertyToBeNotified_; actionConfig.eventsToFire_ = itActionInfo->second->eventsToFire_; actionConfig.selectionCriterion_ = (*schedulingUnit)->selectionCriterion_; std::map<std::string, std::vector<EventElement*> > eventInfo = pEventsMapping.getEventMap(); std::map<std::string, std::vector<EventElement*> >::iterator eventElementCol = eventInfo.find( (*schedulingUnit)->eventName_); if (eventElementCol == eventInfo.end()) { if ((*schedulingUnit)->isEventOptional_) { // This scheduling-unit is skipped because the event-mapping doesn't // implement the logical-event, which is perfectly right because the // event is optional continue; } else { // The event-mapping doesn't implement a required logical-event: it's fatal. throw FesaException(__FILE__, __LINE__, FesaErrorEventNotFound.c_str(), (*schedulingUnit)->eventName_.c_str()); } } //TODO : This should never happen if (eventElementCol->second.size() == 0) { //the events-mapping does not contain any definition of concrete-event for this logicel-event. throw FesaException(__FILE__, __LINE__, FesaErrorConcreteEventNotFound.c_str(), (*schedulingUnit)->eventName_.c_str()); } // A concrete-event collection is homogeneous: same event-source and same type (global-event or device-event). EventElement* eventElement = eventElementCol->second[0]; //use the first one to retrieve common info. actionConfig.eventSourceName_ = eventElement->getEventSourceName(); actionConfig.className_ = (*schedulingUnit)->className_; AbstractDeviceFactory* pDeviceFactory = pRTDeviceClass->getDeviceFactory(); actionConfig.globalDevice_ = pDeviceFactory->getGlobalDevice(); std::set<HomogeneousDevCol *> devColCol = pDeviceFactory->getSortedDeviceCollection( (*schedulingUnit)->selectionCriterion_); // register scheduling-unit with empty device collections if (devColCol.empty()) { schedulingUnitWithEmptyDevCol_.push_back((*schedulingUnit)->name_); } // for each device collection ==================================================== // CRFESA-764 another service to assign the event and event source to the action config for (std::set<HomogeneousDevCol *>::iterator itDevCol = devColCol.begin(); itDevCol != devColCol.end(); ++itDevCol) { std::vector<AbstractDevice*> pDevCol = (*itDevCol)->getAsVector(); // device with the same conrete-name if (eventElement->isDeviceEvent()) { //This is a device-event: scheduling-event name is defined by the related device event-field //Device collection is homogeneous: same concrete-event definition (or group-name in case of multiple), we can use the first device actionConfig.eventName_ = pDevCol[0]->getField((*schedulingUnit)->eventField_)->getAsString(); //concrete-name or group name if (actionConfig.eventName_ == "not-used") { continue; } } else { //This is a global-event: scheduling-event name is equivalent to logical-event nane actionConfig.eventName_ = eventElement->getConcreteName(); } // actionConfig.eventName_ contains the reference of the concrete device-event or // the group-name (in case of multiple device) in the events-mapping. // We need to search this conrete-event to retrieve the specific info. std::vector<EventElement*>::iterator eventElementIterator; for (eventElementIterator = eventElementCol->second.begin(); eventElementIterator != eventElementCol->second.end(); eventElementIterator++) { if ((*eventElementIterator)->getConcreteName() == actionConfig.eventName_) { eventElement = (*eventElementIterator); break; } } // assign the right device collection to the current action std::vector<EventElement*> eventCol; eventCol.clear(); if (eventElement->eventElementType_ == Multiple) { eventCol = ((EventGroupElement*) eventElement)->getEventElementCol(); } else // single element { eventCol.push_back(eventElement); } actionConfig.deviceCol_ = pDevCol; for (unsigned inti = 0; i < eventCol.size(); ++i) { actionConfig.eventName_ = (eventCol[i])->getConcreteName(); //The search should never fail because events-mapping and events-field are consistent!! // retrieve the layer info (name, priority, ..) // Seek the layer attached to the current RTAction RTSchedulingUnitRefschedulingUnitRef; RTLayerInfo* layerInfo; unsigned int k; bool schedulingFound = false; for (k = 0; k < layerInfoCol.size(); ++k) { for (unsigned int j = 0; j < layerInfoCol[k]->schedulingUnitRefCol_.size(); ++j) { std::cout << "[RTController.cpp:276] comparing scheduling-unit: (" << layerInfoCol[k]->schedulingUnitRefCol_[j].name_ << "," << (*schedulingUnit)->name_ << ")" << std::endl; std::cout << "[RTController.cpp:277] comparingcalss name: (" << layerInfoCol[k]->schedulingUnitRefCol_[j].className_ << "," << (*schedulingUnit)->className_ << ")" << std::endl; if (!(layerInfoCol[k]->schedulingUnitRefCol_[j].name_.compare((*schedulingUnit)->name_)) && !((layerInfoCol[k]->schedulingUnitRefCol_[j].className_.compare( (*schedulingUnit)->className_)))) { layerInfo = layerInfoCol[k]; schedulingUnitRef = layerInfoCol[k]->schedulingUnitRefCol_[j]; layerInfoCol[k]->schedulingUnitRefCol_[j].found_ = true; schedulingFound = true; break; } } if (schedulingFound == true) { break; } } if (k == layerInfoCol.size()) { std::cout << "**Warning: scheduling unit" << (*schedulingUnit)->className_ << ":" << (*schedulingUnit)->name_.c_str() << " is not used in the current deploy unit" << std::endl; continue; // Should we log this? /* throw FesaException(__FILE__, __LINE__, FesaErrorLayerInfoNotFound.c_str(), (*schedulingUnit)->name_.c_str()); */ } // compute scheduler name std::stringstreamlayerName; layerName << layerInfo->layerName_; // relying on selection criterion if required if (schedulingUnitRef.perDeviceCollection_) { layerName << (*itDevCol)->getSharedCriteria(); } std::map<conststd::string, RTScheduler*>::iterator schedIter = schedulersCol_.find(layerName.str()); if (schedIter != schedulersCol_.end()) currentScheduler = (*schedIter).second; else // create a new scheduler ==================================================== { // Create a log with the appropriate className in oder to follow the // custom topics which are class dependant FesaLogger* log = FesaLogger::getLogger(rtLogName, layerName.str(), actionConfig.className_); currentScheduler = new RTScheduler(layerName.str(), layerInfo->priorityLevel_, layerInfo->priorityOffset_, layerInfo->queueSize_, log); currentSchedulersCol.push_back(currentScheduler); schedulersCol_.insert(std::make_pair(layerName.str(), currentScheduler)); } actionConfig.log_ = currentScheduler->getLogger(); // create the RT action AbstractRTAction* action = pRTActionFactory->createRTAction(actionConfig.actionName_, actionConfig); if (action == NULL) throw FesaException(__FILE__, __LINE__, FesaErrorRTActionNameNotFound.c_str(), actionConfig.actionName_.c_str()); // at this point currentScheduler contains the scheduler for this action currentScheduler->addRTAction(actionConfig.eventName_, action); // create a new event-source (if needed) ==================================================== createEventSource(actionConfig.eventSourceName_, eventCol[i], currentScheduler, pRTDeviceClass->getEventSourceFactory()); } delete *itDevCol; } // for each device collection delete (*schedulingUnit); } // for each scheduling-unit // delete resources for (AbstractRTDeviceClass::RTActionInfoCol::iterator actions = actionInfoCol.begin(); actions != actionInfoCol.end(); actions++) delete (actions->second); actionInfoCol.clear(); if (eventsManager != NULL) { // clear the map so that the EventsMappingLoader removes the events from the heap pEventsMapping.clear(); delete eventsManager; } } // end createSchedulers Split, create more classes and delegate
Singletons and static methods
NotificationProducer::NotificationProducer(conststd::string& queueName) { intsize = AbstractEquipment::getInstance()->getConfigData()->getIntValue(FESA_CONFIG_TAG_MSGNUM_MAX); msgQueuePtr_ = MsgQueueFactory::getOrCreateMsgQueue(queueName, size, false); } Avoid static methods and singletons
Mix creation… with business logic
void NotificationProducer::sendAutomaticNotification(const unsigned int& notificationID, MultiplexingContext& muxContext, FesaLogger* log, unsigned intmsgPrio) { try { msgQueuePtr_->checkState(msgPrio); if (AbstractEquipment::getInstance()->getProcessType() == unsplit) { MultiplexingContext* ctx = new MultiplexingContext(muxContext); msgQueuePtr_->postMsg(new AutomaticNotificationMessage(notificationID, ctx, msgPrio)); } else { AutomaticNotificationMessagemsg(notificationID, &muxContext, msgPrio); msgQueuePtr_->postMsg(&msg); } *(EquipmentData::getInstance()->notificationFailure_) = false; } Pass dependencies In the constructor.
Too much work… …in the constructor
TimingEventSource::TimingEventSource() : fesa::AbstractEventSource(fesa::TIMING_EVENT_SOURCE_NAME, fesa::TimingSource) { ourDebugOptions_ = getDebugOptions(); fd_ = TimLibFdInitialize(TimLibDevice_ANY); if (fd_ == 0) { char* ignore = getenv("TimingEventSource_IgnoreHardware"); if (!ignore) { throw CERNException(__FILE__, __LINE__, FESACERNErrorInitializingTimLib.c_str()); } } // Set EventQueueing ON and no timeout TimLibFdQueue(fd_, 0, 0); } Simple constructors
NotificationProducer::NotificationProducer(conststd::string& queueName) { intsize = AbstractEquipment::getInstance()->getConfigData()->getIntValue(FESA_CONFIG_TAG_MSGNUM_MAX); msgQueuePtr_ = MsgQueueFactory::getOrCreateMsgQueue(queueName, size, false); } “Each unit should only talk to its friends; don't talk to strangers.” Law of Demeter
IF YOU WANT TO BE GOOD… AT DOING CODE EASY TO TEST…
USE DEPENDENCY INJECTION
class UserAccount { public: UserAccount(string& userName) { database = new Database(); database.registerUser(userName); }; } class UserAccount { public: UserAccount(string& userName, IDatabase & database) { database.registerUser(userName); }; }
“Everything can be tested” StephaneDeghaye
“Do unit-test, you have nothing to lose but your bugs” Steve Freeman
Image Credits • Slide 3: car by Sylwia (flickr) • Slide 5: car lamp by Gostop39(flickr) • Slide 8: chain by kstoerz(flickr) • Slide 14: seam by Lost Star (flickr) • Slide 26: earth by NASA Goddard (flickr) • Slide 30: construction by Winglet Photography (flickr) • Slide 32: russian dolls by Pink Flutterby (flickr) • Slide 35: vaccinne by SanofiPasteur (flickr)