5,696,576 members and growing! (19,047 online)
Email Password   helpLost your password?
Languages » VB.NET » HowTo     Intermediate License: The Code Project Open License (CPOL)

How to use Reflection to test your code

By Andy Davies

Demonstrates how to use Reflection to run test functions inside your compiles assemblies
VB, Windows, Architect, Dev, QA, Design

Posted: 20 Jan 2008
Updated: 20 Jan 2008
Views: 7,848
Bookmarked: 5 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
3 votes for this Article.
Popularity: 1.43 Rating: 3.00 out of 5
1 vote, 33.3%
1
0 votes, 0.0%
2
1 vote, 33.3%
3
0 votes, 0.0%
4
1 vote, 33.3%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Contents

  • Contents
  • Introduction
  • The Setup
  • How it works
  • Notes & Observations
  • Conclusion
  • Revision History
  • Introduction

    If, like me, you have quite a large code base that many of your projects use, there is always the risk that if you change something in your base classes for one dependent project, it may break another dependent project. This was a problem for us at work, where all our projects use a library for data access, which can get changed quite often, and as a result could break many projects. To combat this I was assigned the task of writing a test function in each class, which tested all the public methods of the class. I then wrote an application that could automatically run all the tests. So, this is an article on how to use Reflection, as I couldn?t find anything to really help me achieve this.

    The Setup

    The project is split into three parts: an interface, the item(s) to be tested, and the test application.

    The Interface

    The interface is a single class library, with two parts, the interface itself, and a public enum, which I use to give the test a result type. Entire Testbench.vb code listing:
    Public Interface TestBench
    
        Function Test(ByVal log As List(Of String)) As TestResult
    
    End Interface
    
    Public Enum TestResult
        Fail = 0
        Fail_Data_Error
        Fail_Exception
        Fail_Timeout
    
        Pass = 20
        Pass_No_Test_Needed
    End Enum
    The Interface has only one function in it, a Test function with a list of strings to provide feedback with, and a result type to send back at the end of the test. The other item in the file is a padded enum. The enum is padded to allow more pass and fail states to be added at a later date. Anything less than 20 is a fail, and anything greater than or equal to 20 is a pass.

    The Test Item

    The Test items are also simple to write. All that is required is a reference in the project to TestBench.dll, and for your classes to implement the interface. In the example I provide 3 classes to show what you can do. The code listing for one of the items to be tested:
    Public Class PassingItem
        Implements TestBench.TestBench
    
        Public Function Test(ByVal log As System.Collections.Generic.List(Of String)) _
        As TestBench.TestResult Implements TestBench.TestBench.Test
            log.Add("This is a test, one that passes")
            Return TestBench.TestResult.Pass
        End Function
    End Class
    As you can see, all I have done is add the interface and hit enter, which automatically puts the function into your class. I then added a single entry to the log, and return the desired result type. If you have the function in the class, but without the implements statement, the test app will not pick it up, as it looks for a class with the interface in it.

    The Testing Application

    The test application for the purposes of this article is simple; it contains one listbox, and one button. The listbox shows us the log of the tests, and the button starts the test. At the core of the test function is the following code:
    Dim assembly As Reflection.Assembly = Reflection.Assembly.LoadFrom(path)
    
    lst.Items.Add(String.Format("Assembly {0} Loaded", assembly.FullName))
    
    For Each t As Type In assembly.GetTypes
        If t.IsClass AndAlso t.GetInterface("TestBench") IsNot Nothing Then
            lst.Items.Add(String.Format("---Starting test for {0}", t.Name))
    
            _log.Clear()                                                            
    
            Dim r As TestBench.TestResult = DirectCast(Activator.CreateInstance(t), _
            TestBench.TestBench).Test(_log)
    
            lst.Items.AddRange(_log.ToArray)                                        
    
            lst.Items.Add(String.Format("---Finished Testing {0}, with result: {1}", _
            t.Name, r.ToString))
    
        End If
    Next 
    Let's go through it, and see what does what.

    How it works

     Dim assembly As Reflection.Assembly = Reflection.Assembly.LoadFrom(path)
    This line creates a new Assembly, which is then set to the contents of the selected DLL. There is a slight problem with this however; it keeps the DLL locked until your application quits. While in some situations this may be no problem, this can be annoying if you are constantly recompiling the target dll, as VS will detect that the DLL is locked, and won?t write the output file. The way in which I solved this was suggested to me by John H. of the VB Dot Net Forums. His suggestion was to use ReadAllBytes() function in the System.IO.File namespace. This allows you to load the DLL in without locking it. The replacement of the line above is:
    Dim assembly As Reflection.Assembly = Nothing
    assembly = Reflection.Assembly.Load(System.IO.File.ReadAllBytes(path))
    Next is the loop:
    For Each t As Type In assembly.GetTypes
        If t.IsClass AndAlso t.GetInterface("TestBench") IsNot Nothing Then
            '...
        End If
    Next 
    This loops through all of the Types in the assembly. There are a lot of these included which we have no interest in, such as TestITem.My.MyProject+MyWebServices and TestITem.My.Resources.Resources, so we need to check that the Type loaded is a Class and that it implements our interface. To do this we use t.GetInterface() and check if it is not nothing, if it is nothing, the interface is not present, and if it is not null, we have a testable class. The next part is the most important part of the testing process, creating an instance of the class, and running the Test() function. First off, we create a variable to store the test result in.
    Dim r As TestBench.TestResult = Nothing
    We then use the Activator.CreateInstance() function to create an instance of our class, and use DirectCast to cast it to the interface, which allows us to execute anything contained in the interface, in this case, the Test() function.
    r = DirectCast(Activator.CreateInstance(t), TestBench.TestBench).Test(_log)

    Notes & Observations

    There is no error handling in this code, I know. This is to make the code very easy to read and understand, and what levels of error protection you wish to put in are up to you. Good places to start are Timeout and Null Reference exceptions in the actual test itself, and have the exception messages go to the log. You must have a blank constructor for your class to be tested. By default if you have not specified a Public Sub Main() of any sort in the class, it will work, but if you have a constructor with parameters , you must also have a blank one, even if it is only there for use with your Test Bench.

    Conclusion

    I hope this article was of some use to you, and that you can extend the basic methods mentioned here to create some automated testing for your code. Code based round this method has been very useful at work, and helps spot errors in code and new Database Constraints of which the code may be in violation.

    Revision History

    20th January 2008 - Article Released

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    About the Author

    Andy Davies


    Andy is a software developer for a Recruitment Agency in the UK. He specializes in GUI Design, XML, GDI+, Controls & anything else that takes his fancy.

    When not coding, he is throwing people around in Judo or reading. Or driving to/from work.

    He hopes that his articles might be of help to someone. He likes receiving feedback for his articles (Hint Hint)
    Occupation: Software Developer
    Location: United Kingdom United Kingdom

    Other popular VB.NET articles:

    Article Top
    Sign Up to vote for this article
    You must Sign In to use this message board.
    FAQ FAQ Noise ToleranceSearch Search Messages 
     Layout  Per page   
     Msgs 1 to 3 of 3 (Total in Forum: 3) (Refresh)FirstPrevNext
    GeneralWhy not NUnit?memberMike Lang4:53 21 Jan '08  
    AnswerRe: Why not NUnit?memberAndy Davies8:26 21 Jan '08  
    GeneralRe: Why not NUnit?memberMike Lang9:04 21 Jan '08  

    General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    PermaLink | Privacy | Terms of Use
    Last Updated: 20 Jan 2008
    Editor:
    Copyright 2008 by Andy Davies
    Everything else Copyright © CodeProject, 1999-2008
    Web10 | Advertise on the Code Project