2010-10-15 11 views
48

Mam funkcję Python, która zapisuje plik wyjściowy na dysk.Jak wykonać testowanie jednostek funkcji zapisujących pliki przy użyciu python unittest

Chcę napisać test jednostkowy dla niego za pomocą modułu Unittest Python.

W jaki sposób powinienem zapewnić równość plików? Chciałbym uzyskać błąd, jeśli zawartość pliku różni się od oczekiwanej + listy różnic. Podobnie jak w wyjściu polecenia unix diff.

Czy istnieje oficjalny/zalecany sposób robienia tego?

Odpowiedz

34

Najprostszą rzeczą jest napisanie pliku wyjściowego, następnie odczytanie jego zawartości, odczytanie zawartości złotego (oczekiwanego) pliku i porównanie ich z prostą równością łańcuchową. Jeśli są takie same, usuń plik wyjściowy. Jeśli są różne, podnieś asercję.

W ten sposób, po zakończeniu testów każdy nieudany test będzie reprezentowany przez plik wyjściowy, a do porównywania z plikami złota można użyć narzędzia innej firmy (Beyond Compare jest do tego cudowny).

Jeśli naprawdę chcesz podać własne wyjście diff, pamiętaj, że stdlib Pythona ma moduł difflib. Nowy unittest wsparcie w Pythonie 3.1 zawiera metodę assertMultiLineEqual że używa go, aby pokazać dyferencjału, podobne do tego:

def assertMultiLineEqual(self, first, second, msg=None): 
     """Assert that two multi-line strings are equal. 

     If they aren't, show a nice diff. 

     """ 
     self.assertTrue(isinstance(first, str), 
       'First argument is not a string') 
     self.assertTrue(isinstance(second, str), 
       'Second argument is not a string') 

     if first != second: 
      message = ''.join(difflib.ndiff(first.splitlines(True), 
               second.splitlines(True))) 
      if msg: 
       message += " : " + msg 
      self.fail("Multi-line strings are unequal:\n" + message) 
2

Można oddzielić generowanie treści z przetwarzania plików. W ten sposób możesz sprawdzić, czy zawartość jest poprawna, bez konieczności bałagania z plikami tymczasowymi i czyszczenia ich później.

Jeśli napiszesz kod generator method, który zapewnia każdą linię zawartości, możesz mieć metodę obsługi plików, która otwiera plik i wywołuje file.writelines() z sekwencją wierszy. Te dwie metody mogłyby nawet należeć do tej samej klasy: kod testowy wywoływałby generator, a kod produkcyjny wywoływałby obsługę plików.

Oto przykład pokazujący wszystkie trzy sposoby testowania. Zazwyczaj wystarczy wybrać jedną, w zależności od tego, jakie metody są dostępne w klasie do przetestowania.

import os 
from io import StringIO 
from unittest.case import TestCase 


class Foo(object): 
    def save_content(self, filename): 
     with open(filename, 'w') as f: 
      self.write_content(f) 

    def write_content(self, f): 
     f.writelines(self.generate_content()) 

    def generate_content(self): 
     for i in range(3): 
      yield u"line {}\n".format(i) 


class FooTest(TestCase): 
    def test_generate(self): 
     expected_lines = ['line 0\n', 'line 1\n', 'line 2\n'] 
     foo = Foo() 

     lines = list(foo.generate_content()) 

     self.assertEqual(expected_lines, lines) 

    def test_write(self): 
     expected_text = u"""\ 
line 0 
line 1 
line 2 
""" 
     f = StringIO() 
     foo = Foo() 

     foo.write_content(f) 

     self.assertEqual(expected_text, f.getvalue()) 

    def test_save(self): 
     expected_text = u"""\ 
line 0 
line 1 
line 2 
""" 
     foo = Foo() 

     filename = 'foo_test.txt' 
     try: 
      foo.save_content(filename) 

      with open(filename, 'rU') as f: 
       text = f.read() 
     finally: 
      os.remove(filename) 

     self.assertEqual(expected_text, text) 
+0

można podać przykładowy kod do tego? To brzmi interesująco. – buhtz

+1

Dodałem przykład dla wszystkich trzech podejść, @buhtz. –

38

wolę mieć funkcje wyjściowe wyraźnie Przyjmowanie pliku uchwyt (lub plikopodobnym obiekt), zamiast zaakceptować plik nazwa i otwierając plik sami. W ten sposób mogę przekazać obiekt StringIO do funkcji wyjściowej w moim teście urządzenia, następnie .read() zawartość z powrotem z tego obiektu StringIO (po wywołaniu .seek(0)) i porównać z moim oczekiwanym wyjściem.

Na przykład, będziemy przechodzić kod jak ten

##File:lamb.py 
import sys 


def write_lamb(outfile_path): 
    with open(outfile_path, 'w') as outfile: 
     outfile.write("Mary had a little lamb.\n") 


if __name__ == '__main__': 
    write_lamb(sys.argv[1]) 



##File test_lamb.py 
import unittest 
import tempfile 

import lamb 


class LambTests(unittest.TestCase): 
    def test_lamb_output(self): 
     outfile_path = tempfile.mkstemp()[1] 
     try: 
      lamb.write_lamb(outfile_path) 
      contents = open(tempfile_path).read() 
     finally: 
      # NOTE: To retain the tempfile if the test fails, remove 
      # the try-finally clauses 
      os.remove(outfile_path) 
     self.assertEqual(result, "Mary had a little lamb.\n") 

do kodu jak ten

##File:lamb.py 
import sys 


def write_lamb(outfile): 
    outfile.write("Mary had a little lamb.\n") 


if __name__ == '__main__': 
    with open(sys.argv[1], 'w') as outfile: 
     write_lamb(outfile) 



##File test_lamb.py 
import unittest 
from io import StringIO 

import lamb 


class LambTests(unittest.TestCase): 
    def test_lamb_output(self): 
     outfile = StringIO() 
     # NOTE: Alternatively, for Python 2.6+, you can use 
     # tempfile.SpooledTemporaryFile, e.g., 
     #outfile = tempfile.SpooledTemporaryFile(10 ** 9) 
     lamb.write_lamb(outfile) 
     outfile.seek(0) 
     content = outfile.read() 
     self.assertEqual(content, "Mary had a little lamb.\n") 

Podejście to ma tę dodatkową zaletę dokonywania funkcję wyjścia bardziej elastyczne, jeśli na przykład, zdecyduj, że nie chcesz pisać do pliku, ale do innego bufora, ponieważ akceptuje on wszystkie obiekty podobne do plików.

Należy pamiętać, że za pomocą StringIO zakłada się, że zawartość wyników testu mieści się w pamięci głównej.W przypadku bardzo dużych wyników można użyć podejścia temporary file (np. tempfile.SpooledTemporaryFile).

+2

To jest lepsze niż zapisanie pliku na dysk. Jeśli używasz ton unittests, IO na dysk powoduje różnego rodzaju problemy, szczególnie próbuje je wyczyścić. Miałem testy zapisu na dysk, tearDown usuwanie zapisanych plików. Testy działałyby dobrze po jednym na raz, a następnie kończyły się niepowodzeniem, gdy Run All. Przynajmniej z Visual Studio i PyTools na maszynie Win. Również prędkość. – srock

+1

Podczas gdy jest to dobre rozwiązanie do testowania oddzielnych funkcji, nadal jest kłopotliwe podczas testowania rzeczywistego interfejsu, który zapewnia twój program (np. Narzędzie CLI). – Joost

+0

Możesz zastąpić 'seek()' i 'read()' ' getvalue() '. –

-2

Na podstawie sugestii podjąłem następujące czynności.

class MyTestCase(unittest.TestCase): 
    def assertFilesEqual(self, first, second, msg=None): 
     first_f = open(first) 
     first_str = first_f.read() 
     second_f = open(second) 
     second_str = second_f.read() 
     first_f.close() 
     second_f.close() 

     if first_str != second_str: 
      first_lines = first_str.splitlines(True) 
      second_lines = second_str.splitlines(True) 
      delta = difflib.unified_diff(first_lines, second_lines, fromfile=first, tofile=second) 
      message = ''.join(delta) 

      if msg: 
       message += " : " + msg 

      self.fail("Multi-line strings are unequal:\n" + message) 

stworzyłem MyTestCase podklasy jak mam wiele funkcji, które wymagają do odczytu/zapisu plików, więc naprawdę trzeba mieć do ponownego użycia metody wymuszenia. Teraz w moich testach podklasowałbym MyTestCase zamiast unittest.TestCase.

Co o tym sądzisz?

+2

patrz http://stackoverflow.com/questions/4617034/python-open-multiple-files-using-with-open –

14
import filecmp 

Następnie

self.assertTrue(filecmp.cmp(path1, path2))