1 / 48

DO YOU…

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

quinta
Download Presentation

DO YOU…

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. DO YOU… TESTYOUR CODE? Leandro Fernandez 21-February-2012

  2. bugs test stress Michael C. Feathers

  3. WHOLESYSTEM

  4. INSOLATE COMPONENT

  5. UNIT-TEST

  6. Super fast

  7. Easy to Identify what is wrong

  8. No need of GDB

  9. DO YOU “UNIT-TEST” YOUR CODE?

  10. CODE WITHOUT UNIT-TEST IS NOT A GOOD CODE

  11. CODE WITHOUT UNIT-TEST HAS NOT A GOOD DESIGN

  12. TEST DRIVER CLASS UNDER TEST Stimulus Assertions

  13. 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

  14. GOOGLE TEST RICH ASSERTIONS ASSERT_* EXPECT_*

  15. 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()); }

  16. 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…

  17. DEPENDENCIES & COLLABORATORS OTHER CLASS OTHER CLASS OTHER CLASS OTHER CLASS TEST DRIVER CLASS UNDER TEST Stimulus OTHER CLASS OTHER CLASS Assertions OTHER CLASS

  18. USE SEAMS

  19. USE SEAMS TEST FRIEND CLASS OTHER CLASS TEST DRIVER CLASS UNDER TEST Stimulus SEAM OTHER CLASS TEST FRIEND CLASS Assertions

  20. COLLABORATORS DO NOT USE THE REAL OBJECT Stubs, Dummies and Mocks

  21. PREPROCESING SEAM #ifdef #define #if

  22. 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); };

  23. LINK SEAM g++ -o test.bin test.cpp -ldatabase g++ -o test.bin test.cpp -lfake_database

  24. POLYMORPHISM SEAM class Database + registerUser(string userName); class DatabaseFake + registerUser(string userName);

  25. POLYMORPHISM SEAM class IDatabase {abstract} + registerUser(string userName) class DatabaseImpl + registerUser(string userName) class DatabaseFake + registerUser(string userName)

  26. 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()); }

  27. GOOGLE TEST TEST FRIEND CLASS CLASS UNDER TEST TEST DRIVER ASK ANSWER

  28. GOOGLE MOCK CLASS UNDER TEST ASK TEST DRIVER TEST FRIEND CLASS EXPECTATIONS

  29. 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));

  30. “There is not secret to write tests, there are only secrets to write testable code.” MiskoHevery

  31. WE ARE GOOD… AT DOING CODE HARD TO TEST

  32. MONOLITHIC

  33. 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

  34. Singletons and static methods

  35. 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

  36. Mix creation… with business logic

  37. 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.

  38. Too much work… …in the constructor

  39. 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

  40. Looking for things

  41. 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

  42. IF YOU WANT TO BE GOOD… AT DOING CODE EASY TO TEST…

  43. USE DEPENDENCY INJECTION

  44. class UserAccount { public: UserAccount(string& userName) { database = new Database(); database.registerUser(userName); }; } class UserAccount { public: UserAccount(string& userName, IDatabase & database) { database.registerUser(userName); }; }

  45. “Everything can be tested” StephaneDeghaye

  46. “Do unit-test, you have nothing to lose but your bugs” Steve Freeman

  47. Thank you!

  48. 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)

More Related