3 minute read

How to solve the “ModuleNotFound” error when running Python’s from terminal unittest within a project folder structure, according to Zed Shaw.

Overview

The use of terminal and the project folder structure is advocated in Zed Shaw’s Learn Python 3 The Hard Way.

However, this depended on using “nose” for unit testing, and at the end of chapter 46 “A Project Skeleton” is a message that might ruin your day:

“WARNING! At the time of publication I learned that the nose project has been abandoned and might not work very well. … On Windows you may not have this problem, but using python -m “nose” will solve it if you do.”

The important bit is the “python -m” syntax, which allows relative imports of test modules.

Without it, many attempts to run unit testing from the terminal will fail.

Project Directory Structure

Zed Shaw advocates the following directory structure which we can apply to the automated testing example of exercise 48 (ex48):

myprojects/ 
├── ex48/ 
|   ├── ex48/
|   |   ├── __ init __.py 
|   |   └── lexicon.py
|   ├── bin/ 
|   ├── docs/
|   ├── tests/
|       ├── __ init __.py
|       └── test_lexicon.py 
└── ...

Lexicon Script

The the lexicon.py file in exercise 48 can be scripted as follows:


""" 
Title: Learn Python the Hard Way. Ex 48. Advanced User Input 
Author: Zed Shaw 
Script Name: lexicon.py
Script Developer: Heinzad 
Script Dated: 9 Jan 2023 
Script Description: Checks if user-supplied strings contain words 
accepted in a game's lexicon. 
""" 

# Build dictionary of words that are accepted in game answers 

lexica = {
    # directions 
    'north':'direction',
    'south':'direction',
    'east':'direction',
    'west':'direction',
    'down':'direction',
    'up':'direction',
    'left':'direction',
    'right':'direction',
    'back':'direction',
    
    # verbs 
    'go':'verb',
    'stop':'verb',
    'kill':'verb',
    'eat':'verb',
    
    # stops 
    'the':'stop',
    'in':'stop',
    'of':'stop',
    'at':'stop',
    'it':'stop', 
    
    # nouns 
    'door':'noun',
    'bear':'noun',
    'princess':'noun',
    'cabinet':'noun',
    } 


def convert_number(n): 
	""" Returns an integer or nothing if n is unconvertable """ 
    try: 
        return int(n) 
    except ValueError: 
        return None  

    
def dicta_scan(sentance): 
    """ Returns a (value, key) tuple from a dictionary 
	where a word in the sentance is in the game's lexicon. 
    >>> dicta_scan("north")
    ('direction', 'north')   
    >>> dicta_scan("go")
    ('verb', 'go')   
    >>> dicta_scan("the")
    ('stop', 'the')   
    >>> dicta_scan("bear")
    ('noun', 'bear')   
    >>> dicta_scan("1234")
    ('number', 1234)    
    >>> dicta_scan("ASDFADFASDF")
    ('error', 'ASDFADFASDF')   
    """ 
    
    words = sentance.split() 
    default = "___" 
    result = default 
    for word in words: 
        
        lex_key = lexica.get(word, default)  
        
        if lex_key != default: 
            lex_val = lexica[word] 
            return(lex_val, word) 
        
        elif word.isnumeric() and convert_number(word): 
            return('number', int(word)) 
        
        else: 
            return('error', word)    


if __name__ == "__main__":
    import doctest
    doctest.testmod() 


Lexicon Test Script

The the test_lexicon.py file in exercise 48 can be scripted using unittest as follows:



""" 
Title: Learn Python the Hard Way. Ex 48. Advanced User Input 
Author: Zed Shaw 
Script Name: test_lexicon.py
Script Developer: Heinzad 
Script Dated: 9 Jan 2023 
Script Description: Runs unit testing on the lexicon module.  
""" 


import unittest
from ex48.lexicon import dicta_scan


class TestDicta(unittest.TestCase): 
    # tests the lexica scan  
    
    def test_direction_scan(self): 
        expected = ('direction', 'north') 
        actual = dicta_scan("north") 
        self.assertEqual(expected, actual, "'north' is a direction")
        
    def test_verb_scan(self): 
        expected = ('verb', 'go') 
        actual = dicta_scan("go") 
        self.assertEqual(expected, actual, "'go' is a verb")    
        
    def test_stops_scan(self): 
        expected = ('stop', 'the') 
        actual = dicta_scan("the") 
        self.assertEqual(expected, actual, "'the' is a stop")
        
    def test_nouns_scan(self): 
        expected = ('noun', 'bear') 
        actual = dicta_scan("bear") 
        self.assertEqual(expected, actual, "'bear' is a noun") 
        
    def test_number_scan(self): 
        expected = ('number', 1234) 
        actual = dicta_scan("1234") 
        self.assertEqual(expected, actual, "'1234' is a number")
        
    def test_error_scan(self): 
        expected = ('error', 'ASFADFASDF') 
        actual = dicta_scan("ASFADFASDF") 
        self.assertEqual(expected, actual, "'ASFADFASDF' is an error")
   

if __name__ == '__main__':
	unittest.main() 


Terminal

We are now in a position to run unittest from the PowerShell terminal using the -m flag:


cd myprojects
cd ex48 
python -m unittest tests\test_lexicon.py 

With any luck we get an output of “……”:

Ran 6 tests in 0.001s

QED

© Adam Heinz

9 January 2023

Categories:

Updated: