pythonpython-unittest

Issues Reading a File when Using unittest in Python


I am unit testing a credit card validating program that accepts a .txt file containing fake credit cards numbers, and returns a dictionary of the card, whether it's a valid credit card number, and the card type (visa, mastercard, etc.).

Using the unittest module, I've successfully established that my implementation of the Luhn algorithm is sound, and that the program can correctly identify the card type, using a collection of card numbers hard-coded into the test file. However, when trying to read the .txt file into the program, testing that it returns the correct results, it outputs None instead.

Is this some quirk of the unittest module, where it cannot compare function output, when the function itself read files?

Also, the .txt file is in the same shared folder as the function's file, and the test file.

Process file function

def process_file(file_path):
    try:
        with open(file_path, 'r') as file:
            card_numbers = file.readlines()
            
        results = {}
        
        for card_number in card_numbers:
            is_valid = check_card_number(card_number)
            card_type = get_card_type(card_number)
            
            results[card_number] = (is_valid, card_type)
            
        return results
    except Exception as e:
        print(f"Error reading file: {e}")
def check_card_number(card_number):
    if not card_number.isdigit():
        return False
        
    return luhn_algorithm(card_number)
def get_card_type(card_number):
    patterns = {
        'Visa': r"^4[0-9]{12}(?:[0-9]{3})?$",
        'MasterCard': r"^5[1-5][0-9]{14}$",
        'American Express': r"^3[47][0-9]{13}$",
        'Discover': r"^6(?:011|5[0-9]{2})[0-9]{12}$",
        'JCB': r"^(?:2131|1800|35\d{3})\d{11}$",
        'Diners Club': r"^3(?:0[0-5]|[68][0-9])[0-9]{11}$",
        'Maestro': r"^(5018|5020|5038|56|57|58|6304|6759|676[1-3])\d{8,15}$"
    }
    
    for type, pattern in patterns.items():
        if re.match(pattern, card_number):
            return type
            
    return 'Unknown'
def luhn_algorithm(card_number:str) -> bool:
    payload = [int(d) for d in card_number[:-1]]
    
    even, odd = payload[-1::-2], payload[-2::-2]
    
    total = sum(odd) + sum([(i * 2) - 9 if i >= 5 else i * 2 for i in even])
    
    return 10 - (total % 10) == int(card_number[-1])

Test Process File Function

class TestProcessFile(unittest.TestCase):
    def test_validate_card_and_get_type(self):
        file_path = r'file_path\test_cards.txt'
        
        credit_cards = ('6390448951544', '3507088898930091')
        correct_card_types = ('Unknown', 'JCB')
        correct_validation = (False, True)
        correct_results = dict(zip(credit_cards, zip(correct_validation, correct_card_types)))
        
        self.assertEqual(process_file(file_path), correct_results)

When running the program itself, it reads the file and generate the expected output just fine. But when testing the program with unittest, within the assertEqual method, the function outputs None instead of the expected dictionary.


Solution

  • I have executed your code in my system with 2 changes.

    I have added the following import in the file process.py:

    import re
    

    I have create the text file test_cards.txt in the same folder of process.py, with the following content:

    6390448951544
    3507088898930091
    

    In the same folder I have created the test file:

    from process import process_file
    
    import unittest
    
    class TestProcessFile(unittest.TestCase):
        def test_validate_card_and_get_type(self):
            #file_path = r'file_path\test_cards.txt'
            file_path = r'./test_cards.txt'
    
            credit_cards = ('6390448951544\n', '3507088898930091')
            correct_card_types = ('Unknown', 'JCB')
            correct_validation = (False, True)
            correct_results = dict(zip(credit_cards, zip(correct_validation, correct_card_types)))
    
            self.assertEqual(process_file(file_path), correct_results)
    
    
    if __name__ == '__main__':
        unittest.main()
    
    

    Previous test file is identical to your file but with the second of my changes: the definition of credit_cards variable has the following modification (on my opinion not important and linked to the fact that I'm using a Linux system while I think you are using a Windows system):

    # I HAVE ADDED THE char '\n' in the first credit card number
    credit_cards = ('6390448951544\n', '3507088898930091')
    

    In my system the execution of test_process.py gives the following successfully output (process_file() not return None):

    > python test_process.py
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK