mozilla / sphinx-js

Autodoc-style extraction into Sphinx for your JS project
https://pypi.python.org/pypi/sphinx-js/
MIT License
282 stars 81 forks source link

Use PyTest as testing framework #104

Closed s-weigand closed 5 years ago

s-weigand commented 5 years ago

While working on #92 (typedoc) and running tests with nose, I got pretty annoyed with 'unhelpful' output of nose, which basically just told me "Something isn't working!". Which is why tweaked the test a bit and ran it with pytest, wich gave me a much better overview on what the problem was.

Benefits of pytest

Example outputs

The test tests/test_typedoc.py::test_references, basically compares the saved output of a doclet (desired result), with the generated one fpr quality. So the nose output is basically massiv_list_of_dicts1 != massiv_list_of_dicts2, which doesn't help (see example below).

As an example to compare the given Assertions, I changed the longname name in tests/typescript/class.ts.jsdoc from external:class~ClassDefinition to external:class~ClassDefinition1:

nose output (python setup.py test)

======================================================================

FAIL: test_references (tests.test_typedoc.Tests)
----------------------
------------------------------------------------
Traceback (most recent
 call last):
  File "D:\git\sphinx-js\tests\test_typedoc.py", line 65, 
in test_references
    eq_(jsdoc, jsdoc_ref)
AssertionError: [{'kind': 
'external', 'comment': '<empty>', 'meta': {'path': './', 'filename': 'c
lass.ts', 'lineno': 1, 'code': {}}, 'name': '"class"', 'longname': 'ext
ernal:class', 'description': ''}, {'kind': 'class', 'comment': '<empty>
', 'meta': {'path': './', 'filename': 'class.ts', 'lineno': 4, 'code': 
{}}, 'name': 'ClassDefinition', 'longname': 'external:class~ClassDefini
tion', 'memberof': 'external:class', 'description': 'A definition of a 
class\n\n', 'classdesc': '', 'params': [], 'extends': []}, {'kind': 'fu
nction', 'comment': '<empty>', 'meta': {'path': './', 'filename': 'clas
s.ts', 'lineno': 4, 'code': {'paramnames': ['simple', 'complex', 'args'
]}}, 'name': 'new ClassDefinition', 'longname': 'external:class~ClassDe
finition~new ClassDefinition', 'memberof': 'external:class~ClassDefinit
ion', 'description': 'ClassDefinition constructor\n\n', 'params': [{'na
me': 'simple', 'type': {'names': ['number']}, 'description': 'A paramet
er with a simple type\n\n'}, {'name': 'complex', 'type': {'names': ['<T
ODO>']}, 'description': 'A parameter with a complex type\n\n'}, {'name'
: 'args', 'type': {'names': ['any[]']}, 'description': 'rest arguments\
n\n\n'}], 'returns': [{'name': 'new ClassDefinition', 'type': {'names':
 ['class.ClassDefinition']}}]}, {'kind': 'member', 'comment': '<empty>'
, 'meta': {'path': './', 'filename': 'class.ts', 'lineno': 35, 'code': 
{}}, 'name': 'property', 'longname': 'external:class~ClassDefinition#pr
operty', 'memberof': 'external:class~ClassDefinition', 'description': '
Read/write property/variable\n\n', 'type': {'names': ['any']}}, {'kind'
: 'member', 'comment': '<empty>', 'meta': {'path': './', 'filename': 'c
lass.ts', 'lineno': 38, 'code': {}}, 'name': 'property2', 'longname': '
external:class~ClassDefinition#property2', 'memberof': 'external:class~
ClassDefinition', 'description': 'Read-only  or getter property\n\n', '
type': {'names': ['any']}}, {'kind': 'member', 'comment': '<empty>', 'm
eta': {'path': './', 'filename': 'class.ts', 'lineno': 44, 'code': {}},
 'name': 'property3', 'longname': 'external:class~ClassDefinition#prope
rty3', 'memberof': 'external:class~ClassDefinition', 'description': 'Pr
operty getter\n\n', 'type': {'names': ['any']}}, {'kind': 'member', 'co
mment': '<empty>', 'meta': {'path': './', 'filename': 'class.ts', 'line
no': 52, 'code': {}}, 'name': 'property4', 'longname': 'external:class~
ClassDefinition#property4', 'memberof': 'external:class~ClassDefinition
', 'description': 'Property setter\n\n', 'type': {'names': ['any']}}, {
'kind': 'function', 'comment': '<empty>', 'meta': {'path': './', 'filen
ame': 'class.ts', 'lineno': 20, 'code': {'paramnames': ['simple', 'comp
lex', 'args']}}, 'name': 'method1', 'longname': 'external:class~ClassDe
finition.method1', 'memberof': 'external:class~ClassDefinition', 'descr
iption': 'This is a method without return type\n\n', 'params': [{'name'
: 'simple', 'type': {'names': ['number']}, 'description': '\n\nA parame
ter with a simple type'}, {'name': 'complex', 'type': {'names': ['<TODO
>']}, 'description': '\n\nA parameter with a complex type'}, {'name': '
args', 'type': {'names': ['any[]']}, 'description': '\n\nrest arguments
\n'}], 'returns': []}, {'kind': 'function', 'comment': '<empty>', 'meta
': {'path': './', 'filename': 'class.ts', 'lineno': 30, 'code': {'param
names': ['simple', 'complex', 'args']}}, 'name': 'method2', 'longname':
 'external:class~ClassDefinition.method2', 'memberof': 'external:class~
ClassDefinition', 'description': 'This is a method with a simple return
 type\n\n', 'params': [{'name': 'simple', 'type': {'names': ['number']}
, 'description': '\n\nA parameter with a simple type'}, {'name': 'compl
ex', 'type': {'names': ['<TODO>']}, 'description': '\n\nA parameter wit
h a complex type'}, {'name': 'args', 'type': {'names': ['any[]']}, 'des
cription': '\n\nrest arguments'}], 'returns': [{'name': 'method2', 'typ
e': {'names': ['number']}, 'description': '42\n'}]}] != [{'comment': '<
empty>', 'description': '', 'kind': 'external', 'longname': 'external:c
lass', 'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 1, 'path'
: './'}, 'name': '"class"'}, {'classdesc': '', 'comment': '<empty>', 'd
escription': 'A definition of a class\n\n', 'extends': [], 'kind': 'cla
ss', 'longname': 'external:class~ClassDefinition1', 'memberof': 'extern
al:class', 'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 4, 'p
ath': './'}, 'name': 'ClassDefinition', 'params': []}, {'comment': '<em
pty>', 'description': 'ClassDefinition constructor\n\n', 'kind': 'funct
ion', 'longname': 'external:class~ClassDefinition~new ClassDefinition',
 'memberof': 'external:class~ClassDefinition', 'meta': {'code': {'param
names': ['simple', 'complex', 'args']}, 'filename': 'class.ts', 'lineno
': 4, 'path': './'}, 'name': 'new ClassDefinition', 'params': [{'descri
ption': 'A parameter with a simple type\n\n', 'name': 'simple', 'type':
 {'names': ['number']}}, {'description': 'A parameter with a complex ty
pe\n\n', 'name': 'complex', 'type': {'names': ['<TODO>']}}, {'descripti
on': 'rest arguments\n\n\n', 'name': 'args', 'type': {'names': ['any[]'
]}}], 'returns': [{'name': 'new ClassDefinition', 'type': {'names': ['c
lass.ClassDefinition']}}]}, {'comment': '<empty>', 'description': 'Read
/write property/variable\n\n', 'kind': 'member', 'longname': 'external:
class~ClassDefinition#property', 'memberof': 'external:class~ClassDefin
ition', 'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 35, 'pat
h': './'}, 'name': 'property', 'type': {'names': ['any']}}, {'comment':
 '<empty>', 'description': 'Read-only  or getter property\n\n', 'kind':
 'member', 'longname': 'external:class~ClassDefinition#property2', 'mem
berof': 'external:class~ClassDefinition', 'meta': {'code': {}, 'filenam
e': 'class.ts', 'lineno': 38, 'path': './'}, 'name': 'property2', 'type
': {'names': ['any']}}, {'comment': '<empty>', 'description': 'Property
 getter\n\n', 'kind': 'member', 'longname': 'external:class~ClassDefini
tion#property3', 'memberof': 'external:class~ClassDefinition', 'meta': 
{'code': {}, 'filename': 'class.ts', 'lineno': 44, 'path': './'}, 'name
': 'property3', 'type': {'names': ['any']}}, {'comment': '<empty>', 'de
scription': 'Property setter\n\n', 'kind': 'member', 'longname': 'exter
nal:class~ClassDefinition#property4', 'memberof': 'external:class~Class
Definition', 'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 52,
 'path': './'}, 'name': 'property4', 'type': {'names': ['any']}}, {'com
ment': '<empty>', 'description': 'This is a method without return type\
n\n', 'kind': 'function', 'longname': 'external:class~ClassDefinition.m
ethod1', 'memberof': 'external:class~ClassDefinition', 'meta': {'code':
 {'paramnames': ['simple', 'complex', 'args']}, 'filename': 'class.ts',
 'lineno': 20, 'path': './'}, 'name': 'method1', 'params': [{'descripti
on': '\n\nA parameter with a simple type', 'name': 'simple', 'type': {'
names': ['number']}}, {'description': '\n\nA parameter with a complex t
ype', 'name': 'complex', 'type': {'names': ['<TODO>']}}, {'description'
: '\n\nrest arguments\n', 'name': 'args', 'type': {'names': ['any[]']}}
], 'returns': []}, {'comment': '<empty>', 'description': 'This is a met
hod with a simple return type\n\n', 'kind': 'function', 'longname': 'ex
ternal:class~ClassDefinition.method2', 'memberof': 'external:class~Clas
sDefinition', 'meta': {'code': {'paramnames': ['simple', 'complex', 'ar
gs']}, 'filename': 'class.ts', 'lineno': 30, 'path': './'}, 'name': 'me
thod2', 'params': [{'description': '\n\nA parameter with a simple type'
, 'name': 'simple', 'type': {'names': ['number']}}, {'description': '\n
\nA parameter with a complex type', 'name': 'complex', 'type': {'names'
: ['<TODO>']}}, {'description': '\n\nrest arguments', 'name': 'args', '
type': {'names': ['any[]']}}], 'returns': [{'description': '42\n', 'nam
e': 'method2', 'type': {'names': ['number']}}]}]
----------------------------------------------------------------------

pytest output (pytest)

Pytest shows the failing test from the definition to the line that asserted and tuctates the compared objects of they are too big. Note for the output to change I replaced eq_(jsdoc, jsdoc_ref) (from nose) with assert jsdoc == jsdoc_ref, which is the pytest way to compare equality and IMO the prettier python code.

======================================================== FAILURES ========================================================= 

__________________________________________________ Tests.test_references __________________________________________________

self = <tests.test_typedoc.Tests testMethod=test_references>

    def test_references(self):
        for source in os.listdir(self.source_dir):
            if source.endswith('.ts'):
                with open(self.typedoc(source), 'r') as typedocfile:
                    jsdoc = parse_typedoc(typedocfile)
                jsdoc_ref_file = os.path.join(self.source_dir, source + '.jsdoc')
                if not os.path.exists(jsdoc_ref_file):
                    # When a reference file is missing, this is probably a new test.
                    # Generate a reference file for the developer to review.  If the
                    # generated reference is good, just remove the '.ref' extension.
                    with open(jsdoc_ref_file + '.ref', 'w') as jsdocfile:
                        json.dump(
                            jsdoc,
                            jsdocfile,
                            sort_keys=True,
                            indent=4,
                            separators=(',', ': '))
                    print('wrote %s' % jsdoc_ref_file)
                else:
                    with open(jsdoc_ref_file, 'r') as jsdocfile:
                        jsdoc_ref = json.load(jsdocfile)
                    # eq_(jsdoc, jsdoc_ref)
>                   assert jsdoc == jsdoc_ref
E                   AssertionError: assert [{'comment': ...3', ...}, ...] == [{'comment': '...3', ...}, ...]
E                     At index 1 diff: {'kind': 'class', 'comment': '<empty>', 'meta': {'path': './', 
'filename': 'class.ts', 'lineno': 4, 'code': {}}, 'name': 'ClassDefinition', 'longname': 'external:class~ClassDefinition', 
'memberof': 'external:class', 'description': 'A definition of a class\n\n', 'classdesc': '', 'params': [], 'extends': []} 
!= {'classdesc': '', 'comment': '<empty>', 'description': 'A definition of a class\n\n', 'extends': [], 'kind': 'class', 
'longname': 'external:class~ClassDefinition1', 'memberof': 'external:class', 'meta': {'code': {}, 'filename': 'class.ts', 'lineno...
E
E                     ...Full output truncated (2 lines hidden), use '-vv' to show

tests\test_typedoc.py:66: AssertionError
-------------------------------------------------- Captured stdout call ---------------------------------------------------

pytest output** (pytest -vv)

Note the +, - and ? at the accual difference.

======================================================== FAILURES =========================================================
__________________________________________________ Tests.test_references __________________________________________________

self = <tests.test_typedoc.Tests testMethod=test_references>                                                               

    def test_references(self):                                                                                             
        for source in os.listdir(self.source_dir):                                                                         
            if source.endswith('.ts'):                                                                                     
                with open(self.typedoc(source), 'r') as typedocfile:                                                       
                    jsdoc = parse_typedoc(typedocfile)                                                                     
                jsdoc_ref_file = os.path.join(self.source_dir, source + '.jsdoc')                                          
                if not os.path.exists(jsdoc_ref_file):                                                                     
                    # When a reference file is missing, this is probably a new test.                                       
                    # Generate a reference file for the developer to review.  If the                                       
                    # generated reference is good, just remove the '.ref' extension.                                       
                    with open(jsdoc_ref_file + '.ref', 'w') as jsdocfile:                                                  
                        json.dump(                                                                                         
                            jsdoc,                                                                                         
                            jsdocfile,                                                                                     
                            sort_keys=True,                                                                                
                            indent=4,                                                                                      
                            separators=(',', ': '))                                                                        
                    print('wrote %s' % jsdoc_ref_file)                                                                     
                else:                                                                                                      
                    with open(jsdoc_ref_file, 'r') as jsdocfile:                                                           
                        jsdoc_ref = json.load(jsdocfile)                                                                   
                    # eq_(jsdoc, jsdoc_ref)                                                                                
>                   assert jsdoc == jsdoc_ref                                                                              
E                   AssertionError: assert [{'comment': ...3', ...}, ...] == [{'comment': '...3', ...}, ...]               
E                     At index 1 diff: {'kind': 'class', 'comment': '<empty>', 'meta': {'path': './', 'filename': 'class.ts
, 'lineno': 4, 'code': {}}, 'name': 'ClassDefinition', 'longname': 'external:class~ClassDefinition', 'memberof': 'external:
lass', 'description': 'A definition of a class\n\n', 'classdesc': '', 'params': [], 'extends': []} != {'classdesc': '', 'co
ment': '<empty>', 'description': 'A definition of a class\n\n', 'extends': [], 'kind': 'class', 'longname': 'external:class
ClassDefinition1', 'memberof': 'external:class', 'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 4, 'path': './'}, '
ame': 'ClassDefinition', 'params': []}                                                                                     
E                     Full diff:                                                                                           
E                     [{'comment': '<empty>',                                                                              
E                     'description': '',                                                                                   
E                     'kind': 'external',                                                                                  
E                     'longname': 'external:class',                                                                        
E                     'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 1, 'path': './'},                             
E                     'name': '"class"'},                                                                                  
E                     {'classdesc': '',                                                                                    
E                     'comment': '<empty>',                                                                                
E                     'description': 'A definition of a class\n\n',                                                        
E                     'extends': [],                                                                                       
E                     'kind': 'class',                                                                                     
E                     -   'longname': 'external:class~ClassDefinition',                                                    
E                     +   'longname': 'external:class~ClassDefinition1',                                                   
E                     ?                                              +                                                     
E                     'memberof': 'external:class',                                                                        
E                     'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 4, 'path': './'},                             
E                     'name': 'ClassDefinition',                                                                           
E                     'params': []},                                                                                       
E                     {'comment': '<empty>',                                                                               
E                     'description': 'ClassDefinition constructor\n\n',                                                    
E                     'kind': 'function',                                                                                  
E                     'longname': 'external:class~ClassDefinition~new ClassDefinition',                                    
E                     'memberof': 'external:class~ClassDefinition',                                                        
E                     'meta': {'code': {'paramnames': ['simple', 'complex', 'args']},                                      
E                     'filename': 'class.ts',                                                                              
E                     'lineno': 4,                                                                                         
E                     'path': './'},                                                                                       
E                     'name': 'new ClassDefinition',                                                                       
E                     'params': [{'description': 'A parameter with a simple type\n\n',                                     
E                     'name': 'simple',                                                                                    
E                     'type': {'names': ['number']}},                                                                      
E                     {'description': 'A parameter with a complex type\n\n',                                               
E                     'name': 'complex',                                                                                   
E                     'type': {'names': ['<TODO>']}},                                                                      
E                     {'description': 'rest arguments\n\n\n',                                                              
E                     'name': 'args',                                                                                      
E                     'type': {'names': ['any[]']}}],                                                                      
E                     'returns': [{'name': 'new ClassDefinition',                                                          
E                     'type': {'names': ['class.ClassDefinition']}}]},                                                     
E                     {'comment': '<empty>',                                                                               
E                     'description': 'Read/write property/variable\n\n',                                                   
E                     'kind': 'member',                                                                                    
E                     'longname': 'external:class~ClassDefinition#property',                                               
E                     'memberof': 'external:class~ClassDefinition',                                                        
E                     'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 35, 'path': './'},                            
E                     'name': 'property',                                                                                  
E                     'type': {'names': ['any']}},                                                                         
E                     {'comment': '<empty>',                                                                               
E                     'description': 'Read-only  or getter property\n\n',                                                  
E                     'kind': 'member',                                                                                    
E                     'longname': 'external:class~ClassDefinition#property2',                                              
E                     'memberof': 'external:class~ClassDefinition',                                                        
E                     'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 38, 'path': './'},                            
E                     'name': 'property2',                                                                                 
E                     'type': {'names': ['any']}},                                                                         
E                     {'comment': '<empty>',                                                                               
E                     'description': 'Property getter\n\n',                                                                
E                     'kind': 'member',                                                                                    
E                     'longname': 'external:class~ClassDefinition#property3',                                              
E                     'memberof': 'external:class~ClassDefinition',                                                        
E                     'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 44, 'path': './'},                            
E                     'name': 'property3',                                                                                 
E                     'type': {'names': ['any']}},                                                                         
E                     {'comment': '<empty>',                                                                               
E                     'description': 'Property setter\n\n',                                                                
E                     'kind': 'member',                                                                                    
E                     'longname': 'external:class~ClassDefinition#property4',                                              
E                     'memberof': 'external:class~ClassDefinition',                                                        
E                     'meta': {'code': {}, 'filename': 'class.ts', 'lineno': 52, 'path': './'},                            
E                     'name': 'property4',                                                                                 
E                     'type': {'names': ['any']}},                                                                         
E                     {'comment': '<empty>',                                                                               
E                     'description': 'This is a method without return type\n\n',                                           
E                     'kind': 'function',                                                                                  
E                     'longname': 'external:class~ClassDefinition.method1',                                                
E                     'memberof': 'external:class~ClassDefinition',                                                        
E                     'meta': {'code': {'paramnames': ['simple', 'complex', 'args']},                                      
E                     'filename': 'class.ts',                                                                              
E                     'lineno': 20,                                                                                        
E                     'path': './'},                                                                                       
E                     'name': 'method1',                                                                                   
E                     'params': [{'description': '\n\nA parameter with a simple type',                                     
E                     'name': 'simple',                                                                                    
E                     'type': {'names': ['number']}},                                                                      
E                     {'description': '\n\nA parameter with a complex type',                                               
E                     'name': 'complex',                                                                                   
E                     'type': {'names': ['<TODO>']}},                                                                      
E                     {'description': '\n\nrest arguments\n',                                                              
E                     'name': 'args',                                                                                      
E                     'type': {'names': ['any[]']}}],                                                                      
E                     'returns': []},                                                                                      
E                     {'comment': '<empty>',                                                                               
E                     'description': 'This is a method with a simple return type\n\n',                                     
E                     'kind': 'function',                                                                                  
E                     'longname': 'external:class~ClassDefinition.method2',                                                
E                     'memberof': 'external:class~ClassDefinition',                                                        
E                     'meta': {'code': {'paramnames': ['simple', 'complex', 'args']},                                      
E                     'filename': 'class.ts',                                                                              
E                     'lineno': 30,                                                                                        
E                     'path': './'},                                                                                       
E                     'name': 'method2',                                                                                   
E                     'params': [{'description': '\n\nA parameter with a simple type',                                     
E                     'name': 'simple',                                                                                    
E                     'type': {'names': ['number']}},                                                                      
E                     {'description': '\n\nA parameter with a complex type',                                               
E                     'name': 'complex',                                                                                   
E                     'type': {'names': ['<TODO>']}},                                                                      
E                     {'description': '\n\nrest arguments',                                                                
E                     'name': 'args',                                                                                      
E                     'type': {'names': ['any[]']}}],                                                                      
E                     'returns': [{'description': '42\n',                                                                  
E                     'name': 'method2',                                                                                   
E                     'type': {'names': ['number']}}]}]                                                                    

tests\test_typedoc.py:66: AssertionError                                                                                   
erikrose commented 5 years ago

I'd happily take a PR!