Files
game-cards/addons/gut/documentation/docs/Global-Lifecycle-Hooks.md
2026-05-29 09:16:10 +08:00

158 lines
5.8 KiB
Markdown

# Global Lifecycle Hooks
## Disclaimer
This page describes how to use existing signals to perform logic at various stages of test execution.
Eventually, a more formal mechanism will be implemented.
See [Issue 762](https://github.com/bitwes/Gut/issues/762) for more details.
## Overview
GUT does not expose "global" function hooks that can be run before each test script or method
-- while GutTest exposes [hooks](Creating-Tests.md#details)
to run code before/after each test method/class,
these must be set on every GutTest instance you want the behavior for.
However, the gut instance accessible in a [Pre-Run Hook](Hooks.md#pre-run-hook)
has the following signals that can be connected to.
These signals were not intended to be used for this purpose,
but it's what we have until a more formal solution exists.
```
signal start_run
signal end_run
## Emitted before every test script instance is created.
## Emitted before [method GutTest.before_all] hook on test is run.
## test_script_obj is an instance of addons/gut/collected_script.gd.
signal start_script(test_script_obj)
## Emitted after every test script is run. Emitted after [method GutTest.after_all] hook on test is run.
signal end_script
## Emitted before every test method is run. Emitted after [method GutTest.before_each] hook on test is run.
## test_name is the string name of the current test about to be started.
signal start_test(test_name)
## Emitted after every test method is run. Emitted after [method GutTest.after_each] hook on test is run.
signal end_test
```
By connecting custom functions to these signals during the Pre-Run Hook,
you can call custom code in hooks across every GutTest instance while defining it only once.
Following is an example of how you would write a pre-run hook script to set up a global setup function.
```gdscript
extends GutHookScript
func run():
gut.start_test.connect(_on_test_started)
func _on_test_started(test_name):
# setup logic run before every test in every test script goes here
```
## Signal Order
It may be important to note the order in which the normal GutTest hooks are run
and the GutMain signals are emitted.
The order of the signals and hooks is as follows:
1. signal `start_script` (once per test script)
1. hook `before_all` (once per test script)
1. hook `before_each` (once per test method)
1. signal `start_test` (once per test method)
1. hook `after_each` (once per test method)
1. signal `end_test` (once per test method)
1. hook `after_all` (once per test script)
1. signal `end_script` (once per test script)
## Example
In this example pre-hook script,
functions are connected to each of the lifecycle hooks
to use the names of test methods and files as a test is run.
```gdscript
extends GutHookScript
const NO_TEST := "__NO_TEST__"
var _current_test_script_object = null
var _current_collected_script = null
var _current_test_name := NO_TEST
func run():
gut.start_run.connect(_on_run_started)
gut.start_script.connect(_on_script_started)
gut.start_test.connect(_on_test_started)
gut.end_test.connect(_on_test_ended)
gut.end_script.connect(_on_script_ended)
gut.end_run.connect(_on_run_ended)
# Do pre-run stuff here
# This might be redundant, and it might have already been emitted by the time
# this hook is called. I wanted it in here for illustration purposes.
func _on_run_started():
pass
# This is passed an instance of res://addons/gut/collected_script.gd. It is not
# the instance of the script that will be run. You can get to the script object
# using `load_script`, but you can't get to the actual instance that will be
# run.
func _on_script_started(collected_script):
_current_collected_script = collected_script
# The GutTest script, not the instance.
_current_test_script_object = collected_script.load_script()
# _current_collected_script.get_full_name() returns the path of the file
# that contains the test
# This is just the name of the test method being ran.
func _on_test_started(test_name):
_current_test_name = test_name
func _on_test_ended():
# example of inspecing the test that ended if you wanted to.
var failed = _current_collected_script.get_test_named(_current_test_name).is_failing()
if (failed):
print(_current_test_name + " failed")
else:
print(_current_test_name + " passed")
_current_test_name = NO_TEST
func _on_script_ended():
#example of inspecing the script that ended if you wanted to
if(!_current_collected_script.was_skipped):
if(_current_collected_script.get_fail_count() > 0):
print("The script ", _current_collected_script.get_full_name(), " failed")
else:
print("The script ", _current_collected_script.get_full_name(), " passed")
_current_collected_script = null
_current_test_script_object = null
# I'm not sure if this is called before or after the post-run hook. This can't
# do everything a post-run hook can do, but it might be enough for this.
func _on_run_ended():
pass
# Do "after_every" things here.
```
## Related Material
The `start_script` signal contains an object `test_script_obj` which is an instance of
the [collected_script.gd](https://github.com/bitwes/Gut/blob/main/addons/gut/collected_script.gd) class,
which may be found at `addons/gut/collected_script.gd`.
This is NOT the instance of the test script that is actually being run.
This class is not intended for public consumption, so use this value at your own risk.
## Improvements
The GutMain signals were not originally intended to be used for this purpose
(the astute may observe that the ordering of signals and hooks is not particularly orderly).
Plans for a new system of hooks created for this purpose
were considered [here](https://github.com/bitwes/Gut/pull/804#issuecomment-3929695342),
but further input and consideration of the matter would be appreciated
-- if you've got an idea, open an issue about it!