Rhino.Testing

Hello dear colleagues,

I have recently came to conclusion that I need to do a unit tests to my GH plugin. Searching this forum I have discovered few repos/packages which should be suitable for this.

First idea was to test my GH scripts with C# custom components according to this sample. However, it did not worked well and I left that approach. Issues are described in here (1 and 2 ).

Finally, I have found Rhino.Testing library which looks very promising. There are also the SampleTests repo, which I downloaded. My aim now is to test my plugin in the following cases:

  • Test my methods which uses RhinoCommon library.
  • Read Geometry from Rhino and use it for test of my methods.
  • Call methods like Curve.CreateInterpolatedCurve() in my tests and use result for testing my methods.
  • Run GH script and read result according to Rhino.Testing workflow.

And here comes the issues. Over last two days I have tried to put Rhino.Testing library into operation unfortunately behavior of the test is unclear to me.

When I run the sample repo it works except tests when Rhino is opened (Headless or not; Net7.0 version works correctly). In these cases test are simply aborted, testing ends without any result. Test where Rhino.FileIO is used works just fine.

When I add new project to my plugin and set things according to instruction written here. it works only if I don’t use SetupFixture class.
So I can use Rhinocommon classes like Point3d etc. but I cannot use advanced methods like intersections and others. If I use them in test method I get error that rhcommon_c.dll cannot be referenced/found.

Using SetupFixture (according to the sample file and readme in the Rhino.Testing), which should solve this problem, as Rhino must be initialized for these advanced methods, leads to
different error. It occurs when OneTimeSetUp method is called. I have tried to reference RhinoCommon.dll for both Rhino 7 and Rhino 8 version. No change at all.

Class for the test is quite simple:

[TestFixture]
 public sealed class TestTunnel : RhinoTestFixture
 {
  [Test, Description("Test that constructor produce valid TBM_Class")]
     public void TestTunnelConstructor()
     {
         Assert.That(true);
     }
 }

Last try which I have done is simply adding SampleTests project to my solution. That works (again except tests where Rhino does not use Rhino.FileIO to read .3dm file). It works even if I reference different version of RhinoCommon.dll assembly in the tests project and my plugin. However, if I manually remove RhinoCommon.dll assembly reference from unit test project and add new one at the same location it stops to work and produce following error (System.DllNotFoundException: Liberary DLL RhinoLibraryCannot be found):

So my question is what I am missing/doing wrong? Why it works when I add sample repo but not for new one? What directory is not found? Why openHeadless method does not work for .Net48 version and work for .Net7.0?

Hope @fraguada or @menno could help me.

Thanks for the reply and time

Best regasrds

Ondřej

Just my own update. I was able to resolve problem with missing RhinoLibrary

and DirectoryNotFoundException.

I have mismatched copy local for some .dll and configs.xml file.

Now I have run into another (but minor issue). Which can be rather feature then problem. I would like to perform multiple testcases on one test. Some of the input to the tests is Rhino Geometry (curves, points etc). However, if I use TestCaseSource or TestCase attribute and define geometry in that input i again fails and raise error that RhinoCommon cannot be loaded.

    [TestFixture]
    public sealed class TestTunnel : RhinoTestFixture
    {
        private static IEnumerable<TestCaseData> TestTunnelConstructor_Params()
        {
            yield return new TestCaseData(null, null, Point3d.Unset, Point3d.Unset, false);
            yield return new TestCaseData(null, null, Point3d.Unset, Point3d.Unset, false);
            yield return new TestCaseData(null, null, new Point3d(0, 0, 0), Point3d.Unset, false);
            yield return new TestCaseData(null, null, new Point3d(0, 0, 0), new Point3d(1, 1, 1), false);
            yield return new TestCaseData(Curve.CreateInterpolatedCurve(new List<Point3d>() { new Point3d(0, 0, 0), new Point3d(1, 0, 0) }, 1), new List<Curve>(), new Point3d(0, 0, 0), new Point3d(1, 0, 0), true);
            yield return new TestCaseData(Curve.CreateInterpolatedCurve(new List<Point3d>() { new Point3d(0, 0, 0), new Point3d(1, 0, 0) }, 1), new List<Curve>(), new Point3d(0, 0, 0), new Point3d(1, 0, 0), true);
        }
        [Test, Description("Test of constructor produce valid TBM_Class")]
        [TestCaseSource(nameof(TestTunnelConstructor_Params))]
      
        public void TestTunnelConstructor(Curve alignment, List<Curve> alignments, Point3d startPoint, Point3d endPoint, bool result)
        {
            var tunnel = new TBM.TBM_Common.TBM_Tunnel(null, null, startPoint, endPoint);
            Assert.That(tunnel.IsValid == result);
        }

    }

If geometry is created directly in the test method it works. I except problem to be in the SetupFixuture class which (i assume) is not called before the input values (together with rhino geometry) are initialized. I am I right? Is this bug or feature I have to live with it? I can image some workaround (reading geometry from .3dm etc). But direct input with TestCaseSource would be much easier.

Thanks a lot for your reply and time.

Ondřej

I’ve followed along with this thread, and it sounds like things are mostly working for you now?

Regarding this;

The Rhino.Testing library inserts a resolver into the test classes that tell dotnet “Hey, if you can’t find an assembly, ask me! I might know where it is!”. Unfortunately, static C# properties load before this resolver is registered. Hence, it’s best if you use the [RhinoTestFixture] instead of inheriting the Test Setup class as attributes are created before static properties, and hence, allow that resolver to be registered before the static class can be instantiated.

Ssee docs here about this

2 Likes

Thanks for your reply and advice. Most of the things now works. :smiley:

I have read the docs you mention, before I started to implement [Rhino.Testing]. However, If ‘[Rhino.Testing]’ library is installed via NuGet marking class [RhinoTestFixture] raises an error Cannot apply attribute class RhinoTestFixture because it is abstract. So, I left that option.
This happens for my my test and even for this sample.

When I look into Rhino.Testing repo itself [RhinoTestFixture] is used in some tests and works. Even if the attribute is [RhinoTestFixture] it points rather to the [RhinoTestFixtureAttrbute class] which is not marked as abstract.

I tried to add [Rhino.Testing] project to my solution and reference it as a project not via NuGet. And it works (stops to raise error with abstract class). So it looks like there is a difference between nuget version and a github and downloaded version.

Both says version 8.0.023.0

Am I missing something else?

Thanks

Ondřej

Would you be able to share your project, or a stripped down version @janotaondrej91, I’m trying to reproduce your issue, but I can’t seem to.

No problem. However, I tried to reproduce error as well and changed Rhino.Library reference back to NuGet package. It works back so I can’t reproduce it as well. I will let you know if it appear again.

Thank you very much for help.

Ondřej

1 Like

Is this library able to be used with Rhino 7 and net48?

Is there some way not to run Rhino headless? I want to do some UI testing but I also want access to certain parameters of my plugin. I want to have some kind of hybrid test that opens the Rhino UI and clicks on things, but also “knows” about what Rhino is doing under the hood.

I have used the NUnitTestRunner NUnitTestRunner (quite extensively) in the past for running commands and complex end to end tests in a CI/CD System.

Thanks, it is wpf plugin and the goal is to test the UI buttons, etc. in an automated way, so that’s why I want to actually have the Rhino window physically open rather than headless.

If you’re going to run it on a CI/CD machine, just note that physically moving the actual mouse cursor around with code will likely fail at some point as some random window will get in the way. If you can, invoke the things you’d like to click or interact with via code.

1 Like

I was able to test WinForms forms by classic unit tests with XUnit. All forms and buttons was invoked by code without running Rhino at all. However, all the input was not Rhino geometry/elements.

1 Like

How did you invoke the buttons? Did you send click events to them via a specific library?

No, did not use any specific library. For the debug env. I set buttons, methods (linked to the click event) and all necessary stuff to be internal. Then I can use it in the test as following:

 [Fact]
 public void ConnectButton_Click_WithXLSXConnectionType_FileDoesNotExist_ShowErrorMessage()
 {
     // Arrange
     var form = new ConnectIbForm();
     form.ConnectionTypeCBox.SelectedIndex = 0;
     form.PathToFileTBox.Text = "NonExistentFile.XLSX";
     form.ConnectionNameTBox.Text = "TestConnection";

     // Act
     form.ConnectButton_Click(null, EventArgs.Empty);

     // Assert
     Assert.Null(form.Connection);
 }

I am not sure if I follow best practices regarding unit testing but it brings the result.

For the interaction with the Rhino/GH I have second project in which I use tmakin/RhinoCommonUnitTesting: Example of unit testing RhinoCommon from within the Visual Studio test runner on windows this repo as a source.

I have also tested Rhino.Testing but I have few problems which I was not able to solve.

At the end of the day I ended using different repo/library for different use case in on solution. What I need to add to have all the tests is to test GHScripts and RhinoCommands directly in the Rhino/GH but without Rhino/GH running graphically.

Ondřej

Thanks all to the replies! I’m having pretty good luck doing action recording in my plugin, and replaying using a standard VS Unit Test csproj, along with flaUI, for managing the clicking of Xaml elements. I tried WinAppDriver but I had some issues, probably because it has been discontinued. Anyways, flaUI seems very robust for the task-at-hand. In terms of interacting with non-XAML elements I had to develop a small api accessed via a Rhino command that runs various plugin-specific operations. The UI replay test both clicks XAML elements and types things into the Rhino command line.