Saturday, December 26, 2020

Generate test data for C# unit

Nowadays, many projects are developed by using C#. As the projects grow bigger and bigger, the test effort becomes enormous. As a result, the need for automation testing for such projects become higher and higher. In this post, I share one of an approach for generating test data for C# projects automatically. This will dramatically increase the test effectiveness and reduce test effort for projects. The key idea of the method is to use abstract syntax tree (AST) to generate test data for a C# unit. From the generated AST, we can generate test data for test paths, do a lot of interesting things for the selected project such as calculating code coverage, making code instrumentation, etc. 

The first step of the method is to read all source code for the project we want to generate test data for. Assume that we have such source code stored in a string variable as follows.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace SampleCode

{

    class Program

    {

        static void Main(string[] args)

        {

        }

    }

 

    public class Student

    {

        Teacher _teacher;

        public Student()

        {

 

        }

        public string Name { get; set; }

        public string Age { get; set; }

        public Teacher Teacher { get => _teacher; set => _teacher = value; }

 

    }

    public class Teacher

    {

        public Teacher()

        {

 

        }

        public string Name { get; set; }

        public string Degree { get; set; }

    }

}

 The sample code below will guide you on how to get the list of classes declared in the project. Later, you can do related things such as finding methods, properties, declarations, generating test paths, test data, etc. from your code.

using Microsoft.CodeAnalysis;

using Microsoft.CodeAnalysis.CSharp;

using Microsoft.CodeAnalysis.CSharp.Syntax;

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

 

namespace AST4CSharp

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

            string sourceCode =

                @"using System;

                using System.Collections.Generic;

                using System.Linq;

                using System.Text;

                using System.Threading.Tasks;

 

                namespace SampleCode

                {

                    class Program

                    {

                        static void Main(string[] args)

                        {

                        }

                    }

 

                    public class Student

                    {

                        Teacher _teacher;

                        public Student()

                        {

 

                        }

                        public string Name { get; set; }

                        public string Age { get; set; }

                        public Teacher Teacher { get => _teacher; set => _teacher = value; }

 

                    }

                    public class Teacher

                    {

                        public Teacher()

                        {

 

                        }

                        public string Name { get; set; }

                        public string Degree { get; set; }

                    }

                }";

 

            SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceCode);

            CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

 

            var finder = new ClassFinder();

            finder.Visit(root);

 

            foreach (var c in finder.classes)

            {

                Console.WriteLine(c.Identifier.Text);

            }

        }

 

        public class ClassFinder : CSharpSyntaxWalker

        {

            public ICollection<ClassDeclarationSyntax> classes { get; } = new List<ClassDeclarationSyntax>();

 

            public override void VisitClassDeclaration(ClassDeclarationSyntax node)

            {

 

                classes.Add(node);

 

                base.VisitClassDeclaration(node);

            }

        }

    }

}

 The result of the above source code is shown below.

Program

Student

Teacher