Author Topic: CP2654: Automated Testing with the AutoCAD® .NET API  (Read 8941 times)

0 Members and 1 Guest are viewing this topic.

Andrey Bushman

  • Swamp Rat
  • Posts: 864
CP2654: Automated Testing with the AutoCAD® .NET API
« on: April 03, 2014, 08:10:13 AM »
In this topic I place gradually my some comments (for discussion) about the article pointed in the title of this theme. I am grateful to Scott McFarlane for his article - it is very useful info!

Quote
. This is a simple AutoCAD program that changes the color of all the circles in the drawing to a specific color based on the circle radius. “Small” (r < 1.0) circles are changed to yellow, “medium” (1.0 <= r <= 10.0) circles are changed to green, and “large” (r > 10.0) circles are changed to red.
Code - C#: [Select]
  1. publicvoidChangeCircleColors()
  2. {
  3.         var document = Application.DocumentManager.MdiActiveDocument;
  4.         var database = document.Database;
  5.  
  6.         using (vartr = database.TransactionManager.StartTransaction())
  7.         {
  8.                 try
  9.                 {
  10.                         RXClasscircleClass = RXObject.GetClass(typeof(Circle));
  11.  
  12.                         varblockTable = (BlockTable) tr
  13.                                 .GetObject(database.BlockTableId, OpenMode.ForRead);
  14.  
  15.                         // Get the block table record
  16.                         vartableRecord = (BlockTableRecord) tr
  17.                                 .GetObject(blockTable[BlockTableRecord.ModelSpace],OpenMode.ForRead);
  18.  
  19.                         foreach (varobjectIdintableRecord)
  20.                         {
  21.                                 if (objectId.ObjectClass.IsDerivedFrom(circleClass))
  22.                                 {
  23.                                         var circle = (Circle) tr.GetObject(objectId, OpenMode.ForWrite);
  24.  
  25.                                         if (circle.Radius< 1.0)
  26.                                         {
  27.                                                 circle.ColorIndex = 2;
  28.                                         }
  29.                                         elseif (circle.Radius> 10.0)
  30.                                         {
  31.                                                 circle.ColorIndex = 1;
  32.                                         }
  33.                                         else
  34.                                         {
  35.                                                 circle.ColorIndex = 3;
  36.                                         }
  37.                                 }
  38.                         }
  39.                         tr.Commit();
  40.                 }
  41.                 catch (Exception ex)
  42.                 {
  43.                         tr.Abort();
  44.                         document.Editor.WriteMessage(ex.ToString());
  45.                 }
  46.         }
  47. }
The majority of this code is “boiler plate” code that you see in any program that manipulates AutoCAD entities in model space. There is really no need to “test” that code. The part of the code we really want to test is the logic that sets the circle color based on the radius.
About the text are marked by the red color: I don't agree. For example, the Document can be null (this check is not exist in the code); also if it is a command's code the command can be marked as a "Session" (i.e. the Document must be locked), as I think. Tell me if I am not right.
« Last Edit: April 03, 2014, 08:17:26 AM by Andrey Bushman »

Locke

  • Guest
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #1 on: April 03, 2014, 09:26:33 AM »
Interesting, thanks for sharing.  I agree with your assessment so far, it has a few issues.  Not sure what vomited all over his foreach declaration, looks like it was flubbed copying it up.  Moreover, several of the 'var' statements are pressed against their assigning variables, weird.

Is the .IsDerivedFrom technique quicker than 'Entity as EntityInheritedClass' with a null check following?

Edit:  Hypothesizing on my own question, if we can skip needing to open the objectid then it should be a performance gain.  I'll have to adopt this technique if it proves fruitful.
« Last Edit: April 03, 2014, 09:33:07 AM by Locke »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #2 on: April 03, 2014, 09:34:23 AM »
I copied the code from the Word document (the article). The are many keywords was spliсed in it (It is Word's formatting problem). But it is not problem for the code's understanding.
« Last Edit: April 03, 2014, 09:38:46 AM by Andrey Bushman »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #3 on: April 03, 2014, 09:38:19 AM »
Is the .IsDerivedFrom technique quicker than 'Entity as EntityInheritedClass' with a null check following?

Edit:  Hypothesizing on my own question, if we can skip needing to open the objectid then it should be a performance gain.  I'll have to adopt this technique if it proves fruitful.
In this case the attention to the testing. The remaining moments are secondary.

Locke

  • Guest
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #4 on: April 03, 2014, 09:57:09 AM »
Update:  Using the DerivedFrom method (new to me, probably not new to anyone here) I cut down processing time in half with 150,000 circles.

Here's my suggested cleanup for the original author:

Code - C#: [Select]
  1. public static void ChangeCircleColors()
  2. {
  3.     var document = Application.DocumentManager.MdiActiveDocument;
  4.     var database = document.Database;
  5.  
  6.     using (var tr = database.TransactionManager.StartOpenCloseTransaction())
  7.     {
  8.         try
  9.         {
  10.             var circleClass = RXObject.GetClass(typeof(Circle));
  11.             var modelSpaceId = SymbolUtilityServices.GetBlockModelSpaceId(database);
  12.             var modelSpace= (BlockTableRecord)tr.GetObject(modelSpaceId, OpenMode.ForRead);
  13.  
  14.             foreach (ObjectId id in modelSpace)
  15.             {
  16.                 if (!id.ObjectClass.IsDerivedFrom(circleClass))
  17.                     continue;
  18.  
  19.                 var circle = (Circle)tr.GetObject(id, OpenMode.ForWrite);
  20.                 circle.ColorIndex = circle.Radius < 1.0 ? 2
  21.                                   : circle.Radius > 10.0 ? 1
  22.                                   : 3;
  23.             }
  24.             tr.Commit();
  25.         }
  26.         catch (Autodesk.AutoCAD.Runtime.Exception ex)
  27.         {
  28.             tr.Abort();
  29.             document.Editor.WriteMessage(ex.Message);
  30.         }
  31.     }
  32. }
  33.  

Doesn't address the issues you mentioned regarding the document and database being null.  An exception could end up ultra dying too since he's trying to write to an editor that may not exist.
« Last Edit: April 03, 2014, 10:10:01 AM by Locke »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #5 on: April 03, 2014, 10:05:36 AM »
Code - C#: [Select]
  1. circle.ColorIndex = circle.Radius < 1.0 ? 2
  2.         : circle.Radius > 10.0 ? 1
  3.         : 3;
In my opinion: this variant is difficult for the reading and therefore can to be subjected for the mistakes.

P.S. I ask not to change the topic's direction

Locke

  • Guest
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #6 on: April 03, 2014, 10:08:39 AM »
Ternary usage is a pretty subjective thing.  If the method was doing anything hearty, it wouldn't be a good fit. 

Secondarily, I apologize for derailing the topic.  I think I probably misinterpreted your original aim for this thread, likely lost in translation.

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #7 on: April 03, 2014, 10:37:32 AM »
Quote
Set up a Batch File to Launch the Test Runner
Add a file called test.bat to your project. This file will look something like so:
Code: [Select]
SET GALLIO_RUNTIME_PATH=C:\Gallio\bin
"C:\Gallio\bin\Gallio.Echo.exe" TestingExample1GallioTest.dll /r:AutoCAD
pause

Your file may look slightly different depending on where you installed Gallio, and the name of your test DLL. Be sure to set the properties of test.bat such that it gets copied to your output directory.

Run Your Tests
Finally, build your project, navigate to the project output folder, and launch test.bat. You should see a console window open, then AutoCAD will launch. Your tests will run, and then AutoCAD will close.
The output in the Console window should look something like this:



But I have some problems in this case:

1. I use remote debugging only (by MS Visual Studio): all AutoCAD's are located on the other machine, not the same as IDE's.
2. Remote machine has more than one AutoCAD installed: AutoCAD 2009-2015.

Questions:
1. How can I point the target AutoCAD version for the Gallio.Echo.exe?
2. I prefer to install all software for developing at the special developing machine (it contains the MS VS 2005-2013 and SDK's only, without the AutoCADs, BricsCADs, Revits, etc). So I prefer to install the Gallio on this machine, instead of Gallio installing to my test machines which are contain the CAD programs.

The compilation result are located in the shared folder of the remote machine. So this code loading to the AutoCAD without any problems. But now I want to fasten to it the using of Gallio... May be can I copy to output directory and some necessary Gallio's files (Gallio.Echo.exe for example) for the successful testing? Or, nevertheless, I must to install the Gallio on the remote machine?
« Last Edit: April 03, 2014, 10:48:16 AM by Andrey Bushman »

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #8 on: April 03, 2014, 11:00:19 AM »
I see the some info in the Gallio.AutoCAD.plugin file:
Code: [Select]
...
      <traits>
        <name>AutoCAD</name>
        <description>
          Runs tests within an AutoCAD process.

          Supported test runner properties:
          - AcadAttachToExisting: If "true" attaches to an existing AutoCAD process, otherwise starts a new one.
          - AcadExePath: Specifies the full path of the "acad.exe" program.
        </description>
      </traits>
    </component>
...
I will try to copy Gallio into output directory and run it. The monologue is ended.

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #9 on: April 03, 2014, 11:01:21 AM »
Unless I am reading Kean Walmsley's blog incorrectly, prior to release year 2015 the MdiActiveDocument could not be null.  (I could very easily be misunderstanding what he wrote).


Quote
One application-level change brought about by this work, effectively providing support for non-drawing documents, is the fact that MdiActiveDocument can now be "null": it's important to check it has a valid object reference before using it, from AutoCAD 2015 onwards.


Being a extremely novice programmer it could be that i misunderstood this quote but I take this to mean that before 2015 it could not be null so there would be no reason to check for it?  I am reading that correctly?


The entire post by Kean can be read at this link.  http://through-the-interface.typepad.com/through_the_interface/2014/03/autocad-2015-for-developers.html?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+typepad%2Fwalmsleyk%2Fthrough_the_interface+%28Through+the+Interface%29



Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #10 on: April 03, 2014, 11:12:39 AM »
I wanted to tell we need to check more information. For example, Bjarne Stroustrup (the C++ creater) advices to check all the data from the input (the Document in our case) and the result before the return. We need to do it not in the testing only, but our tests must to check it too (i.e. we can forget to do it in the our "work" code).

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #11 on: April 03, 2014, 11:14:43 AM »
Makes sense.  Thanks for the clarification.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

Locke

  • Guest
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #12 on: April 03, 2014, 11:16:45 AM »
Unless I am reading Kean Walmsley's blog incorrectly, prior to release year 2015 the MdiActiveDocument could not be null.

I can get an 'Object reference not set to an instance of an object' to throw if it is in fact null.  I think for traditional approaches (via command method) it's not generally ever going to be the case.

However if you are making calls to AutoCAD through a ServicedComponent interface, it can definitely be null.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #13 on: April 03, 2014, 11:16:53 AM »
Take a look at
http://www.theswamp.org/index.php?topic=42197.msg474011#msg474011
http://www.theswamp.org/index.php?topic=41311.msg464457#msg464457


Sort of more BCLish and maybe more reusable

Code - C#: [Select]
  1.         [CommandMethod("ChangeCircles")]
  2.         public void ChangeCircles()
  3.         {
  4.             using (Transaction trx = Db.TransactionManager.StartTransaction())
  5.             {
  6.                 BlockTableRecord model = Db.ModelSpace();
  7.                
  8.                 foreach (var circle  in model.GetEntities<Circle>(OpenMode.ForWrite))
  9.                 {
  10.                     if (circle.Radius < 1.0)
  11.                     {
  12.                         circle.ColorIndex = 2;
  13.                     }
  14.                     else
  15.                     {
  16.                         circle.ColorIndex = circle.Radius > 10.0 ? 1 : 3;
  17.                     }
  18.                 }
  19.                 trx.Commit();
  20.             }
  21.         }
  22.  
  23.  
  24.         [CommandMethod("Changecurves")]
  25.         public void Changecurves()
  26.         {
  27.             using (Transaction trx = Db.TransactionManager.StartTransaction())
  28.             {
  29.                 BlockTableRecord model = Db.ModelSpace();
  30.  
  31.                 foreach (var curve in model.GetEntitiesAssignableFrom<Curve>(OpenMode.ForWrite))
  32.                 {
  33.  
  34.                         curve.ColorIndex = curve.Closed ? 4 : 5;
  35.                  
  36.                 }
  37.                 trx.Commit();
  38.             }
  39.         }
  40.  

Code - C#: [Select]
  1.         public static IEnumerable<T> GetEntities<T>(this BlockTableRecord btr, Transaction trx, OpenMode mode = OpenMode.ForRead, bool includingErased = false, bool openObjectsOnLockedLayers = false) where T : Entity
  2.         {
  3.             if (trx == null)
  4.             {
  5.                 throw new NoActiveTransactionException("No active Transaction");
  6.             }
  7.  
  8.             var impObject = RXObject.GetClass(typeof(T)).UnmanagedObject;
  9.  
  10.  
  11.             foreach (ObjectId id in includingErased ? btr.IncludingErased : btr)
  12.             {
  13.                 if(id.ObjectClass.UnmanagedObject == impObject)
  14.                 {
  15.                     yield return (T)trx.GetObject(id, mode, includingErased, openObjectsOnLockedLayers);
  16.                 }
  17.  
  18.             }
  19.         }
  20.  
  21.         public static IEnumerable<T> GetEntities<T>(this BlockTableRecord btr, OpenMode mode = OpenMode.ForRead, bool includingErased = false, bool openObjectsOnLockedLayers = false) where T : Entity
  22.         {
  23.             return btr.GetEntities<T>(btr.Database.TransactionManager.TopTransaction, mode, includingErased, openObjectsOnLockedLayers);
  24.         }
  25.  
  26.  


Code - C#: [Select]
  1.         public static IEnumerable<T> GetEntitiesAssignableFrom<T>(this BlockTableRecord btr, Transaction trx, OpenMode mode = OpenMode.ForRead, bool includingErased = false, bool openObjectsOnLockedLayers = false) where T : Entity
  2.         {
  3.             if (trx == null)
  4.             {
  5.                 throw new NoActiveTransactionException("No active Transaction");
  6.             }
  7.  
  8.             RXClass rxclass = RXClass.GetClass(typeof(T));
  9.  
  10.  
  11.             foreach (ObjectId id in includingErased ? btr.IncludingErased : btr)
  12.             {
  13.                 if (id.ObjectClass.IsDerivedFrom(rxclass))
  14.                 {
  15.                     yield return (T)trx.GetObject(id, mode, includingErased, openObjectsOnLockedLayers);
  16.                 }
  17.  
  18.             }
  19.  
  20.         }
  21.  
  22.         public static IEnumerable<T> GetEntitiesAssignableFrom<T>(this BlockTableRecord btr, OpenMode mode = OpenMode.ForRead, bool includingErased = false, bool openObjectsOnLockedLayers = false) where T : Entity
  23.         {
  24.             return btr.GetEntitiesAssignableFrom<T>(btr.Database.TransactionManager.TopTransaction, mode, includingErased, openObjectsOnLockedLayers);
  25.  
  26.         }
  27.  

Code - C#: [Select]
  1.         /// <summary>
  2.         /// Author: Tony Tanzillo
  3.         /// Source: http://www.theswamp.org/index.php?topic=41311.msg464457#msg464457
  4.         /// </summary>
  5.         /// <param name="db"></param>
  6.         /// <param name="trx"></param>
  7.         /// <param name="mode"></param>
  8.         /// <returns></returns>
  9.        public static BlockTableRecord ModelSpace(this Database db, Transaction trx, OpenMode mode = OpenMode.ForRead)
  10.         {
  11.             if (trx == null)
  12.             {
  13.                 throw new NoActiveTransactionException("No active Transaction");
  14.             }
  15.             return (BlockTableRecord)trx.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db),mode, false, false);
  16.         }
  17.  
  18.  
  19.         public static BlockTableRecord ModelSpace(this Database db, OpenMode mode = OpenMode.ForRead)
  20.         {
  21.             return db.ModelSpace(db.TransactionManager.TopTransaction, mode);
  22.         }
  23.  

AIYOUNG

  • Guest
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #14 on: April 04, 2014, 12:19:49 PM »
The bits about using Gallio to do automated integration tests in AutoCAD was pretty interesting.

However it looks like Gallio isn't currently maintained and it's last build was in July 2012.

Does anyone know of an alternative test automation platform that could be used with AutoCAD and is better maintained?

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #15 on: April 04, 2014, 12:26:28 PM »
The bits about using Gallio to do automated integration tests in AutoCAD was pretty interesting.

However it looks like Gallio isn't currently maintained and it's last build was in July 2012.

Does anyone know of an alternative test automation platform that could be used with AutoCAD and is better maintained?
I read the code sources for the Gallio's AutoCAD addon and I see the bugs. I study the source code and I want to make other implementation of of this addon, which was more convenient and not dependent from specific CAD. For example it must to work with other CADs: nanoCAD, BricsCAD, etc. Then I want to try to make such for NUnit.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #16 on: April 04, 2014, 12:45:22 PM »
I just don't see it happening or just too much work for creating a stable testing environment. Autodesk would have to create it I think because the amount of resources and working internally to have input on future changes to help and modify or prevent other changes that would wreak havoc on existing environment.

Andrey Bushman

  • Swamp Rat
  • Posts: 864
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #17 on: April 04, 2014, 12:48:16 PM »
It is necessary to hope to Autodesk in the latest case only. It is very faint and unreliable hope.

Jeff H

  • Needs a day job
  • Posts: 6150
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #18 on: April 04, 2014, 01:06:52 PM »
I guess anything is possible but I would not hold any amount hope they would create one.

If you enjoyed the classes by Scott I could be wrong but looks like he definitely read this book
http://www.amazon.com/Professional-Test-Driven-Development-Applications-ebook/dp/B004X75OGG/ref=sr_1_2?ie=UTF8&qid=1396630097&sr=8-2&keywords=test+driven+development

DavidS

  • Mosquito
  • Posts: 15
Re: CP2654: Automated Testing with the AutoCAD® .NET API
« Reply #19 on: August 01, 2018, 09:17:31 AM »
I know that this is an old topic but I wanted to update the Autodesk University reference with the latest link for Scott McFarlane's class.  This class has a bit more about testing in AutoCAD as of the 2015 AU "SD12077: Being a Remarkable C# .NET AutoCAD Developer" class.

http://au.autodesk.com/au-online/classes-on-demand/class-catalog/classes/year-2015/autocad/sd12077