欢迎使用 Unittest2doc¶
English | 中文
Unittest2doc 是一个将 Python 单元测试代码转换为文档的工具。本文档中的单元测试部分就是是使用 Unittest2doc 生成的。
百闻不如一见,我们直接通过本文档的例子来学习 Unittest2doc 的用法。
项目地址为: https://github.com/Fmajor/unittest2doc
项目的文件结构为:
unittest2doc/ # 项目根目录
src/ # 源代码目录
unittest2doc/ # 源代码的包目录
__init__.py
unittest2doc.py
...
sphinx-docs/ # 文档目录
source/ # 文档源文件目录
conf.py # 配置文件, 在这里导入要测试的包
index.rst
unittests/ # Unittest2doc 会在这里生成rst文件
src/ # sphinx通过autosummary功能生成的API文档
...
build/ # 文档构建目录, 运行make html 后会在这里生成本文档
tests/ # 测试目录, 我们使用Unittest2doc的地方
test_unittest2doc.py # 测试文件, 其中包括了一个Unittest2doc类, 和其运行示例
test_pformat_json.py # 我们这里测试了一个结构化输出json的函数
# 对于一个结构化输出函数,最好的测试方法是展示出其结果并保存为文档
pyproject.toml
README.rst
在项目根目录使用
make unittest
实际执行
python -m unittest discover -s tests -p '*.py' -b
这就是一般的单元测试,我们只关心测试结果,不关心测试过程
在项目根目录使用
make generate-unittest-docs
实际执行
unittest2doc -s tests -p '*.py'
, 他会直接运行所有满足条件的测试文件我们每一个测试文件都可以单独执行, 其中的运行参数使得他们可以在运行的时候生成RST格式的sphinx文档, 并且保存到
/sphinx-docs/source/unittests/
目录下, 最终展现在我们的文档中
这里我们直接展示测试文件
tests/test_unittest2doc.py
¶
其生成的文档为: unittest2doc.unittest2doc.Unittest2Doc
1import unittest
2import unittest2doc
3from unittest2doc import Unittest2Doc, docpprint
4import time
5import json
6import yaml
7import textwrap
8from pathlib import Path
9
10if 'test Unittest2Doc' and 1:
11 class Test(unittest.TestCase):
12 ''' docstring of class, new title
13 -----------------------------
14
15 * Sphinx have already use "=" as title marker
16 * to form a subtitle, we should use ``-`` as the title marker
17
18 '''
19 def test(s):
20 a = 1
21 b = 2
22 print("# this is a normal unittest.TestCase")
23 print("# we can use all its assertion methods")
24 s.assertEqual(a, 1)
25 s.assertNotEqual(a, b)
26 s.assertIs(a, 1)
27 s.assertIsNot(a, b)
28 s.assertIsNone(None)
29 s.assertIsNotNone(a)
30 s.assertTrue(True)
31 s.assertFalse(False)
32
33 def rst_test_doc(s):
34 ''' group of tests
35 --------------
36
37 function startswith rst will only provide its docstring to generate docs
38
39 we set the ``title_marker`` config to '^', and following tests will be grouped under this title
40
41 because rst title markers have these priorities:
42
43 * ``=`` (already used by upper Sphinx structure)
44 * ``-``
45 * ``^``
46
47 '''
48 unittest2doc.update_config(s, title_marker='^')
49
50 #@unittest2doc.stop_after
51 #@unittest2doc.only
52 #@unittest2doc.stop
53 def test_show_variable(s):
54 """ the title marker is `^` (set in previous function rst_test_doc)
55
56 here we test the ``Unittest2Doc.v`` method, to display variables
57 """
58 a = 1
59 b = '2'
60 c = {
61 'normal': 'some data',
62 'secret': 'should be masked',
63 'subsecret': {
64 'good': 1,
65 'bad': 0,
66 'sub': {
67 'good': 1,
68 'bad': 0,
69 }
70 }
71 }
72 d = [1,2,3]
73 unittest2doc.v(['a', 'b', 'c', 'd'], locals(), globals(), mask=[
74 'c.secret',
75 'c.subsecret.bad',
76 'c.subsecret.sub.bad',
77 ])
78 def test_add_more_doc_0(s):
79 """ {"open_input":false}
80
81 here we close the input block by json setting at first line of docstring
82
83 the title marker is still `^` (set in previous function rst_test_doc)
84
85 """
86 pass
87 def test_add_more_doc_1(s):
88 """ {"open_output": false}
89
90 here we close the output block by json setting at first line of docstring
91
92 the title marker is still `^` (set in previous function rst_test_doc)
93 """
94 print('here we close the output ')
95 def test_add_more_doc_2(s):
96 """ after this, set title level to '-', and the current group is finished
97 """
98 unittest2doc.update_config(s, title_marker='-') # set title level to '-' after this test
99 def test_add_more_doc_3(s):
100 """ this test back to top level (because title_marker is set to '-' at last function)
101 """
102 pass
103 def test_title_marker_for_single_test(s):
104 """ {"title_marker": "^"}
105
106 title marker set by above json is only effective in this function
107
108 """
109 print("# the title_marker ^ is only used in this function, and will not affect other tests")
110 print("# after this test, the title_marker is back to previous '-'")
111 def test_output_as_json(s):
112 """ {"output_highlight": "json"}
113
114 the output is highlighted as ``json``
115
116 the title marker here and below are all the default ``-``
117 """
118 print(json.dumps({"1":1, "2":"2", "3": 3.0, "4":4, "a":[{"1":1, "2":2}, {"3":3, "4":4}]}, indent=2))
119 def test_output_as_yaml(s):
120 """ {"output_highlight": "yaml"}
121
122 the output is highlighted as ``yaml``
123 """
124 # pprint({1:1, '2':'2', '3': 3.0, '4':4, 'a':[{1:1, 2:2}, {3:3, 4:4}]}, expand_all=True, indent_guides=False)
125 docpprint({1:1, '2':'2', '3': 3.0, '4':4, 'a':[{1:1, 2:2}, {3:3, 4:4}]})
126 def test_output_as_python(s):
127 """ {"output_highlight": "python"}
128 """
129 # print(pformat_json({1:1, '2':'2', '3': 3.0, '4':4, 'a':[{1:1, 2:2}, {3:3, 4:4}]}))
130 docpprint({1:1, '2':'2', '3': 3.0, '4':4, 'a':[{1:1, 2:2}, {3:3, 4:4}]})
131 from datetime import datetime
132 from collections import OrderedDict
133 d = [
134 {
135 'system_tags': [
136 OrderedDict([('a', 1), ('b', 2), ('c', 3)]),
137 ],
138 'date': datetime.now(),
139 }
140 ]
141 docpprint(d)
142 @unittest2doc.skip
143 def test_skipped(s):
144 raise Exception('this function should be skipped and we should not get this Exception')
145
146 @unittest2doc.expected_failure
147 def test_with_exception(s):
148 """ {"output_processors": ["no_home_folder"]}
149
150 test with exception, the output string will be processed by ``no_home_folder`` processor defined below
151 """
152 raise Exception('expected exception')
153
154 def test_add_foldable_output(s):
155 """ add extra foldable text at end of the doc page
156 """
157 print("# add some output")
158 unittest2doc.add_foldable_output(
159 s, # must pass self into add_foldable_output
160 name='some python code',
161 highlight='python',
162 output=textwrap.dedent('''
163 # some code ...
164 def func(*args, **kwargs):
165 pass
166 '''
167 )
168 )
169
170 # some nested data
171 data = {
172 'a': 1,
173 'b': 2,
174 'c': 3,
175 'd': {
176 'a': 1,
177 'b': 2,
178 'c': 3,
179 }
180 }
181 print("# add some output")
182
183 unittest2doc.add_foldable_output(
184 s, # must pass self into add_foldable_output
185 name='some yaml data',
186 highlight='yaml',
187 output=yaml.dump(data, indent=2)
188 )
189 print("# add some output")
190
191 def test_last(s):
192 """ we use decorator above, make sure that this test is the last one """
193 pass
194
195 class Test2(unittest.TestCase):
196 ''' docstring of class
197 ------------------
198
199 in this class, we test the decorator ``@Unittest2Doc.only``
200
201 '''
202 def setUp(s):
203 print("# this setup function is always called at beginning")
204 def tearDown(s):
205 print("# this tearDown function is always called at end")
206
207 @unittest2doc.only
208 def test_only_1(s):
209 """ when you use ``Unittest2Doc.generate_docs()``, this test will be executed
210
211 Note that if you use ``python -m unittest ...`` framework, all tests will be executed
212
213 Thus the `only` decorator should only be used during your development and testing,
214 e.g., you just want to test one function and want to skip others for speed
215
216 """
217 pass
218
219 def test_other(s):
220 """ when you use ``Unittest2Doc.generate_docs()``, this test will be skipped because of not @unittest2doc.only decorator
221
222 it will be executed anyway if you use ``python -m unittest ...`` framework
223
224 """
225 pass
226
227 @unittest2doc.only
228 def test_only_2(s):
229 """ when you use ``Unittest2Doc.generate_docs()``, this test will be executed
230 """
231 pass
232
233 class Test3(unittest.TestCase):
234 """ docstring of class
235 ------------------
236
237 in this class, we test the decorator ``@Unittest2Doc.stop``
238
239 """
240 def setUp(s):
241 print("# this setup function is always called at beginning")
242 def tearDown(s):
243 print("# this tearDown function is always called at end")
244
245 def test_3(s):
246 """ this should be the only test when you use ``Unittest2Doc.generate_docs()``
247
248 we have a @unittest2doc.stop decorator at next test
249
250 """
251 pass
252
253 @unittest2doc.stop
254 def test_2(s):
255 """ stop before this test when you use ``Unittest2Doc.generate_docs()``
256
257 Note that if you use ``python -m unittest ...`` framework, all tests will be executed
258
259 Thus the `stop` decorator should only be used during your development and testing,
260 e.g., you just want to test above function and want to skip others for speed
261
262 """
263 pass
264
265 def test_1(s):
266 pass
267
268 class Test4(unittest.TestCase):
269 """ docstring of class
270 ------------------
271
272 in this class, we test the decorator ``@Unittest2Doc.stop_after``
273
274 """
275 def setUp(s):
276 print("# this setup function is always called at beginning")
277 def tearDown(s):
278 print("# this tearDown function is always called at end")
279
280 def test_3(s):
281 """ this should be the executed when you use ``Unittest2Doc.generate_docs()`` """
282 pass
283
284 @unittest2doc.stop_after
285 def test_2(s):
286 """ stop after this test when you use ``Unittest2Doc.generate_docs()``
287
288 """
289 pass
290
291 def test_1(s):
292 """ this should be skipped when you use ``Unittest2Doc.generate_docs()``
293
294 Note that if you use ``python -m unittest ...`` framework, all tests will be executed
295
296 Thus the `stop_after` decorator should only be used during your development and testing,
297 e.g., you just want to test above function and want to skip others for speed
298
299 """
300 pass
301
302 class Test5(unittest.TestCase):
303 """ docstring of class
304 ------------------
305
306 in this class, we test the unittest decorator (not unittest2doc decorator)
307
308 """
309 def setUp(s):
310 print("# this setup function is always called at beginning")
311 def tearDown(s):
312 print("# this tearDown function is always called at end")
313
314 @unittest.skip
315 def test_skipped(s):
316 raise Exception('this function should be skipped and we should not get this Exception')
317
318 @unittest.expectedFailure
319 def test_with_exception(s):
320 raise Exception('expected exception')
321
322
323if __name__ == "__main__":
324 def no_home_folder(output):
325 # filter out `${HOME}/*/unittest2doc` to `${PROJECT_ROOT}/unittest2doc`
326 import os
327 home = os.environ.get('HOME')
328 import re
329 pattern = r"{home}/(?:[^/]+/)*?unittest2doc/".format(home=home)
330 replacement = r"${PROJECT_ROOT}/unittest2doc/"
331 output = re.sub(pattern, replacement, output)
332 return output
333 t = Unittest2Doc(
334 testcase=Test(),
335 name='unittest2doc.unittest2doc.Unittest2Doc.basic',
336 ref=':class:`unittest2doc.unittest2doc.Unittest2Doc`',
337 doc_root=Path(__file__).absolute().parent.parent / 'sphinx-docs/source/unittests',
338 output_processors=dict(
339 no_home_folder=no_home_folder,
340 )
341 )
342 t.generate_docs()
343
344 t2 = Unittest2Doc(
345 testcase=Test2(),
346 name='unittest2doc.unittest2doc.Unittest2Doc.test_decorator_only',
347 ref=':class:`unittest2doc.unittest2doc.Unittest2Doc`',
348 doc_root=Path(__file__).absolute().parent.parent / 'sphinx-docs/source/unittests',
349 )
350 t2.generate_docs()
351
352 t3 = Unittest2Doc(
353 testcase=Test3(),
354 name='unittest2doc.unittest2doc.Unittest2Doc.test_decorator_stop',
355 ref=':class:`unittest2doc.unittest2doc.Unittest2Doc`',
356 doc_root=Path(__file__).absolute().parent.parent / 'sphinx-docs/source/unittests',
357 )
358 t3.generate_docs()
359
360 t4 = Unittest2Doc(
361 testcase=Test4(),
362 name='unittest2doc.unittest2doc.Unittest2Doc.test_decorator_stop_after',
363 ref=':class:`unittest2doc.unittest2doc.Unittest2Doc`',
364 doc_root=Path(__file__).absolute().parent.parent / 'sphinx-docs/source/unittests',
365 )
366 t4.generate_docs()
367
368 t5 = Unittest2Doc(
369 testcase=Test5(),
370 name='unittest2doc.unittest2doc.Unittest2Doc.test_unittest_decorator',
371 ref=':class:`unittest2doc.unittest2doc.Unittest2Doc`',
372 doc_root=Path(__file__).absolute().parent.parent / 'sphinx-docs/source/unittests',
373 )
374 t5.generate_docs()
tests/test_pformat_json.py
¶
其生成的文档为: unittest2doc.formatter
1import unittest
2import sys
3import os
4import json
5from pathlib import Path
6import unittest2doc
7from unittest2doc import Unittest2Doc, FLog
8from unittest2doc.formatter.json_formatter import pformat_json
9
10class TestJsonFormatter(unittest.TestCase):
11 """ Test cases for json_formatter module's pformat_json function
12 """
13
14 def setUp(self):
15 """ here we also test the FLog class, it is a helper class to print function inputs and outputs """
16 self.flog = FLog(do_print=True, output_suffix='\n', input_suffix=' # ===>')
17 self.frun = self.flog.frun
18
19 def test_basic_format(self):
20 """ {"output_highlight": "python"}
21 """
22 # Test basic dictionary formatting
23 data = {"name": "John", "age": 30, "city": "New York"}
24 # call pformat_json(data) and print the result
25 self.frun(pformat_json, data)
26
27 # Test basic list formatting
28 data = [1, 2, 3, "four", 5.0]
29 # call pformat_json(data) and print the result
30 self.frun(pformat_json, data)
31
32 # Test simple value
33 data = "simple string"
34 # call pformat_json(data) and print the result
35 self.frun(pformat_json, data)
36
37 def test_nested_structures(self):
38 """ {"output_highlight": "python"}
39 """
40 # Test nested dictionary and list
41 data = {
42 "person": {
43 "name": "Alice",
44 "details": {
45 "age": 28,
46 "occupation": "Engineer"
47 }
48 },
49 "hobbies": ["reading", "hiking", {"sport": "tennis"}]
50 }
51 self.frun(pformat_json, data)
52
53 def test_dict_title_comment(self):
54 """ {"output_highlight": "python"}
55 """
56 # Test using string comment for dict title
57 data = {"key1": "value1", "key2": "value2"}
58 self.frun(pformat_json, data, comments="Dictionary Title")
59
60 # Test using __dtitle__ special key
61 data = {"key1": "value1", "key2": "value2"}
62 comments = {"__dtitle__": "Dictionary Title With Special Key"}
63 self.frun(pformat_json, data, comments=comments)
64
65 # Test multi-line dict title
66 comments = {"__dtitle__": "First Line\nSecond Line\nThird Line"}
67 self.frun(pformat_json, data, comments=comments)
68
69 def test_list_title_comment(self):
70 """ {"output_highlight": "python"}
71 """
72 # Test using string comment for list title
73 data = ["item1", "item2", "item3"]
74 self.frun(pformat_json, data, comments="List Title")
75
76 # Test using __ltitle__ special key
77 comments = {"__ltitle__": "List Title With Special Key"}
78 self.frun(pformat_json, data, comments=comments)
79
80 # Test list prefix and suffix comments
81 comments = {
82 "__ltitle__": "List With Prefix and Suffix",
83 "__lprefix__": ["Prefix Line 1", "Prefix Line 2"],
84 "__lsuffix__": ["Suffix Line 1", "Suffix Line 2"]
85 }
86 self.frun(pformat_json, data, comments=comments)
87
88 def test_specific_element_comments(self):
89 """ {"output_highlight": "python"}
90 """
91 # Test comments for specific dict keys
92 data = {"name": "Bob", "age": 45, "city": "Boston"}
93 comments = {
94 "name": "Person's name",
95 "age": "Person's age in years",
96 "city": "City of residence"
97 }
98 self.frun(pformat_json, data, comments=comments)
99
100 # Test comments for specific list indices
101 data = ["Python", "Java", "JavaScript", "C++"]
102 comments = {
103 0: "My favorite language",
104 2: "Web development language"
105 }
106 self.frun(pformat_json, data, comments=comments)
107
108 def test_callable_comments(self):
109 """ {"output_highlight": "python"}
110 """
111 # Test callable comments for dict
112 data = {"price": 129.99, "quantity": 5, "discount": 0.15}
113
114 def calc_total(key, value):
115 if key == "price":
116 return f"Base price: ${value}"
117 elif key == "quantity":
118 return f"Order quantity of {value} units"
119 elif key == "discount":
120 return f"Discount rate of {int(value*100)}%"
121 return ""
122
123 comments = {
124 "price": calc_total,
125 "quantity": calc_total,
126 "discount": calc_total
127 }
128 self.frun(pformat_json, data, comments=comments)
129
130 def test_compact_mode(self):
131 """ {"output_highlight": "python"}
132 """
133 # Test compact mode for dict
134 data = {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6}
135 comments = {
136 "__lcompact__": 30,
137 "a": "First item",
138 "c": "Third item",
139 "e": "Fifth item"
140 }
141 self.frun(pformat_json, data, comments=comments)
142
143 comments['__lcompact__'] = 25
144 self.frun(pformat_json, data, comments=comments)
145
146 comments['__lcompact__'] = 20
147 self.frun(pformat_json, data, comments=comments)
148
149 comments['__lcompact__'] = 15
150 self.frun(pformat_json, data, comments=comments)
151
152 comments.pop('__lcompact__')
153 self.frun(pformat_json, data, comments=comments)
154
155 self.frun(pformat_json, data, comments=comments, compact=15)
156
157 self.frun(pformat_json, data, comments=comments, compact=20)
158
159 self.frun(pformat_json, data, comments=comments, compact=30)
160
161
162 def test_recursive_comments(self):
163 """ {"output_highlight": "python"}
164 """
165 # Test __lsub__ for all dict elements
166 data = {
167 "user": {
168 "id": 12345,
169 "name": "Alice Smith",
170 "email": "alice@example.com"
171 },
172 "settings": {
173 "theme": "dark",
174 "notifications": True
175 }
176 }
177 comments = {
178 "__dtitle__": "User Profile",
179 "__lsub__": {
180 "id": "Unique identifier",
181 "name": "Full name",
182 "theme": "UI theme preference"
183 }
184 }
185 self.frun(pformat_json, data, comments=comments)
186
187 # Test __llist__ and __ldict__ for typed elements
188 data = [
189 {"type": "book", "title": "Python Programming"},
190 [1, 2, 3],
191 {"type": "video", "title": "Advanced Python"}
192 ]
193 comments = {
194 "__llist__": {
195 "__lcompact__": 40
196 },
197 "__ldict__": {
198 "type": "Content type",
199 "title": "Content title"
200 }
201 }
202 self.frun(pformat_json, data, comments=comments)
203
204 def test_custom_indentation(self):
205 """ {"output_highlight": "python"}
206 """
207 # Test custom indentation
208 data = {
209 "outer": {
210 "middle": {
211 "inner": "value"
212 }
213 }
214 }
215 # Test with different indent values
216 self.frun(pformat_json, data, indent=2)
217 self.frun(pformat_json, data, indent=4)
218
219
220 def test_custom_comment_prefix(self):
221 """ {"output_highlight": "python"}
222 """
223 # Test custom comment prefix
224 data = {"name": "John", "age": 30}
225 comments = {
226 "__dtitle__": "Person Info",
227 "name": "Person's name",
228 "age": "Age in years"
229 }
230 self.frun(pformat_json, data, comments=comments, comment_prefix="// ")
231
232 def test_different_compact_values(self):
233 """ {"output_highlight": "python"}
234 """
235 # Same data with different __lcompact__ values
236 data = {"item1": 100, "item2": 200, "item3": 300, "item4": 400, "item5": 500}
237 comments = {
238 "item1": "First item comment",
239 "item3": "Third item comment",
240 "item5": "Fifth item comment"
241 }
242
243 # No compact mode
244 print("\nNo compact mode:")
245 self.frun(pformat_json, data, comments=comments)
246
247 # Very wide compact mode (essentially same as no compact)
248 print("\nCompact mode (width=100):")
249 comments_wide = comments.copy()
250 comments_wide["__lcompact__"] = 100
251 self.frun(pformat_json, data, comments=comments_wide)
252
253 # Medium compact mode
254 print("\nCompact mode (width=50):")
255 comments_medium = comments.copy()
256 comments_medium["__lcompact__"] = 50
257 self.frun(pformat_json, data, comments=comments_medium)
258
259 # Narrow compact mode
260 print("\nCompact mode (width=25):")
261 comments_narrow = comments.copy()
262 comments_narrow["__lcompact__"] = 25
263 self.frun(pformat_json, data, comments=comments_narrow)
264
265 def test_deeply_nested_structure(self):
266 """ {"output_highlight": "python"}
267 """
268 # Create a deeply nested structure to test indentation handling
269 data = {
270 "level1": {
271 "level2": {
272 "level3": {
273 "level4": {
274 "level5": {
275 "value": "deeply nested value"
276 },
277 "array": [1, 2, [3, 4, [5, 6]]]
278 }
279 }
280 }
281 }
282 }
283
284 comments = {
285 "__dtitle__": "Deep Nesting Test",
286 "__lsub__": {
287 "level1": "First level",
288 "level2": "Second level",
289 "level3": "Third level",
290 "level4": "Fourth level",
291 "level5": "Fifth level",
292 "value": "The final value",
293 "array": "Array of values"
294 }
295 }
296
297 self.frun(pformat_json, data, comments=comments, debug=True)
298
299 def test_comprehensive_example(self):
300 """ {"output_highlight": "python"}
301 """
302 # Test case combining multiple features
303 data = {
304 "metadata": {
305 "title": "Comprehensive Example",
306 "version": 1.5,
307 "tags": ["test", "example", "comprehensive"]
308 },
309 "configuration": {
310 "enabled": True,
311 "options": {
312 "debug": False,
313 "verbose": True,
314 "timeout": 30
315 }
316 },
317 "data_points": [
318 {"id": 1, "value": 10.5, "label": "Point A"},
319 {"id": 2, "value": 20.7, "label": "Point B"},
320 {"id": 3, "value": 15.3, "label": "Point C"}
321 ],
322 "statistics": {
323 "count": 3,
324 "average": 15.5,
325 "max": 20.7,
326 "min": 10.5
327 }
328 }
329
330 # Define comprehensive comments
331 def format_stat(key, value):
332 if key == "average":
333 return f"Average value: {value:.1f}"
334 elif key == "max":
335 return f"Maximum value: {value:.1f}"
336 elif key == "min":
337 return f"Minimum value: {value:.1f}"
338 return str(value)
339
340 comments = {
341 "__dtitle__": "Complete Feature Demonstration",
342 "__lcompact__": 60,
343 "metadata": {
344 "__dtitle__": "Document Metadata",
345 "title": "The title of this example",
346 "tags": "Keywords for categorization"
347 },
348 "configuration": {
349 "__dtitle__": "System Configuration",
350 "__lcompact__": 40,
351 "options": {
352 "__dtitle__": "Available Options",
353 "debug": "Enable debug mode",
354 "verbose": "Show detailed output",
355 "timeout": "Operation timeout in seconds"
356 }
357 },
358 "data_points": {
359 "__ltitle__": "Measurement Data",
360 "__lprefix__": ["Array of data point objects", "Each with id, value and label"],
361 "__ldict__": {
362 "id": "Unique identifier",
363 "value": "Measurement value",
364 "label": "Display name"
365 }
366 },
367 "statistics": {
368 "__dtitle__": "Statistical Analysis",
369 "count": "Number of data points",
370 "average": format_stat,
371 "max": format_stat,
372 "min": format_stat
373 }
374 }
375
376 result = pformat_json(data, comments=comments)
377 print(result)
378
379if __name__ == "__main__":
380 t = unittest2doc.Unittest2Doc(
381 testcase=TestJsonFormatter(),
382 name='unittest2doc.formatter.pformat_json',
383 ref=':func:`unittest2doc.formatter.pformat_json`',
384 doc_root=Path(__file__).absolute().parent.parent / 'sphinx-docs/source/unittests',
385 )
386 t.generate_docs()
tests/test_exec_tool.py
¶
其生成的文档为: unittest2doc.utils.exec_tool
1from pathlib import Path
2import unittest2doc
3from unittest2doc import Unittest2Doc
4from unittest2doc.utils.exec_tool import filter_after_comment_by, load_module, collect_module_attr, load_main
5
6""" Note
7This is a special way to run test cases in the ``if __name__ == "__main__"`` block of a test file (src/unittest2doc/utils/exec_tool.py),
8Using the helper functions from that file.
9"""
10
11if __name__ == "__main__":
12 test_module = "unittest2doc.utils.exec_tool"
13 module = load_module(test_module)
14 module_globals = collect_module_attr(module, all=True)
15
16 main = load_main(
17 module,
18 code_filter=filter_after_comment_by("Run unittests"),
19 globals=module_globals,
20 add_code_object=True,
21 )
22
23 t = unittest2doc.Unittest2Doc(
24 testcase=main['TestExecTool'](),
25 name='unittest2doc.utils.exec_tool',
26 ref=':mod:`unittest2doc.utils.exec_tool`',
27 doc_root=Path(__file__).absolute().parent.parent / 'sphinx-docs/source/unittests',
28 open_input=False,
29 )
30 t.generate_docs()
最后,我们来看一下生成的文档
API 文档来源于我们的项目源代码, 是sphinx中的autosummary功能生成的, 不是本文的重点
而单元测试文档来源于我们的测试代码, 请自行探索其结果并与测试代码对比