Advice on Writing Python Tests?

This is not, strictly speaking, a Ladybug Tools question - so apologies in advance for that. But I wonder if I might ask for some advice, if anyone has any, on the best protocol for writing unittests for my own Python code? In particular, given the large amount of awesome .py code the LBT team has built, I wonder if you have some thoughts on a particular issues I am getting stuck on?

I’m trying to do a better job of testing my code, currently using pytest for most projects - that all works great and I like it a lot. BUT, when I am working on code that I will be using inside the Rhino/GH environment, I’m at a loss for how to manage the Rhino-side dependencies properly? For instance, if I have same super simple code that I’ll later import into some GH components, I can write and test it just fine on its own, so long as it doesn’t have dependencies:

BUT, if I want to do something like use a Rhino or Grasshopper class or function for some reason, now my test-discovery and test-execution fails cus’, obviously, it doesn’t know how to resolve the ‘import Rhino’ line:

The actual code all works fine of course, once its imported into GH and run there

so its not a functional problem - but I’m not sure how to properly set things up so I can test it better? Is this a spot where I should be trying to use mock or patch (still learning all that jazz)? Should I be passing the Rhino module as a dependency to my class during initialization in a case like this? Should I be creating an interface to handle all the Rhino, scriptcontext, etc… items I’d like my classes to be able to access?

I’m sure there isn’t a single answer, but I wonder: given the amount of Python you’ve all written, how are you managing the tests in a scenario like this? I notice that you seem to have nicely segregated most all of your RH dependent code into the ladybug_rhino module, which seems great? Do you think I should I be trying to isolate all my Rhino-entangled code into a separate module and just… not… test it? Any thoughts or advice are of course appreciated, as always.

thanks for any thoughts! best,

Hi @edpmay,

Perhaps Hops is something to look at.

Hi @Erikbeeren , that’s an interesting idea - I’ve not had a chance to dive into the Hops tools yet - could be a good option though? That seems good especially if I wanted to actually run the functions during tests - but might be more than I need for testing simple IO and class behavior. Definitely something I’ll check out though.


This has been a major challenge for us as well and I’ll preface by saying that our current solution is far from ideal but it’s been working for us. Our philosophy with the LBT plugin was just to separate the Rhino/Grasshopper dependencies from the pure Python as much as possible, write robust automated tests for the pure Python part, and then have a suite of sample GH files that we use to test the part that relies on Rhino/Grasshopper.

We have eventual hopes of writing some automated tests with pytest for our ladybug_rhino package and you can see a sample here for how you can use the rhinoinside package to run automated tests. At the time that I wrote those tests, I couldn’t write any tests that relied on a Rhino or Grasshopper doc object but I was told that McNeel was working to add “dummy” rhino docs that could be used for rhinoinside. Also, you won’t be able to run such automated tests using online CI systems (like Travis or GitHub Actions) unless you pay McNeel for license usage on a Rhino compute server. But running automated tests locally on your desktop using your desktop Rhino license is still better than manually opening and running Grasshopper files.

In any case, I am sure that people from McNeel would have more suggestions if you asked this question over on their discourse. The could explain some of the Hops workflows much better than I can.

Hi @chris ,

Thanks so much for the detailed reply. That makes a lot of sense and I’ll take a look at the Rhino Inside option. I think that enforcing that clean separation of the Rhino-side modules from the rest makes a ton of sense - I’ll start there and see how much that helps.