-
Notifications
You must be signed in to change notification settings - Fork 291
Parent test result support for data driven tests #417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
28ab3bc
4f71d7c
7829b45
a783992
311a492
1da1628
fb92014
6925245
74b8768
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -215,12 +215,23 @@ internal UnitTestResult[] RunTestMethod() | |
| Debug.Assert(this.testMethodInfo.TestMethod != null, "Test method should not be null."); | ||
|
|
||
| List<UTF.TestResult> results = new List<UTF.TestResult>(); | ||
| var isDataDriven = false; | ||
|
|
||
| // Parent result. Added in properties bag only when results are greater than 1. | ||
| var parentResultWatch = new Stopwatch(); | ||
| parentResultWatch.Start(); | ||
| var parentResult = new UTF.TestResult | ||
| { | ||
| Outcome = UTF.UnitTestOutcome.InProgress, | ||
| ExecutionId = Guid.NewGuid() | ||
| }; | ||
|
|
||
| if (this.testMethodInfo.TestMethodOptions.Executor != null) | ||
| { | ||
| UTF.DataSourceAttribute[] dataSourceAttribute = this.testMethodInfo.GetAttributes<UTF.DataSourceAttribute>(false); | ||
| if (dataSourceAttribute != null && dataSourceAttribute.Length == 1) | ||
| { | ||
| isDataDriven = true; | ||
| Stopwatch watch = new Stopwatch(); | ||
| watch.Start(); | ||
|
|
||
|
|
@@ -296,6 +307,7 @@ internal UnitTestResult[] RunTestMethod() | |
|
|
||
| if (testDataSources != null && testDataSources.Length > 0) | ||
| { | ||
| isDataDriven = true; | ||
| foreach (var testDataSource in testDataSources) | ||
| { | ||
| foreach (var data in testDataSource.GetData(this.testMethodInfo.MethodInfo)) | ||
|
|
@@ -345,35 +357,81 @@ internal UnitTestResult[] RunTestMethod() | |
| this.testMethodInfo.TestMethodName); | ||
| } | ||
|
|
||
| if (results != null && results.Count > 0) | ||
| { | ||
| // aggregate for data driven tests | ||
| UTF.UnitTestOutcome aggregateOutcome = UTF.UnitTestOutcome.Passed; | ||
| parentResultWatch.Stop(); | ||
| parentResult.Duration = parentResultWatch.Elapsed; | ||
|
|
||
| foreach (var result in results) | ||
| { | ||
| if (result.Outcome != UTF.UnitTestOutcome.Passed) | ||
| { | ||
| if (aggregateOutcome != UTF.UnitTestOutcome.Failed) | ||
| { | ||
| if (result.Outcome == UTF.UnitTestOutcome.Failed | ||
| || aggregateOutcome != UTF.UnitTestOutcome.Timeout) | ||
| { | ||
| aggregateOutcome = result.Outcome; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // Get aggregate outcome. | ||
| var aggregateOutcome = this.GetAggregateOutcome(results); | ||
| this.testContext.SetOutcome(aggregateOutcome); | ||
|
|
||
| this.testContext.SetOutcome(aggregateOutcome); | ||
| // Set a result in case no result is present. | ||
| if (!results.Any()) | ||
| { | ||
| results.Add(new UTF.TestResult() { Outcome = aggregateOutcome, TestFailureException = new TestFailedException(UnitTestOutcome.Error, Resource.UTA_NoTestResult) }); | ||
| } | ||
| else | ||
|
|
||
| // In case of data driven, set parent info in results. | ||
| if (isDataDriven) | ||
| { | ||
| this.testContext.SetOutcome(UTF.UnitTestOutcome.Unknown); | ||
| results.Add(new UTF.TestResult() { Outcome = UTF.UnitTestOutcome.Unknown, TestFailureException = new TestFailedException(UnitTestOutcome.Error, Resource.UTA_NoTestResult) }); | ||
| parentResult.Outcome = aggregateOutcome; | ||
| results = this.UpdateResultsWithParentInfo(results, parentResult); | ||
| } | ||
|
|
||
| return results.ToArray().ToUnitTestResults(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets aggregate outcome. | ||
| /// </summary> | ||
| /// <param name="results">Results.</param> | ||
| /// <returns>Aggregate outcome.</returns> | ||
| private UTF.UnitTestOutcome GetAggregateOutcome(List<UTF.TestResult> results) | ||
| { | ||
| // In case results are not present, set outcome as unknown. | ||
| if (!results.Any()) | ||
| { | ||
| return UTF.UnitTestOutcome.Unknown; | ||
| } | ||
|
|
||
| // Get aggregate outcome. | ||
| var aggregateOutcome = results[0].Outcome; | ||
| foreach (var result in results) | ||
| { | ||
| UnitTestOutcomeExtensions.GetMoreImportantOutcome(aggregateOutcome, result.Outcome); | ||
|
||
| } | ||
|
|
||
| return aggregateOutcome; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Updates given resutls with parent info if results are greater than 1. | ||
| /// Add parent results as first result in updated result. | ||
| /// </summary> | ||
| /// <param name="results">Results.</param> | ||
| /// <param name="parentResult">Parent results.</param> | ||
| /// <returns>Updated results which contains parent result as first result. All other results contains parent result info.</returns> | ||
| private List<UTF.TestResult> UpdateResultsWithParentInfo(List<UTF.TestResult> results, UTF.TestResult parentResult) | ||
| { | ||
| // Return results in case there are no results. | ||
| if (!results.Any()) | ||
| { | ||
| return results; | ||
| } | ||
|
|
||
| // UpdatedResults contain parent result at first position and remaining results has parent info updated. | ||
| var updatedResults = new List<UTF.TestResult>(); | ||
| updatedResults.Add(parentResult); | ||
|
|
||
| foreach (var result in results) | ||
| { | ||
| result.ExecutionId = Guid.NewGuid(); | ||
| result.ParentExecId = parentResult.ExecutionId; | ||
| parentResult.InnerResultsCount++; | ||
|
|
||
| updatedResults.Add(result); | ||
| } | ||
|
|
||
| return updatedResults; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,9 @@ public static UnitTestResult[] ToUnitTestResults(this UTF.TestResult[] testResul | |
| unitTestResult.DisplayName = testResults[i].DisplayName; | ||
| unitTestResult.DatarowIndex = testResults[i].DatarowIndex; | ||
| unitTestResult.ResultFiles = testResults[i].ResultFiles; | ||
| unitTestResult.ExecutionId = testResults[i].ExecutionId; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add/modify some existing tests to cover this as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| unitTestResult.ParentExecId = testResults[i].ParentExecId; | ||
| unitTestResult.InnerResultsCount = testResults[i].InnerResultsCount; | ||
| unitTestResults[i] = unitTestResult; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,7 +61,9 @@ public static UnitTestOutcome ToUnitTestOutcome(this UTF.UnitTestOutcome framewo | |
| /// <returns> Outcome which has higher importance.</returns> | ||
| internal static UTF.UnitTestOutcome GetMoreImportantOutcome(this UTF.UnitTestOutcome outcome1, UTF.UnitTestOutcome outcome2) | ||
| { | ||
| return outcome1 < outcome2 ? outcome1 : outcome2; | ||
| var unitTestOutcome1 = outcome1.ToUnitTestOutcome(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch 👍 |
||
| var unitTestOutcome2 = outcome2.ToUnitTestOutcome(); | ||
| return unitTestOutcome1 < unitTestOutcome2 ? outcome1 : outcome2; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,6 +75,21 @@ internal UnitTestResult(UnitTestOutcome outcome, string errorMessage) | |
| /// </summary> | ||
| public string ErrorStackTrace { get; internal set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the execution id of the result | ||
| /// </summary> | ||
| public Guid ExecutionId { get; internal set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the parent execution id of the result | ||
| /// </summary> | ||
| public Guid ParentExecId { get; internal set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the inner results count of the result | ||
| /// </summary> | ||
| public int InnerResultsCount { get; internal set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets the duration of the result | ||
| /// </summary> | ||
|
|
@@ -149,6 +164,10 @@ internal TestResult ToTestResult(TestCase testCase, DateTimeOffset startTime, Da | |
| EndTime = endTime | ||
| }; | ||
|
|
||
| testResult.SetPropertyValue<Guid>(Constants.ExecutionIdProperty, this.ExecutionId); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar for this. Add/modify existing tests to cover this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
| testResult.SetPropertyValue<Guid>(Constants.ParentExecIdProperty, this.ParentExecId); | ||
| testResult.SetPropertyValue<int>(Constants.InnerResultsCountProperty, this.InnerResultsCount); | ||
|
|
||
| if (!string.IsNullOrEmpty(this.StandardOut)) | ||
| { | ||
| TestResultMessage message = new TestResultMessage(TestResultMessage.StandardOutCategory, this.StandardOut); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,7 +38,10 @@ public void ExecuteCustomTestExtensibilityWithTestDataTests() | |
| "CustomTestMethod2 (B)", | ||
| "CustomTestMethod2 (B)", | ||
| "CustomTestMethod2 (B)"); | ||
| this.ValidateFailedTests( | ||
|
|
||
| // Parent results should fail and thus failed count should be 7. | ||
| this.ValidateFailedTestsCount(7); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did we add this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ValidateFailedTests method checks for number of tests. Number of params passed to the methods are considered as numbers of tests. In our case, we dont want to pass parent results as param but still want to consider it for results length check. Thus checking the count of tests separately. |
||
| this.ValidateFailedTestsContain( | ||
| TestAssembly, | ||
| "CustomTestMethod2 (A)", | ||
| "CustomTestMethod2 (A)", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -429,9 +429,11 @@ public void RunTestMethodForMultipleResultsReturnMultipleResults() | |
| var testMethodRunner = new TestMethodRunner(testMethodInfo, this.testMethod, this.testContextImplementation, false); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a bunch of data-driven tests in this class. Please make sure they are validating correct things even though they are not failing. Ideally they should have failed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Various data driven tests were failing. We have fixed them in the PR. |
||
|
|
||
| var results = testMethodRunner.Execute(); | ||
| Assert.AreEqual(2, results.Length); | ||
| Assert.AreEqual(AdapterTestOutcome.Passed, results[0].Outcome); | ||
| Assert.AreEqual(AdapterTestOutcome.Failed, results[1].Outcome); | ||
| Assert.AreEqual(3, results.Length); | ||
|
|
||
| // results[0] is parent result. | ||
| Assert.AreEqual(AdapterTestOutcome.Passed, results[1].Outcome); | ||
| Assert.AreEqual(AdapterTestOutcome.Failed, results[2].Outcome); | ||
| } | ||
|
|
||
| [TestMethodV1] | ||
|
|
@@ -543,9 +545,11 @@ public void RunTestMethodShouldSetDataRowIndexForDataDrivenTestsWhenDataIsProvid | |
| var results = testMethodRunner.RunTestMethod(); | ||
|
|
||
| // check for datarowIndex | ||
| Assert.AreEqual(results[0].DatarowIndex, 0); | ||
| Assert.AreEqual(results[1].DatarowIndex, 1); | ||
| Assert.AreEqual(results[2].DatarowIndex, 2); | ||
| // 1st is parent result. | ||
| Assert.AreEqual(results[0].DatarowIndex, -1); | ||
| Assert.AreEqual(results[1].DatarowIndex, 0); | ||
| Assert.AreEqual(results[2].DatarowIndex, 1); | ||
| Assert.AreEqual(results[3].DatarowIndex, 2); | ||
| } | ||
|
|
||
| [TestMethodV1] | ||
|
|
@@ -570,9 +574,11 @@ public void RunTestMethodShoudlRunOnlyDataSourceTestsWhenBothDataSourceAndDataRo | |
| var results = testMethodRunner.RunTestMethod(); | ||
|
|
||
| // check for datarowIndex as only DataSource Tests are Run | ||
| Assert.AreEqual(results[0].DatarowIndex, 0); | ||
| Assert.AreEqual(results[1].DatarowIndex, 1); | ||
| Assert.AreEqual(results[2].DatarowIndex, 2); | ||
| // 1st is parent result. | ||
| Assert.AreEqual(results[0].DatarowIndex, -1); | ||
| Assert.AreEqual(results[1].DatarowIndex, 0); | ||
| Assert.AreEqual(results[2].DatarowIndex, 1); | ||
| Assert.AreEqual(results[3].DatarowIndex, 2); | ||
| } | ||
|
|
||
| [TestMethodV1] | ||
|
|
@@ -640,8 +646,80 @@ public void RunTestMethodShouldSetResultFilesIfPresentForDataDrivenTests() | |
| this.testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(this.methodInfo, It.IsAny<Type>(), It.IsAny<bool>())).Returns(attribs); | ||
|
|
||
| var results = testMethodRunner.RunTestMethod(); | ||
| CollectionAssert.Contains(results[0].ResultFiles.ToList(), "C:\\temp.txt"); | ||
| CollectionAssert.Contains(results[1].ResultFiles.ToList(), "C:\\temp.txt"); | ||
| CollectionAssert.Contains(results[2].ResultFiles.ToList(), "C:\\temp.txt"); | ||
| } | ||
|
|
||
| [TestMethodV1] | ||
| public void RunTestMethodShouldReturnParentResultForDataDrivenTests() | ||
| { | ||
| var testMethodInfo = new TestableTestmethodInfo(this.methodInfo, this.testClassInfo, this.testMethodOptions, () => new UTF.TestResult()); | ||
| var testMethodRunner = new TestMethodRunner(testMethodInfo, this.testMethod, this.testContextImplementation, false); | ||
|
|
||
| UTF.DataSourceAttribute dataSourceAttribute = new UTF.DataSourceAttribute("DummyConnectionString", "DummyTableName"); | ||
|
|
||
| var attribs = new Attribute[] { dataSourceAttribute }; | ||
|
|
||
| // Setup mocks | ||
| this.testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(this.methodInfo, It.IsAny<Type>(), It.IsAny<bool>())).Returns(attribs); | ||
| this.testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, this.testContextImplementation)).Returns(new object[] { 1, 2, 3 }); | ||
|
|
||
| var results = testMethodRunner.RunTestMethod(); | ||
|
|
||
| // check for parent result | ||
| Assert.AreEqual(4, results.Length); | ||
| Assert.AreEqual(results[0].ExecutionId, results[1].ParentExecId); | ||
| } | ||
|
|
||
| [TestMethodV1] | ||
| public void RunTestMethodShouldNotReturnParentResultForNonDataDrivenTests() | ||
| { | ||
| var testMethodInfo = new TestableTestmethodInfo(this.methodInfo, this.testClassInfo, this.testMethodOptions, () => new UTF.TestResult()); | ||
| var testMethodRunner = new TestMethodRunner(testMethodInfo, this.testMethod, this.testContextImplementation, false); | ||
|
|
||
| UTF.DataSourceAttribute dataSourceAttribute = new UTF.DataSourceAttribute("DummyConnectionString", "DummyTableName"); | ||
|
|
||
| var attribs = new Attribute[] { dataSourceAttribute }; | ||
|
|
||
| // Setup mocks | ||
| this.testablePlatformServiceProvider.MockReflectionOperations.Setup(rf => rf.GetCustomAttributes(this.methodInfo, It.IsAny<Type>(), It.IsAny<bool>())).Returns(attribs); | ||
| this.testablePlatformServiceProvider.MockTestDataSource.Setup(tds => tds.GetData(testMethodInfo, this.testContextImplementation)).Returns(new object[] { 1 }); | ||
|
|
||
| var results = testMethodRunner.RunTestMethod(); | ||
|
|
||
| // Parent result should not exist. | ||
| Assert.AreEqual(1, results.Length); | ||
| Assert.AreEqual(Guid.Empty, results[0].ParentExecId); | ||
| } | ||
|
|
||
| [TestMethodV1] | ||
| public void RunTestMethodShouldSetParentResultOutcomeProperly() | ||
| { | ||
| var testMethodAttributeMock = new Mock<UTF.TestMethodAttribute>(); | ||
| testMethodAttributeMock.Setup(_ => _.Execute(It.IsAny<UTF.ITestMethod>())).Returns(new[] | ||
| { | ||
| new UTF.TestResult { Outcome = UTF.UnitTestOutcome.Passed }, | ||
| new UTF.TestResult { Outcome = UTF.UnitTestOutcome.Failed } | ||
| }); | ||
|
|
||
| var localTestMethodOptions = new TestMethodOptions | ||
| { | ||
| Timeout = 200, | ||
| Executor = testMethodAttributeMock.Object, | ||
| TestContext = this.testContextImplementation, | ||
| ExpectedException = null | ||
| }; | ||
|
|
||
| var testMethodInfo = new TestableTestmethodInfo(this.methodInfo, this.testClassInfo, localTestMethodOptions, null); | ||
| var testMethodRunner = new TestMethodRunner(testMethodInfo, this.testMethod, this.testContextImplementation, false); | ||
|
|
||
| var results = testMethodRunner.Execute(); | ||
| Assert.AreEqual(3, results.Length); | ||
|
|
||
| // Parent result should show proper aggregate outcome. | ||
| Assert.AreEqual(AdapterTestOutcome.Failed, results[0].Outcome); | ||
| Assert.AreEqual(AdapterTestOutcome.Passed, results[1].Outcome); | ||
| Assert.AreEqual(AdapterTestOutcome.Failed, results[2].Outcome); | ||
| } | ||
|
|
||
| #region Test data | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: results.Count==0 seems more readable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of list, using Count or Any doesn't matter. But in case of Enumerable any is preferred over count as count iterates through entire list. Thus as a general practice, for both list and enumerable I prefer any.