Tarun Arora

Visual Studio ALM MVP
posts - 58, comments - 187, trackbacks - 0

My Links

News


Tarun Arora is a Microsoft Certified professional developer for Enterprise Applications. He has extensively travelled around the world gaining experience learning and working in culturally diverse teams. Tarun has over 5 years of experience developing 'Energy Trading & Risk Management' solutions for leading Trading & Banking Enterprises. His passion for technology has earned him the Microsoft Community Contributor and Microsoft MVP Award.




View Tarun Arora's profile on LinkedIn

profile for Tarun Arora at Stack Overflow, Q&A for professional and enthusiast programmers

Tag Cloud

Article Categories

Archives

Post Categories

Image Galleries

MTM Testing Scorecard using TFS API

 

Download a working demo (below): No showing off, this has been developed in VS11 using MTM11 and version controlled in TFS Azure Smile

In this blog post I’ll be showing you how to use the TFS API to build a testing scorecard. In this testing score card, I’ll be going through each of the test plans programmatically looping through test suits (yes, the test suits can be nested) looking for test cases and querying their status and further going in at the test step level to get the status of each test step individually. The outcome will look something like below,

clip_image001

We all love our testers, they work so hard to make sure that the software is delivered with utmost quality. I thought of creating a report which aggregated test step status, test case status, test suit status and test plan status and presented the result in one single view, this would really simplify their discussions during the scrum daily stand ups. But I hit a problem…

=> Problem

It is not possible to report on the actual progress of test steps in the test case in Microsoft Test Manager. The out of the box reports can tell you the pass/fail status at test case level, not individual test steps. There is nothing wrong with this, Microsoft test manager does not enforce the user to mark test step pass or fail at each level so there is nothing wrong with no out of the box support for reporting against test steps.

image

In the screen shot from Microsoft Test Manager you can see that the test steps have been marked as passed/failed now if i wanted to report on them, there were two possibilities

  • Create a TFS Warehouse adapter that will process all data first from different tables/XML and then move to the TFS warehouse. But that might not be the best option since exporting data at the test step level to the warehouse would significantly increase the size of the warehouse and this is one of the reasons possibly why the build in warehouse does not process the test step result data.

You might turn around to ask why can I not use powerpivot over the tfs operation database tables where test results are stored and build my report off that. There are two reasons why that would not work,

1. Test step is a custom control where the data is stored as XML blob in database. The test result view shown in the screen shot above is possible because MTM (Microsoft Test Manager) actually parses the XML and correlates it with the Test result information before showing it in the MTM.

2. You should never query the TFS operation databases directly. Any interaction to the TFS operational database should be done through the TFS API.

  • Use the Test Case Management (TCM) object model which is part of the TFS API to create a custom report. To be honest it is not too difficult going down this route. Read on for step by step of how I implemented this report…

If you are enjoying this blog post, don’t forget to subscribe to http://feeds.feedburner.com/TarunArora I’ll be exploring this space and posting further solutions…

 

=> Connect to TFS Programmatically using TFS API

I have a separate blog post on how to connect to TFS programmatically using the TFS API. In the below code snippet you can see I am getting a list of team projects using the VersionControlServerService.

public List<string> GetTeamProjects()
        {
            var tfs = TfsTeamProjectCollectionFactory
            .GetTeamProjectCollection(new Uri(ConfigurationManager.AppSettings["TfsUri"]));
            var service = tfs.GetService<VersionControlServer>();
            return service.GetAllTeamProjects(true).Select(i => i.Name).ToList();
        }

 

=> Get all Test Plans in a team Project Programmatically using TFS API

I’ll be using the ITestManagementService to query for the test plans,

public List<string> GetTestPlans(string teamProject)
        {
            var tfs = TfsTeamProjectCollectionFactory
            .GetTeamProjectCollection(new Uri(ConfigurationManager.AppSettings["TfsUri"]));
            var service = tfs.GetService<ITestManagementService>();
            var testProject = service.GetTeamProject(teamProject);
            return testProject.TestPlans.Query("SELECT * FROM TestPlan").Select(i => i.Name).ToList();
        }

 

=> Get all Test Suits in a Test Plan Programmatically using TFS API

Now it gets slightly tricky the test suits can be nested.

            // 1. Connect to TFS and Get Test Managemenet Service and Get all Test Plans
            var tfs = TfsTeamProjectCollectionFactory
            .GetTeamProjectCollection(new Uri(ConfigurationManager.AppSettings["TfsUri"]));
            var service = tfs.GetService<ITestManagementService>();
            var testProject = service.GetTeamProject(teamProject);
            var plan = testProject.TestPlans.Query("SELECT * FROM TestPlan").
                                        .Where(tp => tp.Name == selectedTestPlan).FirstOrDefault();
            List<ScoreBoard> scoreboards = new List<ScoreBoard>();
            var testSuits = new List<IStaticTestSuite>();
            if (plan.RootSuite != null)
            {
                testSuits.AddRange(TestSuiteHelper.GetTestSuiteRecursive(plan.RootSuite));
            }
            TestPlan testPlan = new TestPlan() { Id = plan.Id, Name = plan.Name, TestSuites = testSuits };

So, I’ll be using a simple stack to push a test suite get all related test suits and pop the original test suit and recursively perform this operation

image

static class TestSuiteHelper
        {
            public static List<IStaticTestSuite> GetTestSuiteRecursive(IStaticTestSuite b)
            {
                // 1. Store results in the IStaticTestSuit list.
                List<IStaticTestSuite> result = new List<IStaticTestSuite>();
                // 2. Store a stack of our TestSuite.
                Stack<IStaticTestSuite> stack = new Stack<IStaticTestSuite>();
                // 3. Add Root Test Suite
                stack.Push(b);
                // 4. Continue while there are TestSuites to Process
                while (stack.Count > 0)
                {
                    // A. Get top Suite
                    var dir = stack.Pop();
                    try
                    {
                        // B. Add all TestSuite at this directory to the result List.
                        result.Add(dir);
                        // C. Add all SubSuite at this TestSuite.
                        foreach (IStaticTestSuite ss in dir.SubSuites)
                        {
                            stack.Push(ss);
                        }
                    }
                    catch
                    {
                        // D. Fails to open the test suite
                    }
                }
                return result;
            }
        }

 

Now we have a collection of all Test suits under the test plan, lets process each test suit to get the test cases.

=> Get all Test Cases in a Test Plan Programmatically using TFS API

The Test suite has the TestCases property, you can recursively go through all test cases in the test suit.

for (int i = 0; i < testPlan.TestSuites.Count; i++)
            {
                var ts = testPlan.TestSuites[i];

                 foreach (var tc in ts.TestCases)
                {
                    
                    // tc is a test case object
}
            }

 

=> Get all Test Steps in a Test Case Programmatically using TFS API

Now that we have all the test case we can go through and get the test steps inside the test case.

foreach (var tc in ts.TestCases)
                {
                    foreach (var a in tc.TestCase.Actions)
                    {
                        // a is a test step                      
                    }
                 }

=> Get the Test Step results Programmatically using TFS API

The test results for a test case can be queried by using the TestResults property. Outcome is the property that will reveal the test outcome.

var testResults = testProject.TestResults.ByTestId(tc.TestCase.TestSuiteEntry.Id);
                    foreach (var result in testResults)
                    {
                        for (int actionIndex = 0; actionIndex < tc.TestCase.Actions.Count; actionIndex++)
                        {
                            var action = tc.TestCase.Actions[actionIndex];
                            if (!(action is ITestStep))
                            {
                                continue;
                            }
                            var step = action as ITestStep;
                            var topIteration = result.Iterations.FirstOrDefault();
                            if (topIteration == null)
                                continue;
                            
                            testStepScores.Add(topIteration.Actions[actionIndex].Outcome.ToString());
                        }
                    }

 

=> Putting it all together

Now that we know what we need to do, let’s put it all together,

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.TestManagement.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace TestResults
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            cbTeamProjects.DataSource = GetTeamProjects();
        }
        public class TestPlan
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public List<IStaticTestSuite> TestSuites { get; set; }
            public override string ToString()
            {
                return String.Format("{0}, {1}, {2}", Id, Name, TestSuites.Count);
            }
        }
        static class TestSuiteHelper
        {
            public static List<IStaticTestSuite> GetTestSuiteRecursive(IStaticTestSuite b)
            {
                // 1. Store results in the IStaticTestSuit list.
                List<IStaticTestSuite> result = new List<IStaticTestSuite>();
                // 2. Store a stack of our TestSuite.
                Stack<IStaticTestSuite> stack = new Stack<IStaticTestSuite>();
                // 3. Add Root Test Suite
                stack.Push(b);
                // 4. Continue while there are TestSuites to Process
                while (stack.Count > 0)
                {
                    // A. Get top Suite
                    var dir = stack.Pop();
                    try
                    {
                        // B. Add all TestSuite at this directory to the result List.
                        result.Add(dir);
                        // C. Add all SubSuite at this TestSuite.
                        foreach (IStaticTestSuite ss in dir.SubSuites)
                        {
                            stack.Push(ss);
                        }
                    }
                    catch
                    {
                        // D. Fails to open the test suite
                    }
                }
                return result;
            }
        }
        
        public List<string> GetTeamProjects()
        {
            var tfs = TfsTeamProjectCollectionFactory
            .GetTeamProjectCollection(new Uri(ConfigurationManager.AppSettings["TfsUri"]));
            var service = tfs.GetService<VersionControlServer>();
            return service.GetAllTeamProjects(true).Select(i => i.Name).ToList();
        }

        public List<string> GetTestPlans(string teamProject)
        {
            var tfs = TfsTeamProjectCollectionFactory
            .GetTeamProjectCollection(new Uri(ConfigurationManager.AppSettings["TfsUri"]));
            var service = tfs.GetService<ITestManagementService>();
            var testProject = service.GetTeamProject(teamProject);
            return testProject.TestPlans.Query("SELECT * FROM TestPlan").Select(i => i.Name).ToList();
        }

        public class ScoreBoard
        {
            public string TeamProject { get; set; }
            public string TestPlan { get; set; }
            public int Id { get; set; }
            public string Title { get; set; }
            public string State { get; set; }
            public int TestCaseCount { get; set; }
            public int TestCaseDesign { get; set; }
            public int TestCaseReady { get; set; }
            public int TestCaseClosed { get; set; }
            public int TestStepCount { get; set; }
            public int TestStepPassedCount { get; set; }
            public int TestStepFailedCount { get; set; }
            public int TestStepOutstandingCount { get; set; }
        }
        public List<ScoreBoard> GetTestStatistics(string teamProject, string selectedTestPlan)
        {
            // 1. Connect to TFS and Get Test Managemenet Service and Get all Test Plans
            var tfs = TfsTeamProjectCollectionFactory
            .GetTeamProjectCollection(new Uri(ConfigurationManager.AppSettings["TfsUri"]));
            var service = tfs.GetService<ITestManagementService>();
            var testProject = service.GetTeamProject(teamProject);
            var plan = testProject.TestPlans.Query("SELECT * FROM TestPlan").Where(tp => tp.Name == selectedTestPlan).FirstOrDefault();
            List<ScoreBoard> scoreboards = new List<ScoreBoard>();
            var testSuits = new List<IStaticTestSuite>();
            if (plan.RootSuite != null)
            {
                testSuits.AddRange(TestSuiteHelper.GetTestSuiteRecursive(plan.RootSuite));
            }
            TestPlan testPlan = new TestPlan() { Id = plan.Id, Name = plan.Name, TestSuites = testSuits };

            for (int i = 0; i < testPlan.TestSuites.Count; i++)
            {
                var ts = testPlan.TestSuites[i];
                var scoreboard = new ScoreBoard();
                scoreboard.TeamProject = teamProject;
                scoreboard.TestPlan = selectedTestPlan;
                scoreboard.Id = ts.Id;
                scoreboard.Title = ts.Title;
                scoreboard.State = ts.State.ToString();
                scoreboard.TestCaseCount = ts.TestCases.Count;
                scoreboard.TestCaseDesign = ts.TestCases.Where(tcs => tcs.TestCase.State == "Design").ToList().Count; ;
                scoreboard.TestCaseReady = ts.TestCases.Where(tcs => tcs.TestCase.State == "Ready").ToList().Count;
                scoreboard.TestCaseClosed = ts.TestCases.Where(tcs => tcs.TestCase.State == "Closed").ToList().Count;
                int totalTestSteps = 0;
                List<string> testStepScores = new List<string>();
                foreach (var tc in ts.TestCases)
                {
                    foreach (var a in tc.TestCase.Actions)
                    {
                        totalTestSteps = totalTestSteps + 1;
                    }
                    var testResults = testProject.TestResults.ByTestId(tc.TestCase.TestSuiteEntry.Id);
                    foreach (var result in testResults)
                    {
                        for (int actionIndex = 0; actionIndex < tc.TestCase.Actions.Count; actionIndex++)
                        {
                            var action = tc.TestCase.Actions[actionIndex];
                            if (!(action is ITestStep))
                            {
                                continue;
                            }
                            var step = action as ITestStep;
                            var topIteration = result.Iterations.FirstOrDefault();
                            if (topIteration == null)
                                continue;
                            var test = topIteration.Actions[actionIndex];
                            testStepScores.Add(topIteration.Actions[actionIndex].Outcome.ToString());
                        }
                    }
                }
                scoreboard.TestStepCount = totalTestSteps;
                scoreboard.TestStepPassedCount = testStepScores.Where(tsp => tsp == "Passed").ToList().Count;
                scoreboard.TestStepFailedCount = testStepScores.Where(tsp => tsp == "Failed").ToList().Count;
                scoreboard.TestStepOutstandingCount = testStepScores.Where(tsp => tsp == "None").ToList().Count;
                scoreboards.Add(scoreboard);
            }
            return scoreboards;
        }
        public void button1_Click(object sender, EventArgs e)
        {
            IStaticTestSuiteBindingSource.DataSource =
            GetTestStatistics(cbTeamProjects.SelectedValue.ToString(), cbTestPlans.SelectedValue.ToString());
            this.reportViewer1.RefreshReport();
        }
        private void cbTeamProjects_SelectedIndexChanged(object sender, EventArgs e)
        {
            var teamProject = ((ComboBox)sender).SelectedItem.ToString();
            if (!string.IsNullOrEmpty(teamProject))
            {
                cbTestPlans.DataSource = GetTestPlans(teamProject);
            }
        }
    }
}

=> Conclusion

Microsoft has done a wonderful job making the TFS API available to us, all interactions with the TFS operational database should be done through the TFS API. What test report views do you use for your testing stand ups? If you have any recommendations on things that we could add to this report or any questions or feedback, feel free to add to this blog post. If you enjoyed the post, remember to subscribe to http://feeds.feedburner.com/TarunArora.

Cheers, Tarun

Share this post :

Print | posted on Sunday, October 02, 2011 9:29 PM | Filed Under [ TFS2010 vNext TFS API TFS SDK TFS Utilties TfsService Tfs11 MTM ]

Feedback

Gravatar

# re: MTM Testing Scorecard using TFS API

Hi,

Getting the test step states could be interesting but are getting the tests case execution state of pass/fail/blocked. It think you are missing that in your code. I use the test case state of design/active/closed as it releates to the current state of the application (when a test case is closed, it means that the application under test no longer matches that test case). For me that state is not used to report excecution status of the test case.

For test planning: A test plan is linked to test suites which are linked to test cases.
For test execution: A test plan is linked to test runs which are linked to test results which is linked to test cases. In MTM from a given test case (suite/configuration) you can see all the related test result.

10/3/2011 8:06 PM | Bertrand
Gravatar

# re: MTM Testing Scorecard using TFS API

Excellent Post, Thanks!
10/10/2011 8:33 AM | Subodh Sohoni
Gravatar

# re: MTM Testing Scorecard using TFS API

Great blog post! Very useful to see test step data
11/2/2011 10:58 AM | Anu
Gravatar

# re: MTM Testing Scorecard using TFS API

Cheers Anu, It's always good to hear feedback :-]
Would you have any recommendations on features we could further build into this report?
11/2/2011 11:00 AM | Tarun Arora
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi, this is great. Thanks! But is there a download link? Or is the idea just to copy from the displayed code? If there is a download, I don't see it. Do I need to register and login to have access to the downloads? Thanks, Bob
11/3/2011 3:24 PM | Bob Hardister
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Bob,

Thanks for your feedback. The link is available on the top, no registration required, or use this link instead, http://geekswithblogs.net/images/geekswithblogs_net/TarunArora/Windows-Live-Writer/TFS-SDK_117CD/TestResults.zip.

Cheers, Tarun
11/3/2011 4:03 PM | Tarun Arora
Gravatar

# re: MTM Testing Scorecard using TFS API

Tarun, got it. Thanks! :)
11/3/2011 7:42 PM | Bob Hardister
Gravatar

# re: MTM Testing Scorecard using TFS API

Thanks for the demo download to help us report on our Test Results for our software projects.
11/21/2011 10:57 PM | Becky
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Becky,

Glad it helps!

Cheers, Tarun
11/21/2011 11:00 PM | Tarun Arora
Gravatar

# re: MTM Testing Scorecard using TFS API

I'm trying to run this against my TFS 2010 server. The application crashes...I've tried recompiling against 4.0 and I installed 4.5. Does this work against tfs 2010?
1/11/2012 12:46 PM | Allen Feinberg
Gravatar

# re: MTM Testing Scorecard using TFS API

The error I'm getting is:
Could not load file or assembly 'Microsoft.ReportViewer.WinForms, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies. The system cannot find the file specified.
1/11/2012 12:50 PM | Allen Feinberg
Gravatar

# re: MTM Testing Scorecard using TFS API

So I've set all the references to copy locally and now the application opens...but when I try to generate a report I get the following error in the report window.

An error occurred during local report processing. The definition of the report " is invalid. An unexpected error occured in Report Processing. Could not load file or assembly 'Microsoft.SqlServer.Types, Version 11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies. The system cannot find the file specified.
1/11/2012 1:06 PM | Allen Feinberg
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Allen,

Open the application and replace all the microsoft visual studio tfs dll's with version 10. Since i developed this for TFS Server (TFS 2011) the dll's i added show as 11.

E-mail me on tarun (.) arora at avanade (.) com and i'll email you a .net 4 tfs 2010 version of this application.

Cheers, Tarun
1/11/2012 8:06 PM | Tarun Arora
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Allen,

You should have received the email on your hotmail id. Please give it a go, happy to work with you in case there are any issues.

Cheers, Tarun
1/11/2012 8:19 PM | Tarun Arora
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi,

I think I found a bug in your tool. If the user changed the TestCase after running a test and adds an additional test step, line 182
topIteration.Actions[actionIndex]; fails with an argument out of range exception. It seems you have to get the TestCase revision of your result. I already managed to get the old TestCase revision, but I couldn't get the old actions. One of my users changed the action text, but the Text I get is always the new one, even if I get the old revision. Any idea? The strange thing is that MTM is showing the correct (old) test.
1/26/2012 9:29 AM | Thomas Liebethal
Gravatar

# re: MTM Testing Scorecard using TFS API

Thanks for reporting the issue. I'll have to look at this in more detail. Are you using the 2010 version or 2011 version? Can you email me the package and the changes you have made to my mail id?
1/26/2012 9:46 AM | Tarun Arora
Gravatar

# re: MTM Testing Scorecard using TFS API

Exactly what I needed. Thanks, Tarun!
2/11/2012 12:20 AM | Clark
Gravatar

# re: MTM Testing Scorecard using TFS API

Can you send me the 2010 version? Thank you!!
3/19/2012 7:05 PM | Laurie
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Laurie,

Emailed you a version of TFS 2010/.net 4.0 MTM scorecard.

Cheers, Tarun
3/22/2012 12:18 PM | Tarun Arora
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Tarun,

First let me congratulate you with the exceptional work.

Is it possible to send me the TFS 2010 version also, please?

Another question, as you say in:
"1. Test step is a custom control where the data is stored as XML blob in database. (...) actually parses the XML and correlates it with the Test result information before showing it in the MTM."
How can i reach this XML? I really need create a report with that look-a-like (step + result), so if i could get it, I would build a parser.
Thx a lot
3/28/2012 4:27 PM | Pedro Bastos
Gravatar

# re: MTM Testing Scorecard using TFS API

Tarun, pay no attention to the previous question. :) If you can send me the TFS 2010 version anyway it would be great! Thx
3/28/2012 6:41 PM | Pedro Bastos
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Tarun,
This score card seems useful to us. Can you email me dependencies for TFS 2010/.NET 4.0 as well?

Thanks
4/2/2012 9:47 PM | Arvind
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Arvind,

Emailed you a version of TFS 2010/.net 4.0 MTM scorecard.

Cheers, Tarun
4/3/2012 11:00 AM | Tarun Arora
Gravatar

# re: MTM Testing Scorecard using TFS API

Tarun - I always enjoy your posts and this one is of particular interest to me.

I had no trouble running your "Connecting to TFS 2010 Programmatically" project in VS2010

I am trying to run this one in VS11 Beta and it's failing immediately at
var service = tfs.GetService<VersionControlServer>();
with a 404 not found.

I am using the same uri for "TfsUri" that I used in the first example. I notice yours was a little different between the examples.

Admittedly, I am coming at this with not much background and just trying to "get something to work" as a basis for further learning. Any idea what the issue might be?

Keep up the good work,

Joel
4/26/2012 12:21 AM | Joel
Gravatar

# re: MTM Testing Scorecard using TFS API

Great post, thank you for explaining it so thoroughly. Will you please also send me your TFS2010 / .net 4.0 project?
5/3/2012 4:21 PM | Dave
Gravatar

# re: MTM Testing Scorecard using TFS API

Could you please get me a 2010/4.0 version as well? Thanks
5/4/2012 10:23 PM | Tom
Gravatar

# re: MTM Testing Scorecard using TFS API

Great work. I am using VSTS 2010. when i open the solution i see the message 'This project is incompatible with the current version of Visual studio.'

Could you please send me the send me your TFS2010 / .net 4.0 project
5/10/2012 1:10 PM | Tahir
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi Tarun,
When I open this "TestResults.sln" in my VSTS 2010. it show error message
"This selected file is a solution file, but was created by a newer version of this application and can't be opned"
5/15/2012 2:59 PM | Diwan Bisht
Gravatar

# re: MTM Testing Scorecard using TFS API

I'm trying to run this against my TFS 2010 server. The application crashes...I've tried recompiling against 4.0 and I installed 4.5. Does this work against tfs 2010?
getting this issue when changed from

Could not resolve this reference. Could not locate the assembly "Microsoft.ReportViewer.Common, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors. TestResults
Please suggest me
Thanks
5/16/2012 7:36 AM | Manu
Gravatar

# re: MTM Testing Scorecard using TFS API

Hi,

This is great and exactly what I've been trying to do myself. Can you send me the TFS2010 / .net 4.0 project?
5/21/2012 2:47 PM | Trish
Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification:
 
 

Powered by: