Python'da birim testinden veri çıkışı


115

Python'da birim testleri yazıyorsam (birim test modülünü kullanarak), başarısız bir testten veri çıkarmak mümkün mü, böylece hataya neyin neden olduğunu anlamamıza yardımcı olması için onu inceleyebilir miyim? Bazı bilgileri taşıyabilen özelleştirilmiş bir mesaj oluşturma yeteneğinin farkındayım, ancak bazen bir dizge olarak kolayca temsil edilemeyen daha karmaşık verilerle uğraşabilirsiniz.

Örneğin, bir Foo sınıfınız olduğunu ve testdata adlı bir listedeki verileri kullanarak bir yöntem çubuğunu test ettiğinizi varsayalım:

class TestBar(unittest.TestCase):
    def runTest(self):
        for t1, t2 in testdata:
            f = Foo(t1)
            self.assertEqual(f.bar(t2), 2)

Test başarısız olursa, bu belirli verinin neden bir hatayla sonuçlandığını görmek için t1, t2 ve / veya f çıktısını almak isteyebilirim. Çıktı olarak, test çalıştırıldıktan sonra değişkenlere diğer tüm değişkenler gibi erişilebileceğini kastediyorum.

Yanıtlar:


73

Benim gibi buraya basit ve hızlı bir cevap arayan biri için çok geç cevap.

Python 2.7'de msg, hata mesajına aşağıdaki gibi bilgi eklemek için ek bir parametre kullanabilirsiniz :

self.assertEqual(f.bar(t2), 2, msg='{0}, {1}'.format(t1, t2))

Resmi dokümanlar burada


1
Python 3'te de çalışır.
MrDBA

18
Dokümanlar bunu ima ediyor, ancak açıkça belirtmeye değer: varsayılan olarak, msgkullanılırsa, normal hata mesajının yerini alacaktır. Almak için msg, normal hata iletiye eklenen, ayrıca set gereken TestCase.longMessage True olarak
Catalin Iacob

1
özel bir hata mesajı iletebileceğimizi bilmek güzel, ancak hatadan bağımsız olarak bazı mesajlar yazdırmakla ilgileniyordum.
Harry Moreno

5
@CatalinIacob'un yorumu Python 2.x için geçerlidir. Python 3.x'te, TestCase.longMessage varsayılan olarak True.
ndmeiri

70

Bunun için loglama modülünü kullanıyoruz.

Örneğin:

import logging
class SomeTest( unittest.TestCase ):
    def testSomething( self ):
        log= logging.getLogger( "SomeTest.testSomething" )
        log.debug( "this= %r", self.this )
        log.debug( "that= %r", self.that )
        # etc.
        self.assertEquals( 3.14, pi )

if __name__ == "__main__":
    logging.basicConfig( stream=sys.stderr )
    logging.getLogger( "SomeTest.testSomething" ).setLevel( logging.DEBUG )
    unittest.main()

Bu, başarısız olduğunu bildiğimiz ve ek hata ayıklama bilgileri istediğimiz belirli testler için hata ayıklamayı açmamızı sağlar.

Bununla birlikte, tercih ettiğim yöntem, hata ayıklamaya çok zaman harcamak değil, sorunu ortaya çıkarmak için daha ayrıntılı testler yazmak için harcamaktır.


Ya testSomething'in içinde foo yöntemini çağırırsam ve bir şeyi günlüğe kaydederse. Logger'ı foo'ya geçirmeden bunun çıktısını nasıl görebilirim?
simao

@simao: Nedir foo? Ayrı bir işlev mi? Bir yöntem işlevi SomeTest? İlk durumda, bir fonksiyonun kendi kaydedicisi olabilir. İkinci durumda, diğer yöntem işlevi kendi kaydedicisine sahip olabilir. loggingPaketin nasıl çalıştığının farkında mısınız ? Çoklu kaydediciler normdur.
S.Lott

8
Tam belirttiğiniz şekilde günlük kaydını kurdum. Çalıştığını varsayıyorum, ancak çıktıyı nerede görebilirim? Konsola çıkmıyor. Bir dosyaya giriş yaparak yapılandırmayı denedim, ancak bu da herhangi bir çıktı üretmiyor.
MikeyE

"Ancak tercih ettiğim yöntem, hata ayıklamaya çok fazla zaman harcamak değil, sorunu ortaya çıkarmak için daha ayrıntılı testler yazmak için harcamak." - iyi söyledin!
Seth

34

Basit baskı ifadelerini veya stdout'a başka herhangi bir şekilde yazabilirsiniz. Ayrıca testlerinizin herhangi bir yerinde Python hata ayıklayıcıyı çalıştırabilirsiniz.

Eğer kullanırsanız burun (I öneriyoruz) testler, her test için stdout'u toplayacak ve test başarısız eğer testler geçerken darmadağın çıkışı ile yaşamak zorunda kalmamak için sadece, sana gösteririm.

burnun ayrıca, önermelerde bahsedilen değişkenleri otomatik olarak göstermek veya başarısız testlerde hata ayıklayıcıyı çağırmak için anahtarları vardır. Örneğin -s( --nocapture) standart çıkışın yakalanmasını engeller.


Ne yazık ki, burun, günlükleme çerçevesi kullanılarak stdout / err'a yazılan günlükleri toplamıyor gibi görünüyor. Birbirimin yanında printve log.debug()yanında var ve yöntemden DEBUGkökte günlüğe kaydetmeyi açıkça açıyorum setUp(), ancak yalnızca printçıktı görünüyor.
haridsv

7
nosetests -sbir hata olup olmadığını stdout'un içeriğini gösterir - faydalı bulduğum bir şey.
hargriffle

Burun dokümanlarında değişkenleri otomatik olarak gösterecek anahtarları bulamıyorum. Bana onları açıklayan bir şeye yönlendirebilir misin?
ABM

Burun veya ünite testindeki değişkenleri otomatik olarak göstermenin bir yolunu bilmiyorum. Testlerimde görmek istediğim şeyleri yazdırıyorum.
Ned Batchelder

16

Tam olarak aradığınız şeyin bu olduğunu sanmıyorum, başarısız olmayan değişken değerleri göstermenin bir yolu yok, ancak bu, sonuçları istediğiniz şekilde çıkarmaya yaklaşmanıza yardımcı olabilir.

TestRunner.run () tarafından döndürülen TestResult nesnesini kullanabilirsiniz.Sonuç analizi ve işleme için . Özellikle TestResult.errors ve TestResult hataları

TestResults Nesnesi Hakkında:

http://docs.python.org/library/unittest.html#id3

Ve sizi doğru yöne yönlendirecek bazı kodlar:

>>> import random
>>> import unittest
>>>
>>> class TestSequenceFunctions(unittest.TestCase):
...     def setUp(self):
...         self.seq = range(5)
...     def testshuffle(self):
...         # make sure the shuffled sequence does not lose any elements
...         random.shuffle(self.seq)
...         self.seq.sort()
...         self.assertEqual(self.seq, range(10))
...     def testchoice(self):
...         element = random.choice(self.seq)
...         error_test = 1/0
...         self.assert_(element in self.seq)
...     def testsample(self):
...         self.assertRaises(ValueError, random.sample, self.seq, 20)
...         for element in random.sample(self.seq, 5):
...             self.assert_(element in self.seq)
...
>>> suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
>>> testResult = unittest.TextTestRunner(verbosity=2).run(suite)
testchoice (__main__.TestSequenceFunctions) ... ERROR
testsample (__main__.TestSequenceFunctions) ... ok
testshuffle (__main__.TestSequenceFunctions) ... FAIL

======================================================================
ERROR: testchoice (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", line 11, in testchoice
ZeroDivisionError: integer division or modulo by zero

======================================================================
FAIL: testshuffle (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "<stdin>", line 8, in testshuffle
AssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

----------------------------------------------------------------------
Ran 3 tests in 0.031s

FAILED (failures=1, errors=1)
>>>
>>> testResult.errors
[(<__main__.TestSequenceFunctions testMethod=testchoice>, 'Traceback (most recent call last):\n  File "<stdin>"
, line 11, in testchoice\nZeroDivisionError: integer division or modulo by zero\n')]
>>>
>>> testResult.failures
[(<__main__.TestSequenceFunctions testMethod=testshuffle>, 'Traceback (most recent call last):\n  File "<stdin>
", line 8, in testshuffle\nAssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n')]
>>>

5

Başka bir seçenek - testin başarısız olduğu bir hata ayıklayıcıyı başlatın.

Testlerinizi Testoob ile çalıştırmayı deneyin (birim test paketinizi değişiklik yapmadan çalıştırır) ve bir test başarısız olduğunda bir hata ayıklayıcı açmak için '--debug' komut satırı anahtarını kullanabilirsiniz.

İşte Windows'ta bir terminal oturumu:

C:\work> testoob tests.py --debug
F
Debugging for failure in test: test_foo (tests.MyTests.test_foo)
> c:\python25\lib\unittest.py(334)failUnlessEqual()
-> (msg or '%r != %r' % (first, second))
(Pdb) up
> c:\work\tests.py(6)test_foo()
-> self.assertEqual(x, y)
(Pdb) l
  1     from unittest import TestCase
  2     class MyTests(TestCase):
  3       def test_foo(self):
  4         x = 1
  5         y = 2
  6  ->     self.assertEqual(x, y)
[EOF]
(Pdb)

2
Burun ( nose.readthedocs.org/en/latest/index.html ), 'hata ayıklayıcı oturumu başlat' seçenekleri sağlayan başka bir çerçevedir. Çıktı yemeyen, ilk hatadan sonra duran ve istisnalar ve test hatalarında pdb'ye düşen '-sx --pdb --pdb-failures' ile çalıştırıyorum. Bu, tembel olmadığım ve bir döngü içinde test etmediğim sürece zengin hata mesajlarına olan ihtiyacımı ortadan kaldırdı.
jwhitlock

5

Kullandığım yöntem gerçekten çok basit. Bunu bir uyarı olarak kaydediyorum, böylece gerçekten ortaya çıkacak.

import logging

class TestBar(unittest.TestCase):
    def runTest(self):

       #this line is important
       logging.basicConfig()
       log = logging.getLogger("LOG")

       for t1, t2 in testdata:
         f = Foo(t1)
         self.assertEqual(f.bar(t2), 2)
         log.warning(t1)

Test başarılı olursa bu işe yarar mı? Benim durumumda uyarı yalnızca test başarısız olursa gösteriliyor
Shreya Maria

@ShreyaMaria yes it will
Orane

5

Sanırım bunu fazla düşünüyordum. Bu işi yapan bulduğum bir yol, teşhis verilerini biriktiren küresel bir değişkene sahip olmaktır.

Bunun gibi bir şey:

log1 = dict()
class TestBar(unittest.TestCase):
    def runTest(self):
        for t1, t2 in testdata:
            f = Foo(t1) 
            if f.bar(t2) != 2: 
                log1("TestBar.runTest") = (f, t1, t2)
                self.fail("f.bar(t2) != 2")

Cevaplar için teşekkürler. Python'da birim testlerinden gelen bilgilerin nasıl kaydedileceği konusunda bana bazı alternatif fikirler verdiler.


2

Günlük kaydını kullanın:

import unittest
import logging
import inspect
import os

logging_level = logging.INFO

try:
    log_file = os.environ["LOG_FILE"]
except KeyError:
    log_file = None

def logger(stack=None):
    if not hasattr(logger, "initialized"):
        logging.basicConfig(filename=log_file, level=logging_level)
        logger.initialized = True
    if not stack:
        stack = inspect.stack()
    name = stack[1][3]
    try:
        name = stack[1][0].f_locals["self"].__class__.__name__ + "." + name
    except KeyError:
        pass
    return logging.getLogger(name)

def todo(msg):
    logger(inspect.stack()).warning("TODO: {}".format(msg))

def get_pi():
    logger().info("sorry, I know only three digits")
    return 3.14

class Test(unittest.TestCase):

    def testName(self):
        todo("use a better get_pi")
        pi = get_pi()
        logger().info("pi = {}".format(pi))
        todo("check more digits in pi")
        self.assertAlmostEqual(pi, 3.14)
        logger().debug("end of this test")
        pass

Kullanımı:

# LOG_FILE=/tmp/log python3 -m unittest LoggerDemo
.
----------------------------------------------------------------------
Ran 1 test in 0.047s

OK
# cat /tmp/log
WARNING:Test.testName:TODO: use a better get_pi
INFO:get_pi:sorry, I know only three digits
INFO:Test.testName:pi = 3.14
WARNING:Test.testName:TODO: check more digits in pi

Ayarlamazsanız LOG_FILE, günlük kaydı yapılacaktır stderr.


2

Bunun için loggingmodülü kullanabilirsiniz .

Dolayısıyla, birim test kodunda şunu kullanın:

import logging as log

def test_foo(self):
    log.debug("Some debug message.")
    log.info("Some info message.")
    log.warning("Some warning message.")
    log.error("Some error message.")

Varsayılan olarak uyarılar ve hatalar çıktı olarak verilir /dev/stderr, bu nedenle konsolda görünür olmaları gerekir.

Günlükleri özelleştirmek için (biçimlendirme gibi) aşağıdaki örneği deneyin:

# Set-up logger
if args.verbose or args.debug:
    logging.basicConfig( stream=sys.stdout )
    root = logging.getLogger()
    root.setLevel(logging.INFO if args.verbose else logging.DEBUG)
    ch = logging.StreamHandler(sys.stdout)
    ch.setLevel(logging.INFO if args.verbose else logging.DEBUG)
    ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
    root.addHandler(ch)
else:
    logging.basicConfig(stream=sys.stderr)

2

Bu durumlarda yaptığım şey, log.debug()başvurumda bazı mesajların yer almasıdır. Varsayılan günlük kaydı seviyesi olduğundan WARNING, bu tür mesajlar normal yürütmede gösterilmez.

Daha sonra, birim testinde kayıt seviyesini olarak değiştiririm DEBUG, böylece bu tür mesajlar çalıştırılırken gösterilir.

import logging

log.debug("Some messages to be shown just when debugging or unittesting")

Birim testlerinde:

# Set log level
loglevel = logging.DEBUG
logging.basicConfig(level=loglevel)



Tam bir örneğe bakın:

Bu, daikiri.pyDaikiri'yi adı ve fiyatı ile uygulayan temel bir sınıftır. make_discount()Belirli bir indirim uygulandıktan sonra o belirli daikiri'nin fiyatını döndüren bir yöntem vardır:

import logging

log = logging.getLogger(__name__)

class Daikiri(object):
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def make_discount(self, percentage):
        log.debug("Deducting discount...")  # I want to see this message
        return self.price * percentage

Ardından, test_daikiri.pykullanımını kontrol eden bir birim testi oluşturuyorum :

import unittest
import logging
from .daikiri import Daikiri


class TestDaikiri(unittest.TestCase):
    def setUp(self):
        # Changing log level to DEBUG
        loglevel = logging.DEBUG
        logging.basicConfig(level=loglevel)

        self.mydaikiri = Daikiri("cuban", 25)

    def test_drop_price(self):
        new_price = self.mydaikiri.make_discount(0)
        self.assertEqual(new_price, 0)

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

Bu yüzden onu çalıştırdığımda log.debugmesajları alıyorum :

$ python -m test_daikiri
DEBUG:daikiri:Deducting discount...
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

1

inspect.trace, bir istisna atıldıktan sonra yerel değişkenleri almanıza izin verir. Ardından, otopsi sırasında incelenmek üzere bu yerel değişkenleri kaydetmek için birim testlerini aşağıdaki gibi bir dekoratörle sarmalayabilirsiniz.

import random
import unittest
import inspect


def store_result(f):
    """
    Store the results of a test
    On success, store the return value.
    On failure, store the local variables where the exception was thrown.
    """
    def wrapped(self):
        if 'results' not in self.__dict__:
            self.results = {}
        # If a test throws an exception, store local variables in results:
        try:
            result = f(self)
        except Exception as e:
            self.results[f.__name__] = {'success':False, 'locals':inspect.trace()[-1][0].f_locals}
            raise e
        self.results[f.__name__] = {'success':True, 'result':result}
        return result
    return wrapped

def suite_results(suite):
    """
    Get all the results from a test suite
    """
    ans = {}
    for test in suite:
        if 'results' in test.__dict__:
            ans.update(test.results)
    return ans

# Example:
class TestSequenceFunctions(unittest.TestCase):

    def setUp(self):
        self.seq = range(10)

    @store_result
    def test_shuffle(self):
        # make sure the shuffled sequence does not lose any elements
        random.shuffle(self.seq)
        self.seq.sort()
        self.assertEqual(self.seq, range(10))
        # should raise an exception for an immutable sequence
        self.assertRaises(TypeError, random.shuffle, (1,2,3))
        return {1:2}

    @store_result
    def test_choice(self):
        element = random.choice(self.seq)
        self.assertTrue(element in self.seq)
        return {7:2}

    @store_result
    def test_sample(self):
        x = 799
        with self.assertRaises(ValueError):
            random.sample(self.seq, 20)
        for element in random.sample(self.seq, 5):
            self.assertTrue(element in self.seq)
        return {1:99999}


suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
unittest.TextTestRunner(verbosity=2).run(suite)

from pprint import pprint
pprint(suite_results(suite))

Son satır, testin başarılı olduğu döndürülen değerleri ve bu durumda x, başarısız olduğunda yerel değişkenleri yazdıracaktır:

{'test_choice': {'result': {7: 2}, 'success': True},
 'test_sample': {'locals': {'self': <__main__.TestSequenceFunctions testMethod=test_sample>,
                            'x': 799},
                 'success': False},
 'test_shuffle': {'result': {1: 2}, 'success': True}}

Har det gøy :-)


0

Onaylama hatasından kaynaklanan istisnayı yakalamaya ne dersiniz? Yakalama bloğunuzda, verileri istediğiniz yerde çıktısını alabilirsiniz. Sonra işin bittiğinde istisnayı yeniden atabilirsin. Test koşucusu muhtemelen farkı anlamaz.

Sorumluluk reddi: Bunu python'un birim testi çerçevesiyle denemedim, ancak diğer birim test çerçevelerinde denedim.



-1

@FC'nin yanıtını genişleterek, bu benim için oldukça iyi çalışıyor:

class MyTest(unittest.TestCase):
    def messenger(self, message):
        try:
            self.assertEqual(1, 2, msg=message)
        except AssertionError as e:      
            print "\nMESSENGER OUTPUT: %s" % str(e),
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.