Author Topic: TDD for Bricscad examples  (Read 9975 times)

0 Members and 1 Guest are viewing this topic.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
TDD for Bricscad examples
« on: August 03, 2014, 08:26:09 PM »
I thought I'd share my new journey into TDD for Bricscad, of course this applies equally to AutoCAD, I just choose to use Bricscad as that's what I use.

Anyway, if you haven't read Scott Macfarlane's AU doc on testing from 2012/13 you should, it's as good a reference as any to get going.
My aim with doing this example is not just to provide another angle but also proof of concept for myself to see if I'm getting it right and to open up discussion on best practices and ideas.

Anyway, here we go.

First off we know that we are going to have to do a lot more work with the cad platform testing, we will need to use Gallio or similar. The way we are going to construct this example we are going to minimise how much we 'touch' the cad api and by doing this our testing for the cad implementation will be more of an integration testing of boiler plate than unit testing what our app is doing.
It may sound impossible but we can leave this until a lot later, maybe even last but I think we still need to keep in mind how most cad apis work and plan for this in our code.

With that in mind I've created a very simple abstraction of a Drawing class that will later wrap our boiler plate and can be tested in isolation from our application code. The code snippet below is heavily commented so take a look through and it should make sense.

We could have started anywhere with this, we could have started with our application objects but we need to store them in our 'Drawing' so this was as good a place as any to start.

Following the rules of TDD we need to:
- write the test first, run it, it should fail.
- write just enough implementation to make the test pass
- re-factor and run the tests to make sure they still pass
- next test....


Part 1 - Adding an object, test return type.

I've written all the code to make this test pass but if you deleted all the code below the tests you could start again.
To set this up just create a simple class library and use nuget and add the NUnit package to your project references.
If you have NUnit installed you can run it side by side with VS, if you have Resharper I think it integrates it into VS for you as well.
Google NUnit to get it all set up, as this is isolated from the cad system at the moment it is no different to the examples on the web.

Code - C#: [Select]
  1. using System;
  2. using NUnit.Framework;
  3.  
  4.  
  5. namespace TestableAppTests
  6. {
  7.     /// <summary>
  8.     /// The code below runs some simple unit tests to test our Drawing object can have
  9.     /// objects added to it.
  10.     /// We create an IDrawing interface, this is an interface for the wrapper of the real
  11.     /// Bricscad drawing database, we could call it Database but we will keep things simple
  12.     /// and 'different' enough that we can't confuse our db with the cad one.
  13.     /// </summary>
  14.     [TestFixture]
  15.     public class UnitTest1
  16.     {
  17.         /// <summary>
  18.         /// Let's write the first test to see if we can add an object to the drawing.
  19.         /// </summary>
  20.         [Test]
  21.         public void AddObjectToDrawingReturnsLong() // we want a long returned, see below.
  22.         {
  23.            
  24.             var drawing = new MyDrawingHelper(); // implements IDrawing interface.
  25.            
  26.             // now test the AddObject method we are about to write, as we don't have
  27.             // a concrete class for objects yet we can see we need another interface
  28.             // for drawing objects. The implementation of the drawing object is not
  29.             // important yet, not for testing this MyDrawing class anyway.
  30.             //
  31.             // We need to either return some sort of result object or other device
  32.             // to tell us we have succeeded, an object id is good as we can store
  33.             // them in simple lists etc for retrieving them from the drawing object
  34.             // at a later time when needed (and follows typical cad return types as well).
  35.             var drawingObject = new DrawingObjectHelper();
  36.             long objectId = drawing.AddObject(drawingObject);
  37.  
  38.             Assert.AreEqual(1234, objectId);
  39.         }
  40.     }
  41.  
  42.  
  43.     /// <summary>
  44.     /// Here is our IDrawing interface, the first thing I want to do is add things to it,
  45.     /// it's a good a place as any to start.
  46.     /// Ideally we would like to write the proper implementation details that work with
  47.     /// the drawing database proper but the goal here is to create the api for our Drawing class.
  48.     /// By creating tests first and only creating the ones we want we can defer the details until later
  49.     /// and we can continue to develop our application in isolation knowing we have 'somewhere'
  50.     /// to store our objects.
  51.     /// </summary>
  52.     public interface IDrawing
  53.     {
  54.         /// <summary>
  55.         /// Here is the method signature for our interface, already we can see we need another
  56.         /// class to represent the objects we want to add to the Drawing, we will create an interface
  57.         /// for this class as well so we can do our testing.
  58.         /// We are not interested in what or how the object works yet, that's not the purpose of these
  59.         /// particular tests, we just want to make sure we can add objects and catch any errors.
  60.         /// </summary>
  61.         /// <param name="drawingObject"></param>
  62.         /// <returns></returns>
  63.         long AddObject(IDrawingObject drawingObject);
  64.     }
  65.  
  66.     /// <summary>
  67.     /// Our drawing implementation. This is basically a 'mock' and could just as easily been
  68.     /// created using the Moq library but to make things clearer for now we'll do it the hard
  69.     /// way by creating a helper class with dummy implementation. For simple objects this suffices
  70.     /// but you will soon see where using mocks will save a bit of time and code.
  71.     /// </summary>
  72.     public class MyDrawingHelper : IDrawing
  73.     {
  74.         public long AddObject(IDrawingObject drawingObject)
  75.         {
  76.             // hard code a know answer here, this is the work of the real
  77.             // database. It's easy to see we need to do some error handling from this
  78.             // which will be the next test.
  79.             long id = 1234;
  80.             return id;
  81.         }
  82.     }
  83.  
  84.     /// <summary>
  85.     /// Our Drawing Object interface, we use this with all objects we ant to add
  86.     /// to the drawing.
  87.     /// </summary>
  88.     public interface IDrawingObject
  89.     {
  90.         // nothing to do here yet, will no doubt end up another set of tests for our real objects.
  91.     }
  92.  
  93.     /// <summary>
  94.     /// The helper class for our drawing object to be passed in as an argument
  95.     /// Again, don't worry about this class, it's only a mock up of what we need later,
  96.     /// the point is our Drawing api will take any class that implements
  97.     /// the IDrawingObject interface.
  98.     /// </summary>
  99.     public class DrawingObjectHelper : IDrawingObject
  100.     {
  101.  
  102.     }
  103. }
  104.  

The next part will be writing tests to handle errors when adding objects, our api will start to take shape.
If we had of started writing a Drawing class first we would no doubt have come up with half a dozen methods and properties without batting an eye, by writing the test first we only write what we need when and as we need it. By continually running the tests we can catch any bugs as we go and as we are re-factoring as well we end up with a nice clean code file with 100% test coverage. Pretty cool, and when it comes time to implement the real database we have complete documentation of how we need to do it and the tests are already written.


« Last Edit: August 03, 2014, 08:30:03 PM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #1 on: August 03, 2014, 08:47:41 PM »
Part 2 - checking for add object errors

An object passed in could be null sop we had better check it, here's the test that is added to the testing class above:

Code - C#: [Select]
  1.         [Test]
  2.         public void AddObjectToDrawingReturnsZero()
  3.         {
  4.             var drawing = new MyDrawingHelper(); // implements IDrawing interface.
  5.  
  6.             // we know we return a long so a value of Zero should be a good enough flag
  7.             // to indicate an error without causing an exception. Maybe we should test for
  8.             // exceptions as well?
  9.             var drawingObject = new DrawingObjectHelper();
  10.             drawingObject = null; // make it broken.
  11.             long objectId = drawing.AddObject(drawingObject);
  12.  
  13.             Assert.AreEqual(0, objectId);
  14.         }
  15.  

I compile and the test is added to NUnit, run the test and it should fail as we have no implementation.

I write and re-factor the AddObject code to pass:
Code - C#: [Select]
  1.     public class MyDrawingHelper : IDrawing
  2.     {
  3.         public long AddObject(IDrawingObject drawingObject)
  4.         {
  5.             long id = 0; // default or error.
  6.  
  7.             // we need to check if we have a valid object, if not set the
  8.             // return to zero as an error flag.
  9.             if (drawingObject != null)
  10.             {
  11.                 id = 1234;
  12.             }
  13.             return id;
  14.         }
  15.     }
  16.  

compile and rerun tests, they pass and I think you get the idea, I might try and get the integration test up next and implement the cad api code.
Hopefully the pic's are in order :)
« Last Edit: August 03, 2014, 08:51:11 PM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #2 on: August 03, 2014, 09:05:29 PM »
Re-factoring:

To move onto the next stage I need to re-factor out our interfaces and test code to a more manageable and reusable state.

I've created a TestableApp class library which is the main app, I've created public interfaces for the Drawing and DrawingObject and moved the interface code over.
I also renamed the test class to DrawingTests to make more sense and this is where any Drawing test should live.

Once built, add reference to this in the TestableAppsTest project and rebuild and run all tests, it should all work as expected.

this has cleaned up our tests project and started the structure of our real application code.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #3 on: August 04, 2014, 02:25:39 AM »
Hit a bit of a snag, the Gallio test runner doesn't have an extension for Bricscad, going to have to build one based on the source from the AutoCAD extension, will report back soon.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: TDD for Bricscad examples
« Reply #4 on: August 04, 2014, 02:32:02 AM »
On the feed from Redgate
https://www.simple-talk.com/dotnet/.net-framework/a-tdd-journey-1-trials-and-tribulations/ 

all 6 parts.

//========
Mick,
Did you also see Scott's DV2177 - Sharpen Your C# Code for Autodesk® AutoCAD® ? from AU 2013.
added
There is also Programming AutoCAD with C#: Best Practices which is from AU2012 (essentially the same lecture).
There is some good code samples with these lectures.

« Last Edit: August 04, 2014, 03:53:05 AM by Kerry »
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: TDD for Bricscad examples
« Reply #5 on: August 04, 2014, 02:37:25 AM »
Hit a bit of a snag, the Gallio test runner doesn't have an extension for Bricscad, going to have to build one based on the source from the AutoCAD extension, will report back soon.

I had a lot of difficulty with Gallio. Old timers disease I think .. the brain is not as good as it used to be.

I think Andrey Bushman found a solution.
http://www.theswamp.org/index.php?topic=39236.0
http://www.theswamp.org/index.php?topic=47110.msg521458#msg521458
and some other posts .
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #6 on: August 04, 2014, 02:48:54 AM »
No, I haven't seen that doc as yet Kerry but will take a look for sure.

The other links look good too, I'm pretty happy with the theory side but now I need to put it into practice. I don't want to write any more untested code. :)

I got Gallio up and running but as the extension it needs to run is for Autocad (i.e. compiled against autocad dlls) I can't test my Bricscad code otherwise I'd have it running.

I've checked out the source from github and taking a look tonight to see what's involved, hopefully it will be a matter of just swapping out the dlls for Bricscad ones and a few minor fixes as a result.

I think I'll create a github repo for this project as well once it takes shape.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

CADbloke

  • Bull Frog
  • Posts: 342
  • Crash Test Dummy
Re: TDD for Bricscad examples
« Reply #7 on: August 04, 2014, 05:46:16 PM »
Bookmarked this one to follow the progress and contribute what I can. There is a related thread at http://www.theswamp.org/index.php?topic=39236.msg522593#msg522593 <== the link is to my post about how I use nUnit. For mocking I use nSubstitute, mainly because I know the guys who wrote it and they are uber alpha testing geeks.

I haven't really looked into Gallio, mostly because (correct me if I'm wrong) they have stopped updating it. Also, all the cool kids use nUnit (or xUnit) so that's where the knowledge and assistance is.

I've gotta say, between unit testing and source control I feel a lot braver to just try something and see if it works, knowing I can test my results and roll back if I broke it. If only my mouth had this functionality.  :|

EDIT: adding more related links so eat a few more of our hours.
Gallio & AutoCAD's version on TheSwamp and also on StackOverflow

Running nUnit on Gallio: http://stackoverflow.com/questions/6566130/running-nunit-tests-with-gallio and a thread on Google Groups at  https://groups.google.com/forum/#!topic/gallio-dev/_0GwY51P5iU
« Last Edit: August 05, 2014, 10:05:01 PM by CADbloke »

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #8 on: August 04, 2014, 06:04:38 PM »
Hey CADbloke, thanks for the link. I think together we can all cobble something together that makes sense :)

After reading through the 6 part link Kerry provided I'm starting to rethink whether or not we need Gallio. It would definitely be good for integration testing but by using mocks we should be able to intercept the calls to the un-testable code for unit testing.
In reality, do we need to test a function, not written by us and that should already at least been proven to work if not tested?

For example, if in our app we have a circle class and we have a method that sets the radius, it might look something like this:

Code - C#: [Select]
  1.     public interface ICircle{
  2.         double GetRadius();
  3.     }
  4.  
  5.     public class MyCircle : Circle ICircle{
  6.         public double GetRadius(){
  7.             return this.Radius;
  8.         }
  9.     }
  10.  

We have derived from an AutoCAD Circle class as a base and implemented our ICircle interface so we get the class for free and we only implement our interface so we can use this circle in our app.
By doing this we could derive another MyCircle class to use in another CAD application with a different api and it wouldn't break our other code.

With a mock we should be able to intercept this call and return a hard coded double.
We are testing the api and behaviour of 'our' class not the api of the Circle we derived from.

I'll give it a go later and see what comes of it.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: TDD for Bricscad examples
« Reply #9 on: August 04, 2014, 06:08:00 PM »
< ..>
In reality, do we need to test a function, not written by us and that should already at least been proven to work if not tested?

< ..>

With a mock we should be able to intercept this call and return a hard coded double.
We are testing the api and behaviour of 'our' class not the api of the Circle we derived from.


Rushing past ...

No
Yes
Yes

kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

CADbloke

  • Bull Frog
  • Posts: 342
  • Crash Test Dummy
Re: TDD for Bricscad examples
« Reply #10 on: August 04, 2014, 07:23:03 PM »
I got Gallio up and running but as the extension it needs to run is for Autocad (i.e. compiled against autocad dlls) I can't test my Bricscad code otherwise I'd have it running.

I've checked out the source from github and taking a look tonight to see what's involved, hopefully it will be a matter of just swapping out the dlls for Bricscad ones and a few minor fixes as a result.

I think I'll create a github repo for this project as well once it takes shape.

This commit on Github may give you an idea of where to look for the AutoCAD / BricsCAD etc support.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #11 on: August 04, 2014, 07:41:37 PM »
Yep, I downloaded the source last night and had a go at reusing the code with some compiler directives for Bricscad and AutoCAD but it already had the AutoCAD runner registered and as the name spacing for the Bricscad runner is the same it's getting confused.

It almost worked but I think I will need to just clone the whole source and just update it for Bricscad, not a big deal really just time find/replacing and a few other things that I came across last night.
I should be able to just provide a folder that can be added to the Gallio install and it will work the same as the AutoCAD plugin. Just a shame to have 2 sets of code and it would be a lot more work to make it suit both.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #12 on: August 04, 2014, 08:10:07 PM »
Ok, we have some success!

Here's an update of the code from before, the interfaces for the Drawing and DrawingObject are in the other project and the code below our tests should be re-factored out as well.

I had to create a further abstraction of the cad api so I could mock it but the design is much more flexible now and still makes sense (I think...).

Here is the code that is dependent on the CadEngine proper and it fails (see pic) as it can't load the cad dll's etc.
Code - C#: [Select]
  1. using System;
  2. using NUnit.Framework;
  3. using TestableApp;
  4. using Moq;
  5. using Teigha.DatabaseServices;
  6.  
  7.  
  8. namespace TestableAppTests
  9. {
  10.     [TestFixture]
  11.     public class DrawingTests
  12.     {
  13.         [Test]
  14.         public void AddObject_ToDrawing_ReturnsNonEmptyString()
  15.         {
  16.             var mockDrawingObject = new Mock<IDrawingObject>();
  17.  
  18.             // create our Drawing object passing in the cad engine dependency:
  19.             var drawing = new MyDrawing(new CadEngine());
  20.  
  21.             string objectId = drawing.AddObject(mockDrawingObject.Object);
  22.  
  23.             Assert.AreEqual("1234", objectId);
  24.         }
  25.  
  26.  
  27.         [Test]
  28.         public void AddObject_ToDrawing_ReturnsEmptyString()
  29.         {
  30.             // no need to implement the mock return as it shouldn't reach the call:
  31.             var stubCadEngine = Mock.Of<ICadEngine>();
  32.             var drawing = new MyDrawing(stubCadEngine);
  33.  
  34.             // just pass in a null object, no need to 'null' anything to get the same result:
  35.             string objectId = drawing.AddObject(null);
  36.  
  37.             Assert.AreEqual(String.Empty, objectId);
  38.         }
  39.     }
  40.  
  41.     /****** Class implementations for testing *********/
  42.     public class MyDrawing : IDrawing
  43.     {
  44.         private ICadEngine _cadEngine;
  45.  
  46.         public MyDrawing(ICadEngine cadEngine)
  47.         {
  48.             _cadEngine = cadEngine;
  49.         }
  50.  
  51.         public string AddObject(IDrawingObject drawingObject)
  52.         {
  53.             if (drawingObject != null)
  54.                 return _cadEngine.AddObjectToDB(drawingObject);
  55.             else
  56.                 return String.Empty;
  57.         }
  58.     }
  59.  
  60.     /// <summary>
  61.     /// Our interface to abstract the real cad api away from our Drawing class.
  62.     /// We need this so we can create a mock and to use the interface methods.
  63.     /// </summary>
  64.     public interface ICadEngine
  65.     {
  66.         string AddObjectToDB(IDrawingObject drawingObject);
  67.     }
  68.     /// <summary>
  69.     /// This could be our boiler plate class to handle the direct manipulation
  70.     /// of the parent cad system api. This could be in its own dll and have its own
  71.     /// integration test suite written using Gallio to test a running instance.
  72.     /// We could maybe even just write some hard coded test commands and do our own
  73.     /// assertions and print results to the command line or a file.(?)
  74.     /// </summary>
  75.     class CadEngine : ICadEngine
  76.     {
  77.         public string AddObjectToDB(IDrawingObject drawingObject)
  78.         {
  79.             string id = String.Empty; // default and for error.
  80.  
  81.             // cast it to something the db can handle:
  82.             Entity entity = drawingObject as Entity;
  83.             if (entity != null)
  84.             {
  85.                 // the standard boiler plate for adding entities to a dwg database:
  86.                 Database db = HostApplicationServices.WorkingDatabase;
  87.                 using (Transaction tr = db.TransactionManager.StartTransaction())
  88.                 {
  89.                     try
  90.                     {
  91.                         var blockTable = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
  92.  
  93.                         var modelSpace = (BlockTableRecord)tr.GetObject(
  94.                             blockTable[BlockTableRecord.ModelSpace], OpenMode.ForWrite);
  95.  
  96.                         // as our DrawingObjects inherit from classes that inherit from Entity,
  97.                         // we should be able to just add them here:
  98.                         ObjectId objectId = modelSpace.AppendEntity(entity);
  99.                         tr.AddNewlyCreatedDBObject(entity, true);
  100.                         tr.Commit();
  101.  
  102.                         // convert the returned id to a long for our use:
  103.                         id = objectId.ToString();
  104.                     }
  105.                     catch (Exception ex)
  106.                     {
  107.                         tr.Abort();
  108.                         // if we get here there was an error and we will return the default value empty string.
  109.                     }
  110.                 }
  111.  
  112.             }
  113.             return id;
  114.         }
  115.     }
  116. }
  117.  
  118.  

And here's the fix up that uses the mock to short circuit the call to the real cad api. (just the test), all test now pass.
Code - C#: [Select]
  1.         [Test]
  2.         public void AddObject_ToDrawing_ReturnsNonEmptyString()
  3.         {
  4.             var mockDrawingObject = new Mock<IDrawingObject>();
  5.  
  6.             // set up the mock with handler to bypass the untestable
  7.             // method in the cad engine:
  8.             var mockCadEngine = new Mock<ICadEngine>();
  9.             mockCadEngine.Setup(x => x.AddObjectToDB(mockDrawingObject.Object)).Returns("1234");
  10.  
  11.             // create our Drawing object passing in the mock dependency:
  12.             var drawing = new MyDrawing(mockCadEngine.Object);
  13.  
  14.             string objectId = drawing.AddObject(mockDrawingObject.Object);
  15.  
  16.             Assert.AreEqual("1234", objectId);
  17.         }
  18.  

The trick with mocks is you need to use an interface, I've just created a CadEngine abstraction and implemented an interface with the methods I need to handle and all is good.
« Last Edit: August 04, 2014, 08:16:12 PM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #13 on: August 04, 2014, 11:49:51 PM »
I've probably wasted a day adapting the Gallio AutoCAD test runner to work with Bricscad and I almost made it. I had the plugin all set up even with the control panel settings addin but there's still some issues when running the tests.
The source came with its own tests so I figured I'd get them going, they were all re-written and compiled ok so I thought I'd give them a run...do you think I could find a simple tutorial on using mbUnit to run the tests??

As I was looking I noticed a few posts and also a note on the Gallio site saying the project is in hiatus...great :(

I've lost all enthusiasm for Gallio now and will be searching for a better option, I'm happy with the current set up though (as per above sample) as I've abstracted the cad stuff far enough away from my the actual app.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: TDD for Bricscad examples
« Reply #14 on: August 04, 2014, 11:56:07 PM »
Hi Mick,
PM Andrey Bushman. He may be able to share what he discovered.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #15 on: August 05, 2014, 12:13:26 AM »
Hi Kerry,
the problem isn't really with Gallio, it's with the Gallio extension for testing AutoCAD. It is written only for AutoCAD and thus doesn't work with Bricscad as the AutoCAD lib's are tied to the build.
Getting it running is easy on AutoCAD once the bat file is set up.

I have a few more things to try with the Bricscad extension before I give up altogether.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

CADbloke

  • Bull Frog
  • Posts: 342
  • Crash Test Dummy
Re: TDD for Bricscad examples
« Reply #16 on: August 05, 2014, 06:07:26 PM »
Just a thought and not necessarily an good one ... is running tests inside AutoCAD / Bricscad is more like integration testing than unit testing? Perhaps is is more like behavio(u)ral testing too? ie. Do you want to test the result of the operation rather than the units of the operation? This is still testing and therefore valid but what I am trying to say (badly) is that the approach should be driven by what you are actually testing so there is probably a need for different types of tests for the same code.

Related to this - has anyone tried a Coded UI test in AutoCAD / Bricscad?

Hey, it's 8am and I've only just finished my first coffee.  :-P

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #17 on: August 05, 2014, 07:48:30 PM »
Just a thought and not necessarily an good one ... is running tests inside AutoCAD / Bricscad is more like integration testing than unit testing? ....

Yes, you are right there and that was the reason for using the mocks to isolate the actual implementation code. We should be able to write the implementation for out tests and mock them out as needed to make the tests pass and confirm our design/behaviour.

I think TDD includes all types of testing and integration testing is just as important as unit testing, it's just another hurdle I want to hobble over before I get too far along the process and testing against the cad api is the hardest.

I've made a bit of progress though, I've actually embedded the tests in the class library (these could be loaded separately of course) and it works fine, I just need to re-route the test output to a file or the command line somehow.

This code works fine and wouldn't be hard to automate the calls to each test from one command. You can see I'm not using mocks and it places the circle and then no circle but I have no feedback to whether it is a pass or fail.
Code - C#: [Select]
  1. namespace CadEngine
  2. {
  3.     [TestFixture]
  4.    
  5.     class CadEngineDrawingTests
  6.     {
  7.         [CommandMethod("Test1")]
  8.         public void AddObject_ToDrawing_ReturnsNonEmptyString()
  9.         {
  10.             var drawingObject = new MyCircle(50);
  11.  
  12.             // create our Drawing object passing in the mock dependency:
  13.             var cadEngine = new CadEngine();
  14.             var drawing = new Drawing(cadEngine);
  15.  
  16.             string objectId = drawing.AddObject(drawingObject);
  17.  
  18.             Assert.AreNotEqual(String.Empty, objectId);
  19.         }
  20.  
  21.  
  22.         [Test]
  23.         [CommandMethod("Test2")]
  24.         public void AddObject_ToDrawing_ReturnsEmptyString()
  25.         {
  26.             // no need to implement the mock return as it shouldn't reach the call:
  27.             var cadEngine = new CadEngine();
  28.             var drawing = new Drawing(cadEngine);
  29.  
  30.             // just pass in a null object, no need to 'null' anything to get the same result:
  31.             string objectId = drawing.AddObject(null);
  32.  
  33.             Assert.AreEqual(String.Empty, objectId);
  34.         }
  35.     }
  36.  
  37.     /****** Class implementations for testing *********/
  38.     public class Drawing : IDrawing
  39.     {
  40.         private ICadEngine _cadEngine;
  41.  
  42.         public Drawing(ICadEngine cadEngine)
  43.         {
  44.             _cadEngine = cadEngine;
  45.         }
  46.  
  47.         public string AddObject(IDrawingObject drawingObject)
  48.         {
  49.             if (drawingObject != null)
  50.                 return _cadEngine.AddObjectToDB(drawingObject);
  51.             else
  52.                 return String.Empty;
  53.         }
  54.     }
  55. }
  56.  

<edit> I think I might just write my own assertions and be done with it, they aren't that complicated really and can be more finely tuned to the cad api</edit.
« Last Edit: August 05, 2014, 07:55:45 PM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #18 on: August 05, 2014, 08:50:11 PM »
I've written a basic assert class as proof of concept, seems to work ok :)

Code - C#: [Select]
  1.     public class Assert
  2.     {
  3.         private static string GetCaller()
  4.         {
  5.             // this stack trace may have performance penalties later, it's a hack for now:
  6.             System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace();
  7.             int caller = 2;
  8.             System.Diagnostics.StackFrame frame = trace.GetFrame(caller);
  9.             return frame.GetMethod().Name;
  10.         }
  11.  
  12.         public static void AreEqual(string testValue, string returnedValue)
  13.         {
  14.             // if string is empty, format it so we can see:
  15.             if (testValue == String.Empty) testValue = "\"\"";
  16.             if (returnedValue == String.Empty) returnedValue = "\"\"";
  17.  
  18.             if (testValue != returnedValue)
  19.             {
  20.                 _AcAp.Application.DocumentManager
  21.                     .MdiActiveDocument
  22.                     .Editor
  23.                     .WriteMessage(GetCaller() + " passed");
  24.             }
  25.             else
  26.             {
  27.                 _AcAp.Application.DocumentManager
  28.                     .MdiActiveDocument
  29.                     .Editor
  30.                     .WriteMessage(GetCaller() + " FAILED: Expected "
  31.                     + testValue + " - actual result: " + returnedValue);
  32.             }
  33.         }
  34.  
  35.         public static void AreNotEqual(string testValue, string returnedValue)
  36.         {
  37.             // if string is empty, format it so we can see:
  38.             if (testValue == String.Empty) testValue = "\"\"";
  39.             if (returnedValue == String.Empty) returnedValue = "\"\"";
  40.  
  41.             if (testValue == returnedValue)
  42.             {
  43.                 _AcAp.Application.DocumentManager
  44.                     .MdiActiveDocument
  45.                     .Editor
  46.                     .WriteMessage(GetCaller() + " passed");
  47.             }
  48.             else
  49.             {
  50.                 _AcAp.Application.DocumentManager
  51.                     .MdiActiveDocument
  52.                     .Editor
  53.                     .WriteMessage(GetCaller() + " FAILED: Expected "
  54.                     + testValue + " - actual result: " + returnedValue);
  55.             }
  56.         }
  57.     }
  58.  

I added a 3rd test to fail on purpose that expected an empty string to see the output was correct.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

JohnK

  • Administrator
  • Seagull
  • Posts: 10637
Re: TDD for Bricscad examples
« Reply #19 on: August 05, 2014, 09:37:54 PM »
Just stopping in to say that this subject has lead me down a rabbit hole. I need to get back to my reading so I can write up some of my ideas. …great thread Mick.
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: TDD for Bricscad examples
« Reply #20 on: August 05, 2014, 09:48:45 PM »
Just stopping in to say that this subject has lead me down a rabbit hole. I need to get back to my reading so I can write up some of my ideas. …great thread Mick.

Yes John, I've had to stop reading the thread (other than casual perusal) .. I was getting sucked in too.

This IS a great thread Mick !  ... I hope to catch up with it after I've slain a few dragons.
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #21 on: August 05, 2014, 09:50:37 PM »
Glad you like it, just hope it's helping and not confusing the issue too much :)

I am tidying up the whole working solution and will put it on github later today/tonight, got a bit of real work to do this afternoon.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: TDD for Bricscad examples
« Reply #22 on: August 05, 2014, 09:56:59 PM »
Just for interest,
Have you tried UnitTests in Resharper inside Visual Studio Mick ?
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #23 on: August 05, 2014, 10:13:17 PM »
Just for interest,
Have you tried UnitTests in Resharper inside Visual Studio Mick ?

I have but I'm not going to fork out 150 clams for it, it is great for refactoring but I also found it annoying (worse than VS) with the contextual help popups etc when you don't really want them.
Running the NUnit gui is easy and I just have it to the side of VS in the other window.
If you need any help setting it up I'll post up a quick tut but you basically just 'Open Project' from the NUnit gui menu and load the test assembly and hit run, of course this doesn't work within the cad system hence the above posts.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: TDD for Bricscad examples
« Reply #24 on: August 06, 2014, 08:07:35 AM »
As someone who is very new to Unit Testing (i.e. I had to read up on it to know what it means) I wonder if someone could post a very simple VS solution that has a small AutoCAD project and a unit testing project as an example?  I read the code excerpts in this thread and now I feel as if my IQ has dropped about 20 pts because I am completely lost.  Thanks in advance.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #25 on: August 06, 2014, 06:35:59 PM »
No prob's Keith, I'll clean this one up and post it a bit later.

Here's a link to Roy Osherove's vid's, the first one's a bit slow getting going but all are well worth the watch. Try to do some coding and then watch again :)

http://artofunittesting.com/

"Understanding Test Driven Development" is the first one to watch and a few others from that talk are also on that page, should be enough to get started with. As mentioned before, anything from 'Uncle Bob' on SOLID development will help as well when it comes to design problems.

« Last Edit: August 06, 2014, 06:49:10 PM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #26 on: August 06, 2014, 07:58:02 PM »
Here's a VS2010 solution as it stands today, it is using Bricscad v14 lib's but you should be able to switch them out for AutoCAD with little work.

Tools required:
NuGet package manager should be installed in VS to manage the Nunit and Moq packages, this is a lot easier than loading all the requirements manually.
NUnit - download and install, I run it in the 'mini' setup which hides the output progress and messages, you can find the output by right clicking on the test to see any errors etc.

To run the tests in NUnit, start NUnit and go 'Open Project' and browse to the bin folder and select the dll with <ProjectName>.Tests.dll and you should be able to see and run the tests.
Repeat for the other test project/s.

<edit> Another confusing aspect of testing is 'how do you debug?', it can be done but really, the process of testing and running tests takes you straight to the problem if it fails anyway, you shouldn't need to debug at all, if you do your tests (or implementation code) have too much logic, another "code smell" if you like</edit>
« Last Edit: August 06, 2014, 08:06:59 PM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Keith Brown

  • Swamp Rat
  • Posts: 601
Re: TDD for Bricscad examples
« Reply #27 on: August 07, 2014, 09:35:54 AM »
MickD,


Thank you very much for the links and the test project.  I will have some time in the next few weeks to consume this information and hopefully will have a much better understanding of how it all works.  My end goal is of course being able to write good tests but also better understand the process of separating the business layer from the data layer of the software.  And in the long run be able to write more reusable code.  It seems i am constantly coding the same things over and over again with new bugs every time i code it.
Keith Brown | AutoCAD MEP Blog | RSS Feed
AutoCAD MEP 2014 / Revit MEP 2014 / EastCoast CAD/CAM addon / Visual Studio 2013

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #28 on: August 07, 2014, 06:01:15 PM »
No problems Keith, it's not easy (else everyone would be doing it  :-P ) but well worth the effort. I was a doubter until I kept seeing it pop up and read/watched a few talks for it to sink in, actually doing some coding really helps it hit home.

Just by writing the test first forces you to design and code better as a happy side effect and Interfaces (IoC) are the key. Even if you only watch/read one good talk on SOLID code and it's a bit daunting, start writing tests then the SOLID principles start to make sense as well.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

Jeff H

  • Needs a day job
  • Posts: 6150
Re: TDD for Bricscad examples
« Reply #29 on: August 07, 2014, 06:50:31 PM »
Hey Mick,

Did you ever have moment where you were thinking TDD seems like it is not useful then you looked at project and realized Holy Sh*t I have almost a complete working code base for project and I did not spend hours(while driving, at night, etc..) thinking about what classes to create, abstractions, how each object will talk with one another, etc..... it just happened?

MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: TDD for Bricscad examples
« Reply #30 on: August 07, 2014, 07:28:52 PM »
Hey Mick,

Did you ever have moment where you were thinking TDD seems like it is not useful then you looked at project and realized Holy Sh*t I have almost a complete working code base for project and I did not spend hours(while driving, at night, etc..) thinking about what classes to create, abstractions, how each object will talk with one another, etc..... it just happened?

How much better would it be if that code base was already tested and decoupled?
I see your point and I'm not saying you need to write every app from scratch, that's the point of writing SOLID code using TDD, it's even more reusable.

Take the CadEngine I've used as an example, it would be very easy to plug that into another app and extend it as needed making the extensions available to other apps for free.

I think that when writing your new project with TDD and reusing your existing 'tested' code base would be a coding utopia :)

<edit> Actually, by using TDD you should only do the thinking as you code, thinking about design at any other time is a waste of time. You do need some basic idea of your goals and some very basic architecture you think you might need but this should only be like a napkin sketch as it will no doubt change as start to develop your code base and your code will be ready to accept those changes.
« Last Edit: August 07, 2014, 07:55:21 PM by MickD »
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien