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 -
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.
While this won't make you design better tests, it can be helpful to write them.
click for code
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))
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
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
and
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.
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.