Author Topic: Lets start a deeper discussion to teach people (like me) how to apply TDD  (Read 1579 times)

0 Members and 1 Guest are viewing this topic.

shupsta2010

  • Mosquito
  • Posts: 17
I am not the only one who has seen, or posted, forum threads asking for help when it comes to testing your plugin in an automated way. Whether it be unit or integration testing, the desired result is still the desire to apply TDD when developing a .NET plugin for AutoCAD. As someone who has made one of these posts, I feel it is coming from a place of someone who is new to software development and doesn't know where to start with TDD. And then comes to find that there is an extra layer of difficulty in depending on AutoCAD to run your tests.

Now I have read Scott MacFarlane's Automated Testing with the AutoCAD .NET API

https://forums.autodesk.com/autodesk/attachments/autodesk/152/51134/1/handout_2654_CP2654_Automated_Testing.docx

And while it was helpful it is also very out of date, relaying on Gallio which doesn't appear to have been updated in quiet some time.

I have also began using CADBloke's CADTest project in my own for testing

https://github.com/CADbloke/CADtest

This is a great tool, though I've run into difficult a few times with it. But it is simply a tool, and in the hands of an inexperienced developer can only do so much.

I usually think in examples so I'll use a personal one that I think might be a good demonstration. Thankfully this tool has already been developed, but I'm now maintaining it:

Say we want a tool to array out lines to represent joists on a floor plan. The tool asks for a closed polyline, then a joist spacing amount, and an orientation the joists are ment to go (ie EW or NS). I could see manually running a test where I have a closed polyline ready, I input the spacing I know I'm testing for, and the orientation I'm testing for, and then can visually inspect if it worked. But how could this be done with automation?

I could record the handle of the closed polyline and then run a test where that gets fed into the tool, and do the same for the other pieces of information. But how could I verify it worked without visually inspecting it?

I think this is just one example of the difficulty many are trying to over come. I see many posts where people ask how to unit test their plugin and are met with either people saying it's not very feasible, or simply linking one of the above links I've already provided.

I guess I'm just hoping to create a place where people can hopefully get some answers when they Google "AutoCAD Unit Testing".

Thanks for any help, and for reading my rant!

JohnK

  • Administrator
  • Seagull
  • Posts: 10637
Re: Lets start a deeper discussion to teach people (like me) how to apply TDD
« Reply #1 on: November 12, 2021, 12:57:25 PM »
Function return types has a lot to do with testing so your unit testing essentially just amounts down to testing that 1 ='s 1.

In the beginning setup of that document the author touches on what they call "code isolation" which is correct but it is a little more complicated then what they demonstrate.

The code on page 7 starts discussing the task of building `isolated code` but the author doesn't quite get the concept right (this is a really bad example to use in my opinion but let's go with it for now).

This is the routine in the document.
Code: [Select]
private void SetCircleColor(Circle circle) {
if (circle.Radius < 1.0) {
circle.ColorIndex = 2;
} else if (circle.Radius > 10.0) {
circle.ColorIndex = 1;
} else {
circle.ColorIndex = 3;
}
}

To do testing you need to keep the return types in mind when building routines because trying to unit test a bunch of voids is not really possible/useful so you'd build `isolated` routines like this:

Code: [Select]
public int drawcircle()...
public int getcircle()...
public int setcirclecolor()...

A better example is something simple that can be written a few lines of code.

Code: [Select]
public int square(int a){
        return a*a;
}
public int add(int a, int b){
        return a+b;
}
Your tests should be something like:
Code: [Select]
assert.isequal(square(2), 4);
assert.isequal(add(1, 2) 3);
...

As you can see, this keeps things very easy to keep track of.

The code I use for testing in C is very simplistic (overly) but gets the job done.
https://jera.com/techinfo/jtns/jtn002

Here is a more robust version that demonstrates the process of Automation. Which is essentially just an extension of the `return types` checking. If you check out the below and look at the example file "minunit_example.c" you should be able to see that the MAIN will return an int. If you look in the `CI` folder you will see a bat file just runs the "minunit_example" and checks that a passing int is returned.
https://github.com/siu/minunit

Obviously your returns get more complicated as your operations get more complicated. That is to say, you start returning 1 for good and 0 for bad or other type of error codes, but the principle stands; for the most part you check your function returns against expected results.

Some testing will always have to have some human interaction. For example I was building a "breadth-first directory transversal" thing and I could not really build a test for that so I just built a sample directory with some sample files and ran a "tester program" to see if directories printed out in the right order. It wasn't automated or elaborate (just a program that printed stuff to the command line) but I couldn't really build a verification for that.
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

shupsta2010

  • Mosquito
  • Posts: 17
Re: Lets start a deeper discussion to teach people (like me) how to apply TDD
« Reply #2 on: November 12, 2021, 01:57:15 PM »
Function return types has a lot to do with testing so your unit testing essentially just amounts down to testing that 1 ='s 1.

In the beginning setup of that document the author touches on what they call "code isolation" which is correct but it is a little more complicated then what they demonstrate.

The code on page 7 starts discussing the task of building `isolated code` but the author doesn't quite get the concept right (this is a really bad example to use in my opinion but let's go with it for now).

This is the routine in the document.
Code: [Select]
private void SetCircleColor(Circle circle) {
if (circle.Radius < 1.0) {
circle.ColorIndex = 2;
} else if (circle.Radius > 10.0) {
circle.ColorIndex = 1;
} else {
circle.ColorIndex = 3;
}
}

To do testing you need to keep the return types in mind when building routines because trying to unit test a bunch of voids is not really possible/useful so you'd build `isolated` routines like this:

Code: [Select]
public int drawcircle()...
public int getcircle()...
public int setcirclecolor()...

A better example is something simple that can be written a few lines of code.

Code: [Select]
public int square(int a){
        return a*a;
}
public int add(int a, int b){
        return a+b;
}
Your tests should be something like:
Code: [Select]
assert.isequal(square(2), 4);
assert.isequal(add(1, 2) 3);
...

As you can see, this keeps things very easy to keep track of.

The code I use for testing in C is very simplistic (overly) but gets the job done.
https://jera.com/techinfo/jtns/jtn002

Here is a more robust version that demonstrates the process of Automation. Which is essentially just an extension of the `return types` checking. If you check out the below and look at the example file "minunit_example.c" you should be able to see that the MAIN will return an int. If you look in the `CI` folder you will see a bat file just runs the "minunit_example" and checks that a passing int is returned.
https://github.com/siu/minunit

Obviously your returns get more complicated as your operations get more complicated. That is to say, you start returning 1 for good and 0 for bad or other type of error codes, but the principle stands; for the most part you check your function returns against expected results.

Some testing will always have to have some human interaction. For example I was building a "breadth-first directory transversal" thing and I could not really build a test for that so I just built a sample directory with some sample files and ran a "tester program" to see if directories printed out in the right order. It wasn't automated or elaborate (just a program that printed stuff to the command line) but I couldn't really build a verification for that.

Thank you for your reply, that is very helpful info. I hadn't thought about the return types as much, as I figured if something works it works and if not it throws an error. But I see now that might be to simplistic. I'm at work so I can't fully mull over the implications of your reply, but the first thing that came to mind to try and tie back to my original post would be having the function that places the "joist" lines return the number of lines it drew. That could be tested against given a known polyline to start with.

Or better yet passing in a new rectangle object during testing, since the dimensions of the rectangle would be known a known number of "joists" would be placed.

JohnK

  • Administrator
  • Seagull
  • Posts: 10637
Re: Lets start a deeper discussion to teach people (like me) how to apply TDD
« Reply #3 on: November 12, 2021, 02:26:48 PM »
We're discussing the basics; object testing is a bit more complicated (and MSDN has documents and tutorials on that). But for this part, you need a better understanding of returns. Yes, you are correct to start rethinking how you could develop your routines better (and this is the goal; rethink the structure of your program) and a number of lines created is a great step. Or you may also want to try and return 1 upon success and 0 for fail.

Visual studio, Microsoft, C#, Java, etc. really complicate the whole testing thing and this is not necessarily bad just different. A lot of what you need to do in C# or Visual Studio results in the language's or tool's effort to simplify the coding experience (sort of a "cutting off one's nose to spite one's face" sort of thing). -i.e. using MSTest Unit test methods are used differently then what I describe above in that you add [TEST] methods to your code instead of building actual test files like the example above.

However, all this will eventually lead you down the use of `assert`, `try` etc, path in your function designs. I've always kept to the more traditional route -i.e. I program in C/C++ now-a-days and I type source code in a text editor and I send those files to the compiler. So when I'm working on a function or series of functions I spend most of my time developing the code header (name, description, returns) then the actual code. This ends up to be the code's specifications. This approach makes me a heretic and most people do the MSTest unit test method and add [TEST] methods to their code (which seems more of an afterthought to me) but I can at least steer you down the right hole in this rabbit hole topic.





TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

JohnK

  • Administrator
  • Seagull
  • Posts: 10637
Re: Lets start a deeper discussion to teach people (like me) how to apply TDD
« Reply #4 on: November 12, 2021, 02:53:51 PM »
Link posting time...

The basics of traditional unit testing is essentially function returns and thus verifying if a function returns the correct number but in the newer code designs this gets muddy fast! The old method resulted in code structure like:

Code: [Select]
if (!drawcircle()) {
        // oops! get out now!
        return;
}

but now we have a new method (Microsoft doing what it does best; change stuff) called `AAA method pattern something` which looks more like:
Code: [Select]
try{
        drawcircle()
} catch (system.overheat) {
        assert.contains("some thing");
}
Here is a link to a MSDN tutorial.

https://docs.microsoft.com/en-us/visualstudio/test/walkthrough-creating-and-running-unit-tests-for-managed-code?view=vs-2022

TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

shupsta2010

  • Mosquito
  • Posts: 17
Re: Lets start a deeper discussion to teach people (like me) how to apply TDD
« Reply #5 on: November 12, 2021, 03:19:31 PM »
So if I'm following, what you are describing are two different ways of unit testing. And that one would want to more or less know which method they intend to use when designing their method signatures. Both of these though assume you are creating a separate file/class for the tests. This class lets say is what would instantiate the class or use the method under test, and would either log a return of say zero for the old method, or log an exception that is caught in a try catch of some sort.

I have some experience in using asserts in using CADTest, by instantiating a class in the test and feeding the constructor certain input and using assert to test the state of the instantiated object afterward.

JohnK

  • Administrator
  • Seagull
  • Posts: 10637
Re: Lets start a deeper discussion to teach people (like me) how to apply TDD
« Reply #6 on: November 12, 2021, 03:48:34 PM »
If you are working on only AutoCAD based utilities you will be using the MSTest Unit Testing methods but generally speaking, writing only "private void" type routines shines a light on how sloppy you are (in my opinion).  If you are writing yourself a nice bank register routine (like the MS tutorial) I think you can get by using a more traditional methods of testing.

For example, given the following function:

Code: [Select]
public void Withdraw(double amount) {
    if(m_balance >= amount) {
        m_balance -= amount;
    } else {
        throw new ArgumentException(nameof(amount), "Withdrawal exceeds balance!");
    }
}

The following test is a waste of everyone's time in my opinion.  Spending your time designing a good function with a floor and ceiling like the `withdraw` function is you don't need to test.

Code: [Select]
[TestMethod]
public void Withdraw_AmountMoreThanBalance_Throws()
{
    // arrange
    var account = new CheckingAccount("John Doe", 10.0);

    // act and assert
    Assert.ThrowsException<System.ArgumentException>(() => account.Withdraw(20.0));
}

If I were writing the `withdraw` function I would make it return the current account balance (a blind function call/return does you no good other then for throwing exceptions) and handle this in the main calling function(s) -i.e. I wouldn't allow the exception to occur in the first place. Different schools of thought.
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

shupsta2010

  • Mosquito
  • Posts: 17
Re: Lets start a deeper discussion to teach people (like me) how to apply TDD
« Reply #7 on: November 12, 2021, 04:32:42 PM »
That was a good breakdown and example. I've felt that way when thinking about tests. In the handout from AutoDesk University they explain TDD as you write a test that you know will fail, then write the code to make it pass, and don't write any code before writing a test. Well in that case how can you even get started writing a class to house the functions you want for my joist example. And then if you do write a test that fails because that class doesn't exists, then you make it pass by the class existing, again what good does that do?

So I like your way of thinking around writing a function that returns something useful and it being the calling functions responsibility to handle what it returns. How would we apply this to AutoCAD testing?