# Help designing unit tests?

**URL:** <https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544>\
**Category:** Ignition\
**Tags:** ignition81\
**Created:** [January 12, 2023, 4:58pm UTC](https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544 "2023-01-12T16:58:21Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [January 12, 2023, 4:58pm UTC](https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544/1 "2023-01-12T16:58:21Z")

</div>

How do people write unit tests with Ignition? I have finally, FINALLY, refactored this project and completely decoupled the business logic from the GUI and so the first thing I want to do is write some unit tests so anytime we make a change, we can check all the usual processes and make sure nothing unexpectedly broke, move away from the "production is the QA environment" paradigm we have going on now.

This app is 99% CRUD routes for various db forms. So right now I am writing functions that are testing the creation/update/deleting of records and checking I get the appropriate result.

Here's my first attempt -

```auto
def customerTests():
	# Create a new customer, customer name TestCustomer1000
	newCustomer = {...}
	newCreateResult = forms2.Forms.Customer.create(newCustomer)
	print 'creating customer'
	assert newCreateResult.success == True
	print 'passed'
	# Update the new customer to TestCustomer1001
	updateCustomerData = {...}
	# newCreateResult.result is the idx of the newly created one
	customerUpdateResult = forms2.Forms.Customer.update(updateCustomerData, newCustomer, newCreateResult.result)
	print 'updating..'
	assert customerUpdateResult.success == True
	print 'passed'
	# Try to update customer name to existing Customer Name- this should fail
	updateToExistingCustomerName = {...}
	shouldFailUpdate = forms2.Forms.Customer.update(updateToMesser, updateCustomerData, newCreateResult.result)
	print 'trying fail update'
	assert shouldFailUpdate.success == False
	print 'passed'
	# Delete the new customer
	newDeleteResult = forms2.Forms.Customer.delete(newCreateResult.result)
	print 'deleting..'
	assert newDeleteResult.success == True
	print 'passed'

```

And then I would call this and just make sure no assertion error comes up. I am wondering if this is how most people do it or if there is other better methods for writing unit tests specifically within the context of Ignition.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [January 12, 2023, 6:16pm UTC](https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544/2 "2023-01-12T18:16:23Z")

</div>

While this won't make you design better tests, it can be helpful to write them.

> **click for code**
>
> ```python
> import ast
> 
> """
> Basic test helper
> 
> Import test in your file: from utils.test import test
> 
> Decorate your functions to be tested:
> @test
> def test_foo():
> foo = "foo"
> bar = get_bar()
> assert foo == bar, "expected {}, got {}".format(foo, bar)
> 
> Call the function individually, or use run_tests() to run all the marked functions in modules:
> run_tests(module_1, module_b)
> """
> 
> class TestFinder(ast.NodeVisitor):
> def visit_Module(self, node):
> self.test_funcs = []
> self.generic_visit(node)
> 
> def visit_FunctionDef(self, node):
> if "test" in (deco.id for deco in node.decorator_list):
> self.test_funcs.append(node.name)
> 	
> def get_testfuncs(self):
> return self.test_funcs
> 
> def test(func):
> """
> Decorator to mark functions to be tested.
> 	
> Simply import the decorator (from Tests.utils import test) and add @test before the function
> """
> 
> def wrapper():
> try:
> func()
> except AssertionError as e:
> message = "failed ({})".format(e)
> else:
> message = "passed"
> return message
> return wrapper
> 
> def run_tests(*modules):
> """
> Run all the functions marked as tests (with the test decorator) in every packages passed as parameter.
> """
> for m in modules:
> print("{}\ntesting functions in {}...".format("-"*20, m.name))
> 
> tree = ast.parse(m.code)
> f = TestFinder()
> f.visit(tree)
> funcs = f.get_testfuncs()
> 
> if not funcs:
> print("No functions marked for testing found in {}".format(m.name))
> for f in funcs:
> try:
> message = getattr(m, f)()
> print("{:.<40}{}".format(f, message))
> except BaseException as e:
> print(repr(e))
> 
> ```

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/2/62eba7085bfe054b085ccd32178888c8521d206d.png)

Write your tests as simple functions, decorate them with `@test`, then run `run_test(module1, module2, etc)` wherever you want (usually the script console though).  
I haven't used it in a while, and if I remember correctly there were a few quirks. I may or may not have fixed them.

edit: forgot to format code :X

note: You can use `run_tests()` to run all the tests in the modules passed as arguments, or just call a function that's decorated with `@test` directly

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [January 12, 2023, 6:18pm UTC](https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544/3 "2023-01-12T18:18:18Z")

</div>

Something went wrong with your code formatting there.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [January 12, 2023, 6:20pm UTC](https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544/4 "2023-01-12T18:20:19Z")

</div>

What went wrong with the formatting was that there was no formatting 😃

---

<div class="post-metadata">

**Author:** ![PFollansbee](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pfollansbee/32/46440_2.png) [@PFollansbee](https://forum.inductiveautomation.com/u/PFollansbee)\
**Post date:** [June 23, 2024, 6:43am UTC](https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544/5 "2024-06-23T06:43:38Z")

</div>

I've been trying to find a way for this to run in all the loaded modules in the Project Library without having to type the module names into the test each time I run it. Is there a nice way to do this?

I've tried some things along the lines of

> [@Script File List](https://forum.inductiveautomation.com/t/script-file-list/17799):
>
> Hello, From within a script, I would like to get a list of all the modules in the project. I can get all the imported modules using this: import sys for name, module in sys.modules.items(): if 'project' in name: print name but I would like to get a list of all modules (or filenames), even if they haven’t been imported yet. As they haven’t been imported, I may need to query the underlying structure where the modules are stored, but I have no idea of how to do this? Any ideas?

and

> [@Script to list all library script functions](https://forum.inductiveautomation.com/t/script-to-list-all-library-script-functions/64639/6):
>
> A function object from a script module has substantial information attached. Try this (and variations) in your script console: def myFunc(arg1, \*args): pass for x in dir(myFunc): a = getattr(myFunc, x) print x, type(a), repr(a) Note the stuff at the end: \_\_call\_\_ \<type 'builtin\_function\_or\_method'\> \<built-in method \_\_call\_\_ of function object at 0x2\> \_\_class\_\_ \<type 'type'\> \<type 'function'\> \_\_closure\_\_ \<type 'NoneType'\> None \_\_code\_\_ \<type 'tablecode'\> \<code object myFunc at 0x3, file "\<…

but it seemed like there should be an easier way. I'm sure that with enough digging through, you could determine which modules are being loaded through the Project Library, which are being imported, and which are automatically loaded, but this seems like an unnecessary amount of work for something that should be fairly trivial.

Apologies if this should be in a separate thread, but it seemed relevant to this thread and anyone who might stumble on it in the future.

Thanks!

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [June 23, 2024, 7:04am UTC](https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544/6 "2024-06-23T07:04:21Z")

</div>

I can't investigate this now, but worst case scenario you could call an external script that runs `ls -R` or something similar to get the list of scripts filepaths, then process them into a list that can be used by the function. I guess.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [June 23, 2024, 11:54am UTC](https://forum.inductiveautomation.com/t/help-designing-unit-tests/68544/7 "2024-06-23T11:54:26Z")

</div>

You can enumerate the entries in `sys.modules`, using `type()` to pick out the ones that are Ignition modules and packages. Recurse into the latter.
