Test suite fixtures#
You will learn to
create test suite fixtures with Fortuno.
Note
This section assumes, that you already have a working project with Fortuno unit tests. You can set up one by using the Fortran project cookiecutter template or following the instructions in the Tutorials section.
Sometimes initialization and finalization must not be executed for each test separately, but only once, before any tests from a group of tests are run or after all tests of the group had been carried out. A typical scenario would be, if you have to load or generate large amount of data before a certain group of tests accessing that data can be executed.
Those cases can be handled in Fortuno using suite fixtures. Test suites in Fortuno have basically two functions:
They serve as containers for building hierarchical test inftrastructure, each suite containing tests and further test suites.
They can be extended to contain customized data and customized set-up and tear-down procedures. The set-up procedure of a suite is warranted to be executed before any tests in the suite are run, or before the set-up procedure of any of the contained suites is invoked. The tear-down procedure is warranted to be executed after all tests in the test suite had been carried out and the tear-down procedures of all contained suites had been invoked.
For demonstration purposes we will modify the example from the previous section: We will open a file before a group of test is executed, and close it, once the tests had been carried out. The information about the name of the file and the file unit should be passed to the tests. As demonstrated below, there are two ways of passing data to individual tests of a suite: either by using global module variables or using type components introduced by type extension. Former is considerably simpler, but prone to accidental errors, especially when using with complex test hierarchies. Latter needs somewhat more coding, but is is modular and robust.
Storing suite data in module variables#
1!> Module containing unit tests
2module test_mylib
3 use random_file, only : random_file_name
4 use fortuno_serial, only : check => serial_check, check_failed => serial_check_failed,&
5 & serial_suite_base, test => serial_case_item, test_item, test_list
6 implicit none
7
8 type :: test_env
9 character(:), allocatable :: filename
10 integer :: unit = -1
11 contains
12 final :: final_test_env
13 end type test_env
14
15
16 ! Fixtured test suite
17 type, extends(serial_suite_base) :: tempfile_test_suite
18 contains
19 procedure :: set_up => tempfile_test_suite_set_up
20 procedure :: tear_down => tempfile_test_suite_tear_down
21 end type tempfile_test_suite
22
23
24 ! The global test environment instance.
25 type(test_env), allocatable :: global_env
26
27contains
28
29 !> Returns the list of tests in this module
30 function tests()
31 type(test_list) :: tests
32
33 tests = test_list([&
34 tempfile_suite("tempfile_demo", test_list([&
35 test("tempfile_1", test_tempfile_1),&
36 test("tempfile_2", test_tempfile_2)&
37 ]))&
38 ])
39
40 end function tests
41
42
43 !> Intializes the test environment (opens temporary file)
44 !!
45 !! Note: A very simple-minded implementation, for demonstration purposes only.
46 !!
47 subroutine init_test_env(this)
48
49 !> Test environment containing file name and file unit.
50 !!
51 !! Note: if the opening of the temporary file fails for any reasons the unit remains at its
52 !! default value (-1) and the file name string will be unallocated.
53 type(test_env), intent(out) :: this
54
55 integer :: iostat
56
57 this%filename = random_file_name("tmp-", ".txt", 10)
58 !open(newunit=this%unit, file=this%filename, action="readwrite", iostat=iostat)
59
60 end subroutine init_test_env
61
62
63 !> Finalizes the test environment (closes temporary file)
64 subroutine final_test_env(this)
65 type(test_env), intent(inout) :: this
66
67 if (this%unit /= -1) then
68 ! You might want to add status="delete" to remove the temporary file
69 close(this%unit)
70 end if
71
72 end subroutine final_test_env
73
74
75 !> Returns a tempfile_suite instance wrapped as test_item to be used in an array constructors.
76 function tempfile_suite(name, tests) result(testitem)
77 character(*), intent(in) :: name
78 type(test_list), intent(in) :: tests
79 type(test_item) :: testitem
80
81 testitem = test_item(tempfile_test_suite(name=name, tests=tests))
82
83 end function tempfile_suite
84
85
86 !> Initializes the test suite.
87 subroutine tempfile_test_suite_set_up(this)
88 class(tempfile_test_suite), intent(inout) :: this
89
90 allocate(global_env)
91 call init_test_env(global_env)
92 call check(global_env%unit /= -1, msg="Failed to open tempfile")
93 if (check_failed()) deallocate(global_env)
94
95 end subroutine tempfile_test_suite_set_up
96
97
98 !> Finalizes the test suite.
99 subroutine tempfile_test_suite_tear_down(this)
100 class(tempfile_test_suite), intent(inout) :: this
101
102 if (allocated(global_env)) deallocate(global_env)
103
104 end subroutine tempfile_test_suite_tear_down
105
106
107 subroutine test_tempfile_1()
108 write(global_env%unit, "(a)") "Hello from test_tempfile_1"
109 end subroutine test_tempfile_1
110
111
112 subroutine test_tempfile_2()
113 write(global_env%unit, "(a)") "Hello from test_tempfile_2"
114 end subroutine test_tempfile_2
115
116end module test_mylib
117
118
119!> Test app driving Fortuno unit tests.
120program testapp
121 use test_mylib, only : tests
122 use fortuno_serial, only : execute_serial_cmd_app
123 implicit none
124
125 call execute_serial_cmd_app(tests())
126
127end program testapp
Storing data within test suites#
1!> Module containing unit tests
2module test_mylib
3 use random_file, only : random_file_name
4 use fortuno_serial, only : serial_case_base, check => serial_check,&
5 & check_failed => serial_check_failed, scope_pointers => serial_scope_pointers,&
6 & serial_suite_base, test_item, test_list, test_ptr_item
7 implicit none
8
9
10 !> Environment representing the test fixture.
11 type :: test_env
12 character(:), allocatable :: filename
13 integer :: unit = -1
14 contains
15 final :: final_test_env
16 end type test_env
17
18
19 !> Extended test suite containing customized data, initializer and finalizer.
20 type, extends(serial_suite_base) :: tempfile_test_suite
21 type(test_env), allocatable :: env
22 contains
23 procedure :: set_up => tempfile_test_suite_set_up
24 procedure :: tear_down => tempfile_test_suite_tear_down
25 end type tempfile_test_suite
26
27
28 !> Extended test case running a test procedure with one argument
29 type, extends(serial_case_base) :: tempfile_test_case
30 procedure(test_tempfile_1), pointer, nopass :: proc
31 contains
32 procedure :: run => tempfile_test_case_run
33 end type tempfile_test_case
34
35contains
36
37 !!
38 !! Tests
39 !!
40
41 !> Returns the list of tests exported from this module.
42 function tests()
43 type(test_list) :: tests
44
45 tests = test_list([&
46 tempfile_suite("tempfile_demo", test_list([&
47 tempfile_test("tempfile_1", test_tempfile_1),&
48 tempfile_test("tempfile_2", test_tempfile_2)&
49 ]))&
50 ])
51
52 end function tests
53
54
55 !> Test 1
56 subroutine test_tempfile_1(env)
57 type(test_env), intent(in) :: env
58 write(env%unit, "(a)") "Hello from test_tempfile_1"
59 end subroutine test_tempfile_1
60
61
62 !> Test 2
63 subroutine test_tempfile_2(env)
64 type(test_env), intent(in) :: env
65 write(env%unit, "(a)") "Hello from test_tempfile_2"
66 end subroutine test_tempfile_2
67
68
69 !!
70 !! Fixture infrastructure
71 !!
72
73 !> Intializes the test environment (opens temporary file).
74 subroutine init_test_env(this)
75 type(test_env), intent(out) :: this
76
77 integer :: iostat
78
79 this%filename = random_file_name("tmp-", ".txt", 10)
80 open(newunit=this%unit, file=this%filename, action="readwrite", iostat=iostat)
81
82 end subroutine init_test_env
83
84
85 !> Finalizes the test environment (closes temporary file).
86 subroutine final_test_env(this)
87 type(test_env), intent(inout) :: this
88
89 if (this%unit /= -1) then
90 close(this%unit)
91 end if
92
93 end subroutine final_test_env
94
95
96 !> Wraps a tempfile_test_suite instance as test_item to be used in an array constructors.
97 function tempfile_suite(name, tests) result(testitem)
98 character(*), intent(in) :: name
99 type(test_list), intent(in) :: tests
100 type(test_item) :: testitem
101
102 testitem = test_item(tempfile_test_suite(name=name, tests=tests))
103
104 end function tempfile_suite
105
106
107 !> Initializes the test suite.
108 subroutine tempfile_test_suite_set_up(this)
109 class(tempfile_test_suite), intent(inout) :: this
110
111 allocate(this%env)
112 call init_test_env(this%env)
113 call check(this%env%unit /= -1, msg="Failed to open tempfile")
114 if (check_failed()) deallocate(this%env)
115
116 end subroutine tempfile_test_suite_set_up
117
118
119 !> Finalizes the test suite.
120 subroutine tempfile_test_suite_tear_down(this)
121 class(tempfile_test_suite), intent(inout) :: this
122
123 ! Explicit deallocation to trigger the finalizer of the test environment.
124 if (allocated(this%env)) deallocate(this%env)
125
126 end subroutine tempfile_test_suite_tear_down
127
128
129 !> Wraps a tempfile_test_case instance as test_item to be used in an array constructors.
130 function tempfile_test(name, proc) result(testitem)
131 character(*), intent(in) :: name
132 procedure(test_tempfile_1) :: proc
133 type(test_item) :: testitem
134
135 testitem = test_item(tempfile_test_case(name=name, proc=proc))
136
137 end function tempfile_test
138
139
140 !> Runs test procedure with data from test suite.
141 subroutine tempfile_test_case_run(this)
142 class(tempfile_test_case), intent(in) :: this
143
144 type(test_ptr_item), allocatable :: scopeptrs(:)
145 type(test_env), pointer :: test_env_ptr
146
147 ! Get pointers to hosting scopes
148 ! scopeptrs(1): current scope - tempfile_test_case instance
149 ! scopeptrs(2): first enclosing scope - tempfile_test_suite instance
150 scopeptrs = scope_pointers()
151 call check(size(scopeptrs) >= 2)
152 if (check_failed()) return
153
154 ! Create pointer to data stored in the test suite.
155 test_env_ptr => null()
156 select type (suite => scopeptrs(2)%item)
157 type is (tempfile_test_suite)
158 test_env_ptr => suite%env
159 end select
160 call check(associated(test_env_ptr))
161 if (check_failed()) return
162
163 ! Call test routine with data from test suite.
164 call this%proc(test_env_ptr)
165
166 end subroutine tempfile_test_case_run
167
168end module test_mylib
169
170
171!> Test app driving Fortuno unit tests.
172program testapp
173 use test_mylib, only : tests
174 use fortuno_serial, only : execute_serial_cmd_app
175 implicit none
176
177 call execute_serial_cmd_app(tests())
178
179end program testapp