Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unittest for invoking a TKinter GUI

Assume a script that initiates a TKinter GUI (e.g., scripts/launch_GUI.py) and is part of a PyPI package (e.g., MyPackage).

.
├── appveyor.yml
├── MyPackage
│   ├── TkOps.py
│   └── CoreFunctions.py
├── README.md
├── requirements.txt
├── scripts
│   ├── launch_CLI.py
│   └── launch_GUI.py
├── setup.py
└── tests
    └── MyPackage_test.py

The launch script is very minimalistic:

#!/usr/bin/env python2
if __name__ == '__main__':
    import sys, os
    sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'MyPackage'))
    import TkOps
    TkOps.start_GUI()

What unittest would you recommend to evaluate if the TKinter GUI is properly started upon initiating launch_GUI.py?

Note: I only wish to evaluate if the launch script does its work and launches the GUI, not if a user can interact with the GUI.

like image 424
Michael G Avatar asked Dec 05 '18 21:12

Michael G


1 Answers

It can be argued that you'd need a functional test rather than a unit test, but let's try this anyway!

While it's possible to test launch script as is via exec(), it's considered bad practice. Let's refactor it:

def main():
    import sys, os
    sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'MyPackage'))
    import TkOps
    TkOps.start_GUI()

if __name__ == '__main__':
    main()

Then let's establish what the unit test should test, for example:

  • sys.path is updated with the correct path for MyPackage
  • start_GUI is called

Then a mokist [1] unit test could look like this:

@mock.patch("sys.path", copy.copy(sys.path))
@mock.patch.dict(sys.modules, TkOps=mock.MagicMock())
def test_main():
    main()
    # minimal validation of sys.path side effect
    # ideally this would check that path bit points to real directory
    assert any(p.endswith("/MyPackage") for p in sys.path)
    # validation of expected call
    assert sys.modules["TkOps"].start_GUI.called

A classical unit test would require an escape hatch in the GUI, for example:

def start_GUI(dry_run=False):
    import tk
    ...
    if not dry_run: tk.foobar()

[1] https://agilewarrior.wordpress.com/2015/04/18/classical-vs-mockist-testing/

like image 163
Dima Tisnek Avatar answered Oct 24 '22 07:10

Dima Tisnek