Wiki

Clone wiki

TraceabilityTutorial / Home

    #Tutorial

    This is a quick tutorial covering how to add traceability in a project. All projects were created using dotnetcore 2.1. The unit tests were creating using xUnit tests.

    Each of the headers below link to a different branch in the BitBucket repository for this project. This allows us to easily switch between different views as the tutorial progresses.

    ##Step 1 Initial Setup

    Our initial setup is representative of a simple utility library. It consists of the class library project named Sorting and an xUnit test project named SortingTests.

    The Sorting project has one folder, Algorithms which contains an interface, a DataSet class, and five sorting algorithms.

    • DataSet.cs holds a set of data that can be sorted
    • HeapSort.cs sorts a DataSet using the heap sort algorithm
    • ISorter.cs is an interface for the sorting algorithms
    • InsertSort.cs sorts a DataSet using the insert sort algorithm
    • QuickSort.cs sorts a DataSet using the quick sort algorithm
    • SelectionSort.cs sorts a DataSet using the selection sort algorithm
    • SimpleSort.cs sorts a DataSet using the simple sort algorithm

    Each of the classes were designed with tracing being added on later. This means that every member of each class is virtual.

    SortingTests contains two support files and five test files.

    • AssertOrdering.cs tests if all values in a DataSet are in order
    • DataGenerator.cs generates different orderings of data.
    • HeapSortTests.cs contains unit tests for HeapSort
    • InsertSortTests.cs contains unit tests for InsertSort
    • QuickSortTests.cs contains unit tests for QuickSort
    • SlectionSortTests.cs contains unit tests for SelectionSort
    • SimpleSortTests.cs contains unit tests for SimpleSort

    Compiling and running the unit tests will show that all the sorting algorithms do in in fact sort the data.

    ##Step 2 Generating Factories In the solution root folder, create a folder named "Libraries", and add the Traceability.dll and TraceCodeGenerator.dll into that folder.

    Added: Console Application Project "CodeGeneration"

    CodeGeneration Dependencies:

    • Sorting project
    • TraceCodeGenerator.dll

    In the code we are calling GenerateFactory() for each file in Sorting/Algorithms.

    Note: QuickInsertSortFactory was manually created. This is due to the fact that it contains an InsertSortFactory.

    Program.cs.

    using Sorting.Algorithms;
    using System;
    using TraceCodeGenerator;
    
    namespace CodeGeneration
    {
        class Program
        {
            static void Main(string[] args)
            {
                GenerateFactory(typeof(DataSet));
                GenerateFactory(typeof(SimpleSort));
                GenerateFactory(typeof(SelectionSort));
                GenerateFactory(typeof(InsertSort));
                GenerateFactory(typeof(QuickSort));
                //QuickInsertSort Factory was manually generated
                GenerateFactory(typeof(HeapSort));
    
                Console.WriteLine("Done");
            }
    
            private static void GenerateFactory(Type type)
            {
                var factory = new FactoryGenerator(type, "Sorting.Factories");
                WriteFile(factory);
            }
    
            private static void WriteFile(IClassGenerator classGen)
            {
                Console.WriteLine(_fileWriter.CreateFile(classGen));
            }
    
            private static FileWriter _fileWriter = new FileWriter();
        }
    }
    

    Compile all projects, and run the console app. The output in the console window will be the file paths wach file is written to. In the Sorting Project, wou will see a new folder named "Factories" appear. Inside of it, will be all the new factory classes.

    Sorting/Factories

    Each factory class contains one or more Construct() methods that generate the appropriate class.

    ##Step 3 Using Factories in Unit Tests We are making a significant change to the unit tests in this phase. Instead of simply creating a new object in each test, we are using the factories. When we look at the code for SimpleSortTests, there appears to be a bit of madness with the constructor and static variables. However, in this madness there is method.

    public class SimpleSortTests
    {
        private static readonly DataGenerator _staticDataGenerator = new DataGenerator();
        private static readonly SimpleSortFactory _staticSimpleSortFactory = new SimpleSortFactory();
    
        private readonly DataGenerator _dataGenerator;
        private readonly SimpleSortFactory _simpleSortFactory;
    
        public SimpleSortTests(SimpleSortFactory simpleSortFactory = null, DataGenerator dataGenerator = null)
        {
            _simpleSortFactory = simpleSortFactory ?? _staticSimpleSortFactory;
            _dataGenerator = dataGenerator ?? _staticDataGenerator;
        }
    ...
    

    There are four things we need to note here:

    1) We have static variables to hold the factories we need for the unit tests. This is ok, since these factories do not retain any state.

    2) We have two non-static instance variables that also hold the factories. It is these variables that the unit tests will use to create what they need.

    3) We have a constructor for the unit test. The constructor takes the static factories, and places them into the instance variables.

    4) The constructor allows us to inject other factories and use those instead. This will become very important later on.

    Side note: in xUnit, the constructor is called for every unit test that is ran in the file. This is why we have the static variables holding the default factories. That way we don't need to keep recreating the factories. It also important to note that the factories don't have any state, thus reusing them is ok.

    Compile and run the tests, and we see they all pass.

    If you set a breakpoint in one of the test constructors, and then select debug on the file, you will see that you hit the breakpoint for every unit test.

    ##Step 4 Generating Trace Files

    Added: Class Library Project "TraceSorting"

    TraceSorting Dependencies:

    • Sorting Project
    • Traceability.dll

    This new project is going to contain all of the trace code versions of the classes in the Sorting Project. The folder hierarchy will match as well.

    Now we are adding calls to GenerateTracer() for each class in Sorting/Algorithms.

    We have renamed GenerateFactory() to GenerateClasses() since the function is now generating more than factories. Furthermore, since FactoryGenerator and TraceClassGenerator take the same type, we can save some code by reusing the type information.

    The only caveat is that we don't want to generate the QuickInsertSortFactory since it was manually created. Thus, we don't pass typeof(QuickInsertSort) into GenerateClasses(). Instead we explicitly use TraceClassGenerator() and have a comment why QuickInsertSort is separate from the other classes.

    Program.cs

    using Sorting.Algorithms;
    using System;
    using TraceCodeGenerator;
    
    namespace CodeGeneration
    {
        class Program
        {
            static void Main(string[] args)
            {
                GenerateClasses(typeof(DataSet));
                GenerateClasses(typeof(SimpleSort));
                GenerateClasses(typeof(SelectionSort));
                GenerateClasses(typeof(InsertSort));
                GenerateClasses(typeof(QuickSort));
                GenerateClasses(typeof(HeapSort));
    
                //QuickInsertSortFactory was manually generated
                var trace = new TraceClassGenerator(typeof(QuickInsertSort), "TraceSorting.Algorithms");
                WriteFile(trace);
    
                Console.WriteLine("Done");
            }
    
            private static void GenerateClasses(Type type)
            {
                var factory = new FactoryGenerator(type, "Sorting.Factories");
                WriteFile(factory);
    
                var trace = new TraceClassGenerator(type, "TraceSorting.Algorithms");
                WriteFile(trace);
            }
    
            private static void WriteFile(IClassGenerator classGen)
            {
                Console.WriteLine(_fileWriter.CreateFile(classGen));
            }
    
            private static FileWriter _fileWriter = new FileWriter();
        }
    }
    

    Compile and run CodeGeneration. A new folder named Algorithms will appear in the TraceSorting project. Inside of the folder, there will be a trace version of the DataSet and each sorter.

    We don't need to make a trace version of the ISorter for two reasons: 1) it's an interface, there is nothing to trace; and 2) all of the trace sorters implement ISorter anyway.

    ##Step 5 Generating Trace Factories

    CodeGeneration New Dependency

    • TraceSorting project

    Now we are going to add the final set of code generation lines to Program.cs. However, there are a couple of special notes we should make. When we created the factories and trace files, we also created two new namespaces. These namespaces may not exist when we try to create the Trace Factories. Another aspect is that each Trace Factory is dependent upon the Factory and Trace Class we created at an earlier step. So, now we are in a situation where we may be trying to generate classes that have missing dependencies.

    If we look at the GenerateClasses() function in the code below, we will notice that the TraceFactoryGenerator doesn't take type arguments, but instead uses the prior generators. This allows it to gather the qualified names for the types generated by the prior generators. If either of them are missing, it will respond with an error message instead of the file name it wrote to.

    Program.cs

    using Sorting.Algorithms;
    using System;
    using TraceCodeGenerator;
    
    namespace CodeGeneration
    {
        class Program
        {
            static void Main(string[] args)
            {
                GenerateClasses(typeof(DataSet));
                GenerateClasses(typeof(SimpleSort));
                GenerateClasses(typeof(SelectionSort));
                GenerateClasses(typeof(InsertSort));
                GenerateClasses(typeof(QuickSort));
                GenerateClasses(typeof(HeapSort));
    
                //QuickInsertSortFactory was manually generated
                var trace = new TraceClassGenerator(typeof(QuickInsertSort), "TraceSorting.Algorithms");
                WriteFile(trace);
                // TraceQuickInsertSortFactory was manually generated
    
                Console.WriteLine("Done");
            }
    
            private static void GenerateClasses(Type type)
            {
                var factory = new FactoryGenerator(type, "Sorting.Factories");
                WriteFile(factory);
    
                var trace = new TraceClassGenerator(type, "TraceSorting.Algorithms");
                WriteFile(trace);
    
                var traceFactory = new TraceFactoryGenerator(factory, trace, "TraceSorting.Factories");
                WriteFile(traceFactory);
            }
    
            private static void WriteFile(IClassGenerator classGen)
            {
                Console.WriteLine(_fileWriter.CreateFile(classGen));
            }
    
            private static FileWriter _fileWriter = new FileWriter();
        }
    }
    

    Compile and run the CodeGeneration project.

    If we started this process fresh, and only had the base classes, we would see an output like this:

    .../TraceabilityTutorial/Sorting/Factories/DataSetFactory.cs
    .../TraceabilityTutorial/TraceSorting/Algorithms/TraceDataSet.cs
    Could not locate valid type with: 'Sorting.Factories.DataSetFactory,Sorting'.
    .../TraceabilityTutorial/Sorting/Factories/SimpleSortFactory.cs
    .../TraceabilityTutorial/TraceSorting/Algorithms/TraceSimpleSort.cs
    Could not locate valid type with: 'Sorting.Factories.SimpleSortFactory,Sorting'.
    .../TraceabilityTutorial/Sorting/Factories/SelectionSortFactory.cs
    .../TraceabilityTutorial/TraceSorting/Algorithms/TraceSelectionSort.cs
    Could not locate valid type with: 'Sorting.Factories.SelectionSortFactory,Sorting'.
    .../TraceabilityTutorial/Sorting/Factories/InsertSortFactory.cs
    .../TraceabilityTutorial/TraceSorting/Algorithms/TraceInsertSort.cs
    Could not locate valid type with: 'Sorting.Factories.InsertSortFactory,Sorting'.
    .../TraceabilityTutorial/Sorting/Factories/QuickSortFactory.cs
    .../TraceabilityTutorial/TraceSorting/Algorithms/TraceQuickSort.cs
    Could not locate valid type with: 'Sorting.Factories.QuickSortFactory,Sorting'.
    .../TraceabilityTutorial/Sorting/Factories/HeapSortFactory.cs
    .../TraceabilityTutorial/TraceSorting/Algorithms/TraceHeapSort.cs
    Could not locate valid type with: 'Sorting.Factories.HeapSortFactory,Sorting'.
    .../TraceabilityTutorial/TraceSorting/Algorithms/TraceQuickInsertSort.cs
    

    What we are seeing is the Factory being created, then the Trace Class being created, but each Trace Factory failing. The issue now is that, even the two prior classes exist, they are not compiled. Compiling and running the program a second time will show everything being written to files.

    A folder named Factories will now appear in the TraceSorting project. If we look at the files, we will find factories that create trace versions of the DataSet and sorting classes. Note that while each factory has a tracer inside of it, it does not have any tracing code. Instead, they create trace classes which do have trace code in them.

    We can now use the trace factories in place of the regular factories. This allows us to switch between the functional version of a class and the the trace version as needed. In fact, in the next step, we are going to use this fact with the unit tests.

    ##Step 6 Trace Regression Testing

    Added: xUnit Test Project TraceSortingTests

    Dependencies:

    • SortingTests project
    • TraceSorting project
    • Traceability.dll

    An important aspact of traceability is that it does not change the functionality of the code even while extracting the trace data. The best way to ensure this is to use all of the unit tests ran against the functional class are ran against the corresponding trace class as well.

    And, as it turns out, this situation is easily solved with dependency injection. Remember how in Step 3 we changed the unit tests to use factories? This is the reason why.

    Now we need to set up the unit tests. Here is the code for the TraceSimpleSortTests

    using SortingTests;
    using Traceability;
    using TraceSorting.Factories;
    
    namespace TraceSortingTests
    {
        public class TraceSimpleSortTests : SimpleSortTests
        {
            private static readonly FakeTracer _tracer = new FakeTracer();
            private static readonly DataGenerator _dataGenerator = new DataGenerator(new TraceDataSetFactory(_tracer));
            private static readonly TraceSimpleSortFactory _sortFactory = new TraceSimpleSortFactory(_tracer);
    
            public TraceSimpleSortTests()
            : base(_sortFactory, _dataGenerator)
            { }
    }
    

    That's it, that is the entire unit test class!

    We are using the FakeTracer since we don't need to actually trace anything. We are creating static instances of the trace factories, and then using the constructor to inject these trace factories into the already existing unit tests.

    Now, whenever a new test is added to the unit tests in the functional layer, the trace unit tests will automatically get them. We don't have to worry about updating the trace unit tests ever again.

    Compile the code and run the unit tests. You will find that the number of unit tests have doubled.

    Experiment: set a breakpoint in any of the functional unit tests. Run the corresponding trace unit test. Check the types of the factories that are appearing in the unit test. You will see that they are the trace factories.

    ##Step 7 Trace Verify Tests

    Dependency added to TraceSortingTests

    • TraceCodeGenerator.dll

    The final step is adding the Trace Verify Tests to the TraceSortingProject.

    The purpose of these tests is to verify that each trace file matches the corresponding functional class. This way, if the functional class is updated, then these unit tests will tell us if we need to run the code generator again to update the a trace file.

    The static method Verify.VerifyTraceInheritence() returns a results class that lists any discrepancies. If there are no discrepancies, then the Errors list will be empty.

    TraceVerifyTests.cs

    Example Verify Test:

    [Fact]
    public void Verify_Set()
    {
        var results = Verify.VerifyTraceInheritence(typeof(DataSet), typeof(TraceDataSet));
        Assert.Empty(results.Errors);
    }
    

    ##What's Next After reading through this tutorial, you can look at the SortingV2 example code.

    SortingV2 uses the Sorting Library to implement a simulated service. An important part of the service is being able to switch between standard code, and four different trace modes.

    SortingV2

    SystemAdmin controls the trace mode settings.

    TraceModes contains the various trace modes available to the SortingV2 example.

    Updated