İle ifadesinde kullanılan bir açık alay etmeyi nasıl yaparım (Python'da Mock çerçevesini kullanarak)?


188

Aşağıdaki kodu alaylarla nasıl test edebilirim (alay, yama dekoratörü ve Michael Foord'un Mock çerçevesi tarafından sağlanan nöbetçi kullanarak ):

def testme(filepath):
    with open(filepath, 'r') as f:
        return f.read()

@Daryl Spitzer: Meta-soruyu bırakabilir misiniz ("Cevabı biliyorum ...") Kafa karıştırıcı.
S.Lott

Geçmişte bıraktığımda, insanlar kendi sorumu cevapladığımdan şikayet ettiler. Bunu cevabıma taşımayı deneyeceğim.
Daryl Spitzer

1
@Daryl: Genellikle "karma whoring" endişelerinden kaynaklanan bir kişinin kendi sorusunu cevaplama şikayetlerinden kaçınmanın en iyi yolu, soruyu işaretlemek ve / veya bir "topluluk wiki" olarak cevaplamaktır.
John Millikin

3
Eğer kendi sorunuzu cevaplayan Karma Whoring olarak kabul edilirse, SSS bence bu noktada netleştirilmelidir.
EBGreen

Yanıtlar:


132

Bunu yapmanın yolu, özellikle MagicMock'u kullanarak python protokol yöntemlerini (sihirli yöntemler) taklit etmeyi destekleyen sahte 0.7.0'da değişti:

http://www.voidspace.org.uk/python/mock/magicmock.html

Bağlam yöneticisi olarak açık alaycı örneği (alay dokümanındaki örnekler sayfasından):

>>> open_name = '%s.open' % __name__
>>> with patch(open_name, create=True) as mock_open:
...     mock_open.return_value = MagicMock(spec=file)
...
...     with open('/some/path', 'w') as f:
...         f.write('something')
...
<mock.Mock object at 0x...>
>>> file_handle = mock_open.return_value.__enter__.return_value
>>> file_handle.write.assert_called_with('something')

Vaov! Bu, şu anda voidspace.org.uk/python/mock/magicmock.html adresinde bulunan __enter__ve __exit__nesneleri açıkça ayarlayan ve alay eden bağlam yöneticisi örneğinden çok daha basit görünüyor - ikinci yaklaşım güncel değil mi, yoksa hala faydalı mı?
Brandon Rhodes

6
"İkinci yaklaşım" bunu nasıl gösteriyor olmadan (yani o Mock sihirli yöntemlerini destekler nasıl sadece bir örnektir) bir MagicMock kullanarak. Bir MagicMock (yukarıdaki gibi) kullanıyorsanız, giriş ve çıkış sizin için önceden yapılandırılmıştır.
fuzzyman

5
Eğer işaret olabilir blog yayınına neden / nasıl çalışır daha detaylı olarak açıklamak nereye
Rodrigue

9
Python 3'te, 'dosya' tanımlanmamıştır (MagicMock özelliklerinde kullanılır), bunun yerine io.IOBase kullanıyorum.
Jonathan Hartley

1
Not: Python3'te yerleşik filegitti!
exhuma

239

mock_openmockçerçevenin bir parçasıdır ve kullanımı çok basittir. patchbağlam olarak kullanıldığında yamalı olanı değiştirmek için kullanılan nesneyi döndürür: bunu testinizi basitleştirmek için kullanabilirsiniz.

Python 3.x

Yerine builtinskullanın __builtin__.

from unittest.mock import patch, mock_open
with patch("builtins.open", mock_open(read_data="data")) as mock_file:
    assert open("path/to/open").read() == "data"
    mock_file.assert_called_with("path/to/open")

Python 2.7

mockbir parçası değil unittestve yama yapmalısın__builtin__

from mock import patch, mock_open
with patch("__builtin__.open", mock_open(read_data="data")) as mock_file:
    assert open("path/to/open").read() == "data"
    mock_file.assert_called_with("path/to/open")

Dekoratör çantası

Eğer patchdekoratör olarak mock_open()'s sonucunu kullanarak kullanırsanız new patchargümanı biraz tuhaf olabilir.

Bu durumda, new_callable patch'argümanını kullanmak ve kullanmayan her fazladan argümanın dokümantasyonda açıklandığı gibi işlev patchgöreceğini hatırlamak daha iyidir .new_callablepatch

patch (), rastgele anahtar kelime bağımsız değişkenleri alır. Bunlar inşaatta Mock'a (veya new_callable) aktarılacaktır.

Örneğin Python 3.x için dekore edilmiş sürüm :

@patch("builtins.open", new_callable=mock_open, read_data="data")
def test_patch(mock_file):
    assert open("path/to/open").read() == "data"
    mock_file.assert_called_with("path/to/open")

Bu durumda patchsahte nesneyi test fonksiyonunuzun argümanı olarak ekleyeceğini unutmayın .


Sormak için üzgünüm, with patch("builtins.open", mock_open(read_data="data")) as mock_file:dekoratör sözdizimine dönüştürülebilir mi? Denedim, ama @patch("builtins.open", ...) ikinci argüman olarak neye girmem gerektiğinden emin değilim .
imrek

1
@DrunkenMaster Güncellemesi .. işaret ettiğin için teşekkürler. Bu durumda dekoratör kullanmak önemsiz değildir.
Michele d'Amico

Grazie Benim sorunum (ı kanalize etmek zorunda biraz daha karmaşık return_valuebir mock_openbaşka sahte nesnesine ve ikinci alay en assert return_value), ancak ekleyerek çalıştı mock_openolarak new_callable.
imrek

1
@ArthurZopellaro six, tutarlı bir mockmodüle sahip olmak için modüle bakın. Ama builtinsortak bir modülde de eşleşip eşlenmediğini bilmiyorum .
Michele d'Amico

1
Düzeltilecek doğru adı nasıl buluyorsunuz? Yani rastgele bir işlev için @patch (bu durumda 'builtins.open') ile ilgili ilk argümanı nasıl buluyorsunuz?
zenperttu

73

Mock'un en son sürümleriyle, gerçekten yararlı mock_open yardımcısını kullanabilirsiniz:

mock_open (mock = Yok, read_data = Yok)

Açık kullanım yerine alay oluşturmak için bir yardımcı işlevi. Doğrudan çağrılan veya içerik yöneticisi olarak kullanılan açık işler için çalışır.

Sahte argüman yapılandırılacak sahte nesnedir. Hiçbiri (varsayılan) değilse, API standart dosya tanıtıcılarında kullanılabilen yöntem veya özniteliklerle sınırlı olacak şekilde bir MagicMock oluşturulacaktır.

read_data, dosya tanıtıcısının okuma yönteminin döndüreceği bir dizedir. Bu, varsayılan olarak boş bir dizedir.

>>> from mock import mock_open, patch
>>> m = mock_open()
>>> with patch('{}.open'.format(__name__), m, create=True):
...    with open('foo', 'w') as h:
...        h.write('some stuff')

>>> m.assert_called_once_with('foo', 'w')
>>> handle = m()
>>> handle.write.assert_called_once_with('some stuff')

birden fazla .writeçağrı olup olmadığını nasıl kontrol edersiniz ?
n611x007

1
@naxa Bir yol, beklenen her parametreyi iletmektir handle.write.assert_any_call(). handle.write.call_args_listSipariş önemliyse her aramayı almak için de kullanabilirsiniz .
Rob Cutmore

m.return_value.write.assert_called_once_with('some stuff')daha iyi imo. Çağrı kaydetmekten kaçınır.
İsimsiz

2
Manuel olarak iddia Mock.call_args_listetmek Mock.assert_xxx, yöntemlerden herhangi birini çağırmaktan daha güvenlidir . Eğer Mock'un nitelikleri olan ikincisini yanlış hecelerseniz, her zaman sessizce geçerler.
Jonathan Hartley

12

Basit bir dosya için mock_open kullanmak için read()( bu sayfada zaten verilen orijinal mock_open snippet'i daha çok yazmaya yöneliktir):

my_text = "some text to return when read() is called on the file object"
mocked_open_function = mock.mock_open(read_data=my_text)

with mock.patch("__builtin__.open", mocked_open_function):
    with open("any_string") as f:
        print f.read()

Not: mock_open için dokümanlara göre, bu özellikle içindir read(), bu nedenle for line in förneğin ortak kalıplarla çalışmaz .

Python 2.6.6 / alay 1.0.1 kullanır


İyi görünüyor, ancak for line in opened_file:kod türüyle çalışmak için alamıyorum . Ben uygular tekrarlanabilir StringIO ile denemeye çalıştı __iter__ve bunun yerine my_text, ama şans yok kullanarak .
Evgen

@EvgeniiPuchkaryov Bu özellikle read()sizin for line in opened_filedurumunuz için çalışmaz ;
Açıklığa

1
@EvgeniiPuchkaryov for line in f:desteği bunun yerine bir StringIO nesnesiopen() olarak dönüş değeri alay edilerek elde edilebilir .
Iskar Jarak

1
Açıklığa kavuşturmak için, bu örnekte test edilen sistem (SUT): with open("any_string") as f: print f.read()
Brad M

4

En iyi cevap faydalı ama biraz daha genişledim.

Dosya nesnenizin ( fin as f) değerini, iletilen bağımsız değişkenlere dayanarak ayarlamak istiyorsanız open(), bunu yapmanın bir yolu vardır:

def save_arg_return_data(*args, **kwargs):
    mm = MagicMock(spec=file)
    mm.__enter__.return_value = do_something_with_data(*args, **kwargs)
    return mm
m = MagicMock()
m.side_effect = save_arg_return_array_of_data

# if your open() call is in the file mymodule.animals 
# use mymodule.animals as name_of_called_file
open_name = '%s.open' % name_of_called_file

with patch(open_name, m, create=True):
    #do testing here

Temel olarak, open()bir nesneyi döndürür ve o nesneyi withçağırır __enter__().

Düzgün alay open()etmek için, sahte bir nesne döndürmek için alay etmeliyiz . Bu sahte nesne, daha sonra istediğimiz sahte veri / dosya nesnesini (dolayısıyla ) döndürmek için __enter__()çağrıyı alay etmelidir ( MagicMockbunu bizim için yapar mm.__enter__.return_value). Bunu yukarıdaki şekilde 2 alayla yapmak, geçirilen argümanları yakalamamızı ve yöntemimize open()aktarmamızı sağlar do_something_with_data.

Bir dize olarak tüm sahte bir dosyayı geçti open()ve benim do_something_with_datagibi görünüyordu:

def do_something_with_data(*args, **kwargs):
    return args[0].split("\n")

Bu, dizeyi bir listeye dönüştürür, böylece normal bir dosyada yaptığınız gibi aşağıdakileri yapabilirsiniz:

for line in file:
    #do action

Test edilen kod, dosyayı "readline" işlevini çağırarak farklı bir şekilde işlerse, "do_something_with_data" işlevinde istediğiniz öznitelikleri istediğiniz özniteliklerle döndürebilirsiniz.
user3289695

Dokunmaktan kaçınmanın bir yolu var mı __enter__? Kesinlikle önerilen bir yol daha bir kesmek gibi görünüyor.
imrek

enter , open () gibi conext yöneticilerinin nasıl yazıldığını gösterir. Alay etmek için alay etmek için "özel" şeylere erişmeniz gerektiğinden, genellikle biraz acayip olacaktır, ancak buraya girmek iserintly hacky imo değildir
theannouncer

3

Oyuna biraz geç kalmış olabilirim, ancak openyeni bir dosya oluşturmak zorunda kalmadan başka bir modülü çağırırken bu benim için çalıştı .

test.py

import unittest
from mock import Mock, patch, mock_open
from MyObj import MyObj

class TestObj(unittest.TestCase):
    open_ = mock_open()
    with patch.object(__builtin__, "open", open_):
        ref = MyObj()
        ref.save("myfile.txt")
    assert open_.call_args_list == [call("myfile.txt", "wb")]

MyObj.py

class MyObj(object):
    def save(self, filename):
        with open(filename, "wb") as f:
            f.write("sample text")

Modülün openiçindeki işlevi yamaya ekleyerek, bir dosya oluşturmadan bir dosyaya yazabilirim.__builtin__mock_open()

Not: cython kullanan bir modül kullanıyorsanız veya programınız herhangi bir şekilde cython'a bağlıysa, dosyanızın üstüne dahil ederek cython __builtin__modülünü içe aktarmanız gerekir import __builtin__. Eğer __builtin__cython kullanıyorsanız evrensel alay ile alay edemezsiniz .


Test edilen kodun çoğunluğu burada gösterildiği gibi diğer modüllerde olduğundan, bu yaklaşımın bir varyasyonu benim için çalıştı. import __builtin__Test modülüme eklediğimden emin olmalıydım. Bu makale, bu tekniğin neden iyi çalıştığını açıklığa kavuşturmaya yardımcı oldu: ichimonji10.name/blog/6
killthrush

0

Yerleşik open () işlevini unittest ile yamalamak için:

Bu, bir düzeltme ekinin bir json yapılandırmasını okuması için çalıştı.

class ObjectUnderTest:
    def __init__(self, filename: str):
        with open(filename, 'r') as f:
            dict_content = json.load(f)

Alaycı nesne, open () işlevi tarafından döndürülen io.TextIOWrapper nesnesidir

@patch("<src.where.object.is.used>.open",
        return_value=io.TextIOWrapper(io.BufferedReader(io.BytesIO(b'{"test_key": "test_value"}'))))
    def test_object_function_under_test(self, mocker):

0

Daha fazla dosyaya ihtiyacınız yoksa test yöntemini dekore edebilirsiniz:

@patch('builtins.open', mock_open(read_data="data"))
def test_testme():
    result = testeme()
    assert result == "data"
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.