Author Topic: TDD for Bricscad examples  (Read 9938 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.