Home
%3CLINGO-SUB%20id%3D%22lingo-sub-711673%22%20slang%3D%22en-US%22%3EPerformance%20Testing%20of%20BOT%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-711673%22%20slang%3D%22en-US%22%3E%3CP%3EThe%20main%20purpose%20of%20this%20document%20is%20to%20outline%20the%20testing%20strategy%20used%20for%20Performance%20Testing%20of%20BOT%20in%20one%20of%20the%20customer%20engagements.%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3EThe%20Application%20Under%20Test%20is%20a%20Mobile%20App%20that%20communicates%20with%20BOT%20which%20consumes%20services%20to%20call%20the%20Knowledge%20Graph%20APIs.%20Load%20testing%20was%20performed%20at%20the%20BOT%20layer.%3C%2FP%3E%0A%3CP%3EFollowing%20are%20the%20detailed%20steps%20involved%3A%3C%2FP%3E%0A%3COL%3E%0A%3CLI%3EImplementation%20of%20the%20mock%20channel%20to%20which%20BOT%20response%20would%20be%20directed%20to.%3C%2FLI%3E%0A%3C%2FOL%3E%0A%3CUL%3E%0A%3CLI%3EWhen%20a%20request%20is%20sent%20to%20BOT%2C%20response%20of%20BOT%20is%20sent%20back%20to%20BOT%20Connector%20Service%20by%20default.%20In%20order%20to%20capture%20the%20timestamp%20for%20BOT%20response%2C%20we%20create%20a%20mock%20channel%20to%20which%20response%20is%20redirected.%3C%2FLI%3E%0A%3CLI%3EWithin%20this%20mock%20channel%2C%20methods%20for%20testing%20BOT%20are%20defined.%3C%2FLI%3E%0A%3CUL%3E%0A%3CLI%3EWhen%20BOT%E2%80%99s%20endpoint%20receives%20a%20request%20(%20message)%20from%20user%2C%20using%20the%20information%20in%20that%20request%20an%20%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbot-service%2Frest-api%2Fbot-framework-rest-connector-api-reference%3Fview%3Dazure-bot-service-4.0%23activity-object%22%20target%3D%22_blank%22%20rel%3D%22noopener%20noopener%20noreferrer%20noopener%20noreferrer%22%3EActivity%3C%2FA%3E%20object%20is%20created%20for%20response%20to%20user%20request.%3C%2FLI%3E%0A%3CLI%3ETo%20reply%20to%20a%20specific%20message%20within%20the%20conversation%2C%20%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fbot-service%2Frest-api%2Fbot-framework-rest-connector-api-reference%3Fview%3Dazure-bot-service-4.0%23reply-to-activity%22%20target%3D%22_blank%22%20rel%3D%22noopener%20noopener%20noreferrer%20noopener%20noreferrer%22%3EReply%20to%20Activity%3C%2FA%3E%20method%20is%20used%20that%20is%20implemented%20in%20the%20mock%20channel.%3C%2FLI%3E%0A%3CLI%3EWithin%20this%20method%2C%20BOT%20Response%20is%20captured%20and%20saved%20to%20Storage%20Table%20that%20can%20be%20retrieved%20later.%20The%20time%20stamp%20of%20response%20received%20is%20also%20captured%20in%20the%20Storage%20table%20for%20that%20activity.%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3EFor%20details%20to%20implement%20the%20mock%20channel%2C%20please%20refer%20to%20published%20%3CA%20href%3D%22https%3A%2F%2Fgithub.com%2FSoujanyaAK%2FBOTMockChannelmplementation%22%20target%3D%22_blank%22%20rel%3D%22noopener%20noopener%20noreferrer%20noopener%20noreferrer%22%3EIP.%3C%2FA%3E%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E2.%20Send%20Request%20from%20Visual%20Studio%20Web%20Test%20to%20BOT.%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3E%26nbsp%3B%3C%2FSTRONG%3E%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3ERequest1%3A%3C%2FSTRONG%3E%20POST%20%3CA%20href%3D%22https%3A%2F%2Flogin.microsoftonline.com%2Fbotframework.com%2Foauth2%2Fv2.0%2Ftoken%22%20target%3D%22_blank%22%20rel%3D%22noopener%20nofollow%20noopener%20noreferrer%20noopener%20noreferrer%22%3Ehttps%3A%2F%2Flogin.microsoftonline.com%2Fbotframework.com%2Foauth2%2Fv2.0%2Ftoken%3C%2FA%3E%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EObtain%20an%20access%20token%20from%20the%20Bot%20Framework%20to%20authorize%20the%20web%20test%20to%20the%20BOT.%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3EIn%20the%20below%20request%2C%20replace%20%3CSTRONG%3EMICROSOFT-APP-ID%20%3C%2FSTRONG%3Eand%20%3CSTRONG%3EMICROSOFT-APP-PASSWORD%3C%2FSTRONG%3E%20with%20the%20App%20ID%20and%20Password%20of%20the%20BOT%20(obtained%20during%20BOT%20Registration).%3C%2FP%3E%0A%3CP%3EPOST%20%3CA%20href%3D%22https%3A%2F%2Flogin.microsoftonline.com%2Fbotframework.com%2Foauth2%2Fv2.0%2Ftoken%22%20target%3D%22_blank%22%20rel%3D%22noopener%20nofollow%20noopener%20noreferrer%20noopener%20noreferrer%22%3Ehttps%3A%2F%2Flogin.microsoftonline.com%2Fbotframework.com%2Foauth2%2Fv2.0%2Ftoken%3C%2FA%3E%3C%2FP%3E%0A%3CP%3EHost%3A%20login.microsoftonline.com%3C%2FP%3E%0A%3CP%3EContent-Type%3A%20application%2Fx-www-form-urlencoded%3C%2FP%3E%0A%3CP%3ERequest%20Body%3A%3C%2FP%3E%0A%3CP%3Egrant_type%3Dclient_credentials%26amp%3Bclient_id%3D%3CSTRONG%3EMICROSOFT-APP-ID%3C%2FSTRONG%3E%26amp%3Bclient_secret%3D%3CSTRONG%3EMICROSOFT-APP-PASSWORD%3C%2FSTRONG%3E%26amp%3Bscope%3D%3CSTRONG%3EMICROSOFT-APP-ID%3C%2FSTRONG%3E%2F.default%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%26nbsp%3B%26nbsp%3B%26nbsp%3B%20Successful%20request%20returns%20a%20JSON%20payload%20with%20access_token.%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3E%26nbsp%3B%3C%2FSTRONG%3E%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3ERequest2%3A%3C%2FSTRONG%3E%20POST%20%3CA%20href%3D%22https%3A%2F%2F%257b%257bbotendpoint%257d%257d%2Fapi%2Fmessages%22%20target%3D%22_blank%22%20rel%3D%22noopener%20nofollow%20noopener%20noreferrer%20noopener%20noreferrer%22%3Ehttps%3A%2F%2F%7B%7BBotEndPoint%7D%7D%2Fapi%2Fmessages%3C%2FA%3E%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EUse%20the%20Bot%20Connector%20service%20to%20exchange%20messages%20between%20BOT%20and%20user%3A%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3ECreate%20HTTP%20POST%20to%20BOT%E2%80%99s%20endpoint%20URL%20that%20includes%20Authorization%20header%20where%20the%20access_token%20retrieved%20in%20above%20step%20is%20passed.%3C%2FP%3E%0A%3CP%3ERequests%20sent%20to%20BOT%20Endpoint%20%3CA%20href%3D%22https%3A%2F%2F%257b%257bbotendpoint%257d%257d%2Fapi%2Fmessages%22%20target%3D%22_blank%22%20rel%3D%22noopener%20nofollow%20noopener%20noreferrer%20noopener%20noreferrer%22%3Ehttps%3A%2F%2F%7B%7BBotEndPoint%7D%7D%2Fapi%2Fmessages%3C%2FA%3E%20include%3A%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EAuthorization%20token%20retrieved%20earlier%20is%20passed%20in%20the%20header%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%26nbsp%3B%20POST%20api%2Fmessages%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%26nbsp%3B%20Authorization%3A%20Bearer%20%3CACCESS_TOKEN%3E%3C%2FACCESS_TOKEN%3E%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%26nbsp%3B%20Content-Type%3A%20application%2Fjson%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EJSON%20Serialized%20body%20contains%20the%20ServiceUrl%20which%20is%20the%20mock%20channel%20end%20point%20to%20which%20BOT%20sends%20the%20response%20back.%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CPRE%3E%7B%0A%22Type%22%3A%20%22message%22%2C%0A%22Id%22%3A%20%22%7B%7BActivityID%7D%7D%22%0A%22ChannelId%22%3A%20%22test%22%2C%0A%22Conversation%22%3A%20%7B%0A%20%20%20%22id%22%3A%20%22%7B%7BConversationID%7D%7D%22%0A%7D%2C%0A%22From%22%3A%20%7B%0A%20%20%20%22id%22%3A%20%221%22%0A%7D%2C%0A%22Recipient%22%3A%20%7B%0A%20%20%20%22id%22%3A%20%222%22%0A%7D%2C%0A%22ServiceUrl%22%3A%20%22https%3A%2F%2F%7B%7Bmockchannel%7D%7D.azurewebsites.net%22%2C%0A%22Text%22%3A%20%E2%80%9C%7BMessage%20Sent%7D%22%0A%3C%2FPRE%3E%0A%3CP%3E3.%20Save%20the%20above%20request%20body%20along%20with%20request%20timestamp%20to%20the%20same%20Storage%20table%20account%20created%20in%20Step%201.%20with%20ConvID(guid)%20and%20ActivityID(guid)%20as%20the%20PartitionKey%20and%20RowKey.%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EThe%20time%20stamp%20of%20request%20sent%20from%20user%20is%20also%20captured%20in%20the%20Storage%20table%20for%20that%20activity.%20This%20is%20shown%20in%20the%20code%20shared%20below.%3C%2FLI%3E%0A%3CLI%3EThe%20ServiceUrl%20property%20within%20the%20user's%20message%20indicates%20that%20the%20BOT%20should%20send%20its%20response%20to%20the%20endpoint%20%3CA%20href%3D%22https%3A%2F%2F%257b%257bmockchannel%257d%257d.azurewebsites.net%2F%22%20target%3D%22_blank%22%20rel%3D%22noopener%20nofollow%20noopener%20noreferrer%20noopener%20noreferrer%22%3Ehttps%3A%2F%2F%7B%7Bmockchannel%7D%7D.azurewebsites.net%3C%2FA%3E%20that%20will%20be%20the%20base%20URI%20for%20any%20subsequent%20requests%20that%20the%20bot%20issues%20in%20the%20context%20of%20this%20conversation.%3C%2FLI%3E%0A%3CLI%3EContext%20parameter%20e.WebTest.Context%5B%22BotConnectorBaseUrl%22%5D.ToString()%20points%20to%20%3CA%20href%3D%22https%3A%2F%2F%257b%257bmockchannel%257d%257d.azurewebsites.net%2F%22%20target%3D%22_blank%22%20rel%3D%22noopener%20nofollow%20noopener%20noreferrer%20noopener%20noreferrer%22%3Ehttps%3A%2F%2F%7B%7Bmockchannel%7D%7D.azurewebsites.net%3C%2FA%3E%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%3CSTRONG%3E%26nbsp%3B%3C%2FSTRONG%3E%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3ECode%20for%20design%20of%20Storage%20Table%2C%20generation%20of%20request%20body%20for%20api%2Fmessages%20and%20save%20the%20request%20to%20Storage%20Table.%3C%2FSTRONG%3E%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EDesign%20a%20Storage%20Table%20with%20columns%20mapping%20the%20request%20details%20such%20as%20ConversationID%2C%20ActivityID%2C%20requestbody%2C%20request%20timestamp%20etc..%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CP%3E%26nbsp%3B%26nbsp%3B%3C%2FP%3E%0A%3CPRE%3Epublic%20class%20ActivityTableEntity%20%3A%20TableEntity%0A%20%20%20%7B%0A%20%20%20%20%20%20%20public%20ActivityTableEntity()%0A%20%20%20%20%20%20%20%7B%0A%20%0A%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20public%20ActivityTableEntity(string%20convId)%0A%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20this.PartitionKey%20%3D%20convId%3B%0A%20%20%20%20%20%20%20%7D%0A%20%0A%20%20%20%20%20%20%20public%20ActivityTableEntity(string%20convId%2C%20string%20activityId)%0A%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20this.PartitionKey%20%3D%20convId%3B%0A%20%20%20%20%20%20%20%20%20%20%20this.RowKey%20%3D%20activityId%3B%0A%20%20%20%20%20%20%20%7D%0A%20%0A%20%20%20%20%20%20%20public%20string%20activityMsg%3B%0A%20%20%20%20%20%20%20public%20string%20scenarioName%20%7B%20get%3B%20set%3B%20%7D%0A%20%20%20%20%20%20%20public%20string%20incomingAcvitityMsg%20%7B%20get%3B%20set%3B%20%7D%0A%20%20%20%20%20%20%20public%20string%20botResponseMsg%20%7B%20get%3B%20set%3B%20%7D%0A%20%20%20%20%20%20%20public%20DateTime%20requestTimeStamp%20%7B%20get%3B%20set%3B%20%7D%0A%20%20%20%20%20%20%20public%20string%20responseTimeStamp%20%7B%20get%3B%20set%3B%20%7D%0A%20%20%20%20%20%7D%3C%2FPRE%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CUL%3E%0A%3CLI%3EGenerate%20request%20body%20for%20api%2Fmessages%20and%20save%20the%20request%20to%20Storage%20Table.%3C%2FLI%3E%0A%3C%2FUL%3E%0A%3CPRE%3Epublic%20class%20SaveRequest%20%3A%20WebTestPlugin%0A%20%20%20%7B%0A%20%20%20%20%20%20%20private%20static%20CloudStorageAccount%20account%20%3D%20default(CloudStorageAccount)%3B%0A%20%20%20%20%0A%20%20%20%20%20%20%20private%20static%20CloudTableClient%20tableClient%3B%0A%20%20%20%20%20%20%20private%20static%20CloudTable%20botOperationLogTable%3B%0A%20%20%20%20%20%20%20public%20string%20azureStorageAccount%20%3D%20HelperMethods.GetAppConfigValues(%22azureStorageAccount%22)%3B%0A%20%20%20%20%20%20%20public%20string%20azureStorageSecret%20%3D%20HelperMethods.GetAppConfigValues(%22azureStorageSecret%22)%3B%0A%20%20%20%20%20%20%20public%20string%20TableName%20%3D%20HelperMethods.GetAppConfigValues(%22tableName%22)%3B%0A%20%0A%20%20%20%20%20%20%20ActivityTableEntity%20activityTableEntity%3B%0A%20%0A%20%20%20%20%20%20%20private%20string%20postData%20%3D%20string.Empty%3B%0A%20%20%20%20%20%20%20private%20string%20Text%20%3D%20string.Empty%3B%0A%20%20%20%20%20%20%20private%20string%20AuthToken%20%3D%20string.Empty%3B%0A%20%20%20%20%20%20%20private%20Guid%20guid%20%3D%20Guid.NewGuid()%3B%0A%20%20%20%20%20%20%20private%20string%20Id%20%3D%20string.Empty%3B%0A%20%0A%20%0A%20%20%20%20%20%20%20public%20override%20void%20PreWebTest(object%20sender%2C%20PreWebTestEventArgs%20e)%0A%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20if%20(!string.IsNullOrEmpty(azureStorageAccount)%20%26amp%3B%26amp%3B%20!string.IsNullOrEmpty(azureStorageSecret))%0A%20%20%20%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20account%20%3D%20new%20CloudStorageAccount(new%20StorageCredentials(azureStorageAccount%2C%20azureStorageSecret)%2C%20true)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20tableClient%20%3D%20account.CreateCloudTableClient()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20botOperationLogTable%20%3D%20tableClient.GetTableReference(TableName)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20botOperationLogTable.CreateIfNotExistsAsync()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%7D%0A%20%0A%2F*%20ToDo%20Include%20a%20check%20for%20the%20request%20to%20match%20with%20api%2Fmessages%20in%20order%20to%20create%20the%20request%20body%20only%20for%20that%20request%20*%2F%0A%20%20%20%20%20%20%20public%20override%20void%20PreRequestDataBinding(object%20sender%2C%20PreRequestDataBindingEventArgs%20e)%0A%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Generation%20of%20request%20body%0A%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Id%20%3D%20e.WebTest.Context%5B%22ActivityID%22%5D.ToString()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20postData%20%3D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%40%22%7B%0A%20%20%20%22%22Type%22%22%3A%22%22message%22%22%2C%0A%20%20%20%22%22Id%22%22%3A%22%22%22%20%2B%20Id%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%40%22%22%22%2C%0A%20%20%20%22%22ChannelId%22%22%3A%22%22test%22%22%2C%0A%20%20%20%22%22Conversation%22%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%22id%22%22%3A%22%22%22%20%2B%20e.WebTest.Context%5B%22ConvID%22%5D.ToString()%20%2B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%40%22%22%22%0A%20%20%20%7D%2C%0A%20%20%20%22%22From%22%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%22id%22%22%3A%22%221%22%22%0A%20%20%20%7D%2C%0A%20%20%20%22%22Recipient%22%22%3A%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%22id%22%22%3A%22%222%22%22%0A%20%20%20%7D%2C%0A%20%20%20%22%22ServiceUrl%22%22%3A%22%22%22%20%2B%20e.WebTest.Context%5B%22BotConnectorBaseUrl%22%5D.ToString()%20%2B%20%40%22%22%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22%22Text%22%22%20%3A%20%22%22%7B%5C%22%22Query%5C%22%22%3A%5C%22%22%22%20%2B%20%40%22%5C%22%22%7D%22%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%22%3B%0A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20e.WebTest.Context.Add(%22postData%22%2C%20postData)%3B%0A%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20activityTableEntity%20%3D%20new%20ActivityTableEntity(e.WebTest.Context%5B%22ConvID%22%5D.ToString()%2CId)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20activityTableEntity.incomingAcvitityMsg%20%3D%20postData%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20activityTableEntity.requestTimeStamp%20%3D%20DateTime.UtcNow%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20activityTableEntity.scenarioName%20%3D%20%22WebTest1%22%3B%0A%20%0A%20%20%20%20%20%20%20%7D%0A%20%0A%2F*%20ToDo%20Include%20a%20check%20for%20request%20to%20match%20with%20api%2Fmessages%20in%20order%20to%20save%20only%20for%20that%20request%20*%2F%0A%20%0A%20%20%20%20%20%20%20public%20override%20void%20PostRequest(object%20sender%2C%20PostRequestEventArgs%20e)%0A%20%0A%20%20%20%20%20%20%20%7B%0A%2F%2F%20Save%20the%20request%20details%20to%20table%20storage%0A%20%0ATableOperation%20tableOperation%20%3D%20TableOperation.Insert(activityTableEntity)%3B%0AbotOperationLogTable.Execute(tableOperation)%3B%0A%20%20%20%20%20%20%20%7D%0A%20%20%20%7D%3C%2FPRE%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E4.%20Calculation%20of%20response%20timestamp%3A%20Since%20both%20request%20timestamp%20-%3CSTRONG%3ErequestTimeStamp%3C%2FSTRONG%3E%20(from%20web%20test)%20and%20response%20timestamp-%3CSTRONG%3EresponseTimeStamp%3C%2FSTRONG%3E(from%20mock%20channel%20)%20are%20captured%20in%20Storage%20Table%2C%20the%20difference%20in%20columns%20gives%20the%20response%20times.%3C%2FP%3E%0A%3CP%3E%3CSPAN%20class%3D%22lia-inline-image-display-wrapper%20lia-image-align-inline%22%20style%3D%22width%3A%20624px%3B%22%3E%3CIMG%20src%3D%22https%3A%2F%2Fgxcuf89792.i.lithium.com%2Ft5%2Fimage%2Fserverpage%2Fimage-id%2F119932iE024DFEAC65DCD9E%2Fimage-size%2Flarge%3Fv%3D1.0%26amp%3Bpx%3D999%22%20alt%3D%222019_1.png%22%20title%3D%222019_1.png%22%20%2F%3E%3C%2FSPAN%3E%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-TEASER%20id%3D%22lingo-teaser-711673%22%20slang%3D%22en-US%22%3E%3CP%3E%3CSPAN%20style%3D%22display%3A%20inline%20!important%3B%20float%3A%20none%3B%20background-color%3A%20%23ffffff%3B%20color%3A%20%23333333%3B%20cursor%3A%20text%3B%20font-family%3A%20inherit%3B%20font-size%3A%2016px%3B%20font-style%3A%20normal%3B%20font-variant%3A%20normal%3B%20font-weight%3A%20300%3B%20letter-spacing%3A%20normal%3B%20line-height%3A%201.7142%3B%20orphans%3A%202%3B%20text-align%3A%20left%3B%20text-decoration%3A%20none%3B%20text-indent%3A%200px%3B%20text-transform%3A%20none%3B%20-webkit-text-stroke-width%3A%200px%3B%20white-space%3A%20normal%3B%20word-spacing%3A%200px%3B%22%3EThe%20main%20purpose%20of%20this%20document%20is%20to%20outline%20the%20testing%20strategy%20used%20for%20Performance%20Testing%20of%20BOT%20in%20one%20of%20the%20customer%20engagements.%3C%2FSPAN%3E%3C%2FP%3E%3C%2FLINGO-TEASER%3E

The main purpose of this document is to outline the testing strategy used for Performance Testing of BOT in one of the customer engagements.

 

The Application Under Test is a Mobile App that communicates with BOT which consumes services to call the Knowledge Graph APIs. Load testing was performed at the BOT layer.

Following are the detailed steps involved:

  1. Implementation of the mock channel to which BOT response would be directed to.
  • When a request is sent to BOT, response of BOT is sent back to BOT Connector Service by default. In order to capture the timestamp for BOT response, we create a mock channel to which response is redirected.
  • Within this mock channel, methods for testing BOT are defined.
    • When BOT’s endpoint receives a request ( message) from user, using the information in that request an Activity object is created for response to user request.
    • To reply to a specific message within the conversation, Reply to Activity method is used that is implemented in the mock channel.
    • Within this method, BOT Response is captured and saved to Storage Table that can be retrieved later. The time stamp of response received is also captured in the Storage table for that activity.

 

For details to implement the mock channel, please refer to published IP.

 

2. Send Request from Visual Studio Web Test to BOT.

 

Request1: POST https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token

  • Obtain an access token from the Bot Framework to authorize the web test to the BOT.

In the below request, replace MICROSOFT-APP-ID and MICROSOFT-APP-PASSWORD with the App ID and Password of the BOT (obtained during BOT Registration).

POST https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token

Host: login.microsoftonline.com

Content-Type: application/x-www-form-urlencoded

Request Body:

grant_type=client_credentials&client_id=MICROSOFT-APP-ID&client_secret=MICROSOFT-APP-PASSWORD&scope=MICROSOFT-APP-ID/.default

     Successful request returns a JSON payload with access_token.

 

Request2: POST https://{{BotEndPoint}}/api/messages

  • Use the Bot Connector service to exchange messages between BOT and user:

Create HTTP POST to BOT’s endpoint URL that includes Authorization header where the access_token retrieved in above step is passed.

Requests sent to BOT Endpoint https://{{BotEndPoint}}/api/messages include:

  • Authorization token retrieved earlier is passed in the header

   POST api/messages

   Authorization: Bearer <ACCESS_TOKEN>

   Content-Type: application/json

 

  • JSON Serialized body contains the ServiceUrl which is the mock channel end point to which BOT sends the response back.

 

{
"Type": "message",
"Id": "{{ActivityID}}"
"ChannelId": "test",
"Conversation": {
   "id": "{{ConversationID}}"
},
"From": {
   "id": "1"
},
"Recipient": {
   "id": "2"
},
"ServiceUrl": "https://{{mockchannel}}.azurewebsites.net",
"Text": “{Message Sent}"

3. Save the above request body along with request timestamp to the same Storage table account created in Step 1. with ConvID(guid) and ActivityID(guid) as the PartitionKey and RowKey.

  • The time stamp of request sent from user is also captured in the Storage table for that activity. This is shown in the code shared below.
  • The ServiceUrl property within the user's message indicates that the BOT should send its response to the endpoint https://{{mockchannel}}.azurewebsites.net that will be the base URI for any subsequent requests that the bot issues in the context of this conversation.
  • Context parameter e.WebTest.Context["BotConnectorBaseUrl"].ToString() points to https://{{mockchannel}}.azurewebsites.net

 

Code for design of Storage Table, generation of request body for api/messages and save the request to Storage Table.

 

  • Design a Storage Table with columns mapping the request details such as ConversationID, ActivityID, requestbody, request timestamp etc..

  

public class ActivityTableEntity : TableEntity
   {
       public ActivityTableEntity()
       {
 
       }
       public ActivityTableEntity(string convId)
       {
           this.PartitionKey = convId;
       }
 
       public ActivityTableEntity(string convId, string activityId)
       {
           this.PartitionKey = convId;
           this.RowKey = activityId;
       }
 
       public string activityMsg;
       public string scenarioName { get; set; }
       public string incomingAcvitityMsg { get; set; }
       public string botResponseMsg { get; set; }
       public DateTime requestTimeStamp { get; set; }
       public string responseTimeStamp { get; set; }
     }

 

  • Generate request body for api/messages and save the request to Storage Table.
public class SaveRequest : WebTestPlugin
   {
       private static CloudStorageAccount account = default(CloudStorageAccount);
    
       private static CloudTableClient tableClient;
       private static CloudTable botOperationLogTable;
       public string azureStorageAccount = HelperMethods.GetAppConfigValues("azureStorageAccount");
       public string azureStorageSecret = HelperMethods.GetAppConfigValues("azureStorageSecret");
       public string TableName = HelperMethods.GetAppConfigValues("tableName");
 
       ActivityTableEntity activityTableEntity;
 
       private string postData = string.Empty;
       private string Text = string.Empty;
       private string AuthToken = string.Empty;
       private Guid guid = Guid.NewGuid();
       private string Id = string.Empty;
 
 
       public override void PreWebTest(object sender, PreWebTestEventArgs e)
       {
           if (!string.IsNullOrEmpty(azureStorageAccount) && !string.IsNullOrEmpty(azureStorageSecret))
           {
               account = new CloudStorageAccount(new StorageCredentials(azureStorageAccount, azureStorageSecret), true);
               tableClient = account.CreateCloudTableClient();
               botOperationLogTable = tableClient.GetTableReference(TableName);
               botOperationLogTable.CreateIfNotExistsAsync();
           }
       }
 
/* ToDo Include a check for the request to match with api/messages in order to create the request body only for that request */
       public override void PreRequestDataBinding(object sender, PreRequestDataBindingEventArgs e)
       {
                   // Generation of request body
      
                   Id = e.WebTest.Context["ActivityID"].ToString();
                   postData =
                       @"{
   ""Type"":""message"",
   ""Id"":""" + Id +
                   @""",
   ""ChannelId"":""test"",
   ""Conversation"":{
                       ""id"":""" + e.WebTest.Context["ConvID"].ToString() +
                       @"""
   },
   ""From"":{
                       ""id"":""1""
   },
   ""Recipient"":{
                       ""id"":""2""
   },
   ""ServiceUrl"":""" + e.WebTest.Context["BotConnectorBaseUrl"].ToString() + @""",
                  
                   ""Text"" : ""{\""Query\"":\""" + @"\""}""
                 }";
 
                   e.WebTest.Context.Add("postData", postData);
 
                  
                   activityTableEntity = new ActivityTableEntity(e.WebTest.Context["ConvID"].ToString(),Id);
                   activityTableEntity.incomingAcvitityMsg = postData;
                   activityTableEntity.requestTimeStamp = DateTime.UtcNow;
                   activityTableEntity.scenarioName = "WebTest1";
 
       }
 
/* ToDo Include a check for request to match with api/messages in order to save only for that request */
 
       public override void PostRequest(object sender, PostRequestEventArgs e)
 
       {
// Save the request details to table storage
 
TableOperation tableOperation = TableOperation.Insert(activityTableEntity);
botOperationLogTable.Execute(tableOperation);
       }
   }

 

4. Calculation of response timestamp: Since both request timestamp -requestTimeStamp (from web test) and response timestamp-responseTimeStamp(from mock channel ) are captured in Storage Table, the difference in columns gives the response times.

2019_1.png