locked
Starting custom code elements (methods) like Unit Tests in C++ RRS feed

  • Question

  • Hi,

    I am not sure how to better describe what I am looking for.

    My project is a kind of unit test suite that needs to start a number of methods in a code in a row, each one after another as a debugged process. So like in a Unit Test there would be several methods in a class and I would like to build the project once, then start the first method, end the process, start the second and so on.

    How would I do this?

    Example:

    [CustomTest]
    public void start1() {}
    
    [CustomTest]
    public void start2() {}
    
    [CustomTest]
    public void start3() {}

    I would like to start the class this code is in and, possibly after starting a SetUp method, start the first method and then end execution, then start the class and go to start2() and so on.

    Any help would be appreciated. Thanks!


    Tuesday, November 13, 2012 10:18 AM

Answers

  • Hi,

    Your question is more related to Reflection than to Visual Studio Extensibility but anyway:

    - First, if your project is a Class Library you need an additional project (Windows Forms or some executable project kind) in your solution.

    - Second, if you have:

        public class CustomTest: System.Attribute
        {
        }

        public class Class1
        {
            [CustomTest]
            public void start1()
            {
                System.Windows.Forms.MessageBox.Show("start1");
            }

            [CustomTest]
            public void start2()
            {
                System.Windows.Forms.MessageBox.Show("start2");
            }

            [CustomTest]
            public void start3()
            {
                System.Windows.Forms.MessageBox.Show("start3");
            }
        }

    then you can use this code to get the types of your assembly, get their methods, check which ones have the CustomTest attribute, and then create an instance of the type and execute the method:

           private void Form1_Load(object sender, EventArgs e)
            {
                RunMethods(System.Reflection.Assembly.GetExecutingAssembly());
            }

            private void RunMethods(System.Reflection.Assembly assembly)
            {
                foreach (System.Type type in assembly.GetTypes())
                {
                    foreach (System.Reflection.MethodInfo methodInfo in type.GetMethods())
                    {
                        foreach (System.Attribute attribute in methodInfo.GetCustomAttributes(false))
                        {
                            if (attribute is CustomTest)
                            {
                                
                                object instance =  System.Activator.CreateInstance(type);
                                methodInfo.Invoke(instance, null);

                                break;
                            }
                        }
                    }
                }
            }

    There is a lot of additional things to take into account (exception handling, static methods, etc.) but that is the idea.


    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/

    • Proposed as answer by Carlos J. Quintero Tuesday, November 13, 2012 10:51 AM
    • Marked as answer by Ego Jiang Thursday, November 15, 2012 5:39 AM
    Tuesday, November 13, 2012 10:51 AM
  • There could be two potential problems here:

    1) The output assembly is locked by the extension, preventing recompilation. It seems that you are not suffering this.

    2) The assembly is loaded in the AppDomain of the extension and once an assembly is loaded in an AppDomain it can't be unloaded, you have to destroy the AppDomain. Apparently when your extension tries to load the assembly for the second time, it thinks that it's the same already loaded in the AppDomain and it is not loaded.

    The workaround would be to trick the extension to think that it is a different assembly so it is loaded. Which code (Assembly.Load) does your extension use to load the output assembly of the project?

    Another workaround would be to create an AppDomain to load the assembly on each execution and destroy it afterwards, but I haven't tried this route.

    See:

    Best Practices for Assembly Loading

    http://msdn.microsoft.com/en-us/library/dd153782.aspx


    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/



    • Edited by Carlos J. Quintero Wednesday, November 14, 2012 10:10 AM fix
    • Marked as answer by Ego Jiang Thursday, November 15, 2012 5:39 AM
    Wednesday, November 14, 2012 10:06 AM

All replies

  • Hi,

    Your question is more related to Reflection than to Visual Studio Extensibility but anyway:

    - First, if your project is a Class Library you need an additional project (Windows Forms or some executable project kind) in your solution.

    - Second, if you have:

        public class CustomTest: System.Attribute
        {
        }

        public class Class1
        {
            [CustomTest]
            public void start1()
            {
                System.Windows.Forms.MessageBox.Show("start1");
            }

            [CustomTest]
            public void start2()
            {
                System.Windows.Forms.MessageBox.Show("start2");
            }

            [CustomTest]
            public void start3()
            {
                System.Windows.Forms.MessageBox.Show("start3");
            }
        }

    then you can use this code to get the types of your assembly, get their methods, check which ones have the CustomTest attribute, and then create an instance of the type and execute the method:

           private void Form1_Load(object sender, EventArgs e)
            {
                RunMethods(System.Reflection.Assembly.GetExecutingAssembly());
            }

            private void RunMethods(System.Reflection.Assembly assembly)
            {
                foreach (System.Type type in assembly.GetTypes())
                {
                    foreach (System.Reflection.MethodInfo methodInfo in type.GetMethods())
                    {
                        foreach (System.Attribute attribute in methodInfo.GetCustomAttributes(false))
                        {
                            if (attribute is CustomTest)
                            {
                                
                                object instance =  System.Activator.CreateInstance(type);
                                methodInfo.Invoke(instance, null);

                                break;
                            }
                        }
                    }
                }
            }

    There is a lot of additional things to take into account (exception handling, static methods, etc.) but that is the idea.


    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/

    • Proposed as answer by Carlos J. Quintero Tuesday, November 13, 2012 10:51 AM
    • Marked as answer by Ego Jiang Thursday, November 15, 2012 5:39 AM
    Tuesday, November 13, 2012 10:51 AM
  • Carlos, thanks so much!

    The code works (mostly), but I have the problem that apparently the assembly doesn't update. In my addin I have a button to load the assembly and run the code you posted. It invokes all the methods with my attributes, but when I change those methods (and rebuild the project) and rerun the code it doesn't reflect the changes, so when I change

    [MyTest]
    public void test1(){
    MessageBox.Show("1);
    }

    to

    [MyTest]
    public void test1(){
    MessageBox.Show("2);
    }
    the message box still says "1". Cleaning or rebuilding the solution doesn't help. I tried using AppDomain and unloading it in the hope that this would cause a reload, but no luck. Only when I restart VS (and thus the AddIn) it reloads the assembly and reflects the code change.

    Tuesday, November 13, 2012 2:36 PM
  • So you have two projects in the solution, one that runs on build and the other that provides the assembly to be loaded at run-time, but that is not referenced at compile-time by the first one, is that right?

    If so:

    - Go to "Tools", "Options" window

    - Go to "Projects and Solutions", "Build and Run" section

    - UNCHECK the "Only build startup projects and dependencies on Run" checkbox

    (so that all projects are rebuilt on each run).



    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/

    Tuesday, November 13, 2012 5:29 PM
  • Not exactly, I am writing an extension for VS. I want to start a Test Suite containing several methods which look like described. So I have a button in VS to start all the methods in the open document, which works. The first time I click the button, the assembly is loaded and the methods are found and executed. Then I change something in the methods, click the button again and apparently the old methods (=old assembly) is started because it's still loaded/cached somewhere. I have to restart VS and click the button to see the changes made in the methods because then the assembly is reloaded.

    That's why I posted in the VSX forum. Sorry, I didn't properly describe that in the first post. It's probably still not an VSX problem, just that the assembly isn't refreshed.

    Tuesday, November 13, 2012 6:37 PM
  • There could be two potential problems here:

    1) The output assembly is locked by the extension, preventing recompilation. It seems that you are not suffering this.

    2) The assembly is loaded in the AppDomain of the extension and once an assembly is loaded in an AppDomain it can't be unloaded, you have to destroy the AppDomain. Apparently when your extension tries to load the assembly for the second time, it thinks that it's the same already loaded in the AppDomain and it is not loaded.

    The workaround would be to trick the extension to think that it is a different assembly so it is loaded. Which code (Assembly.Load) does your extension use to load the output assembly of the project?

    Another workaround would be to create an AppDomain to load the assembly on each execution and destroy it afterwards, but I haven't tried this route.

    See:

    Best Practices for Assembly Loading

    http://msdn.microsoft.com/en-us/library/dd153782.aspx


    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/



    • Edited by Carlos J. Quintero Wednesday, November 14, 2012 10:10 AM fix
    • Marked as answer by Ego Jiang Thursday, November 15, 2012 5:39 AM
    Wednesday, November 14, 2012 10:06 AM
  • Carlos,

    thank you for trying to help (or actually helping). I will try some things and if I can't get it to work I will try another way of starting the code. You can consider this thread closed, thanks.

    Wednesday, November 14, 2012 11:04 AM