2017-07-10 9 views
5

特定の形式で保存されたファイルと、そのファイルのデータに基づいてオブジェクトを作成するクラスがあります。pytestを使ってオブジェクトが正しく作成されたことを確認するには?

オブジェクトの各属性をテストすることによって、ファイル/文字列内のすべての値が正しく抽出されていることを確認します。

classlist.py

import re 

class ClassList: 
    def __init__(self, data): 
     values = re.findall('name=(.*?)\$age=(.*?)\$', data) 

     self.students = [Student(name, int(age)) for name, age in values] 

class Student: 
    def __init__(self, name, age): 
     self.name = name 
     self.age = age 

test_classlist.py

import pytest 
from classlist import ClassList 

def single_data(): 
    text = 'name=alex$age=20$' 
    return ClassList(text) 

def double_data(): 
    text = 'name=taylor$age=23$' \ 
      'name=morgan$age=25$' 
    return ClassList(text) 


@pytest.mark.parametrize('classinfo, expected', [ 
     (single_data(), ['alex']), 
     (double_data(), ['taylor', 'morgan']) 
]) 
def test_name(classinfo, expected): 
    result = [student.name for student in classinfo.students] 

    assert result == expected 

@pytest.mark.parametrize('classinfo, expected', [ 
     (single_data(), [20]), 
     (double_data(), [23, 25]) 
]) 
def test_age(classinfo, expected): 
    result = [student.age for student in classinfo.students] 

    assert result == expected 

を私はベースのオブジェクトを作成するとします。ここでは

は私がやっているの簡易版でありますさまざまなデータに適用し、それらをパラメータ化された値として使用します。

現在の設定で動作しますが、テストごとにオブジェクトを作成することについての不必要なオーバーヘッドがあります。私はそれらを一度作成したいと思います。

私は次の操作を実行しようとした場合:

... 
@pytest.fixture(scope='module') # fixture added 
def double_data(): 
    text = 'name=taylor$age=23$' \ 
      'name=morgan$age=25$' 
    return ClassList(text) 


@pytest.mark.parametrize('classinfo, expected', [ 
     (single_data, ['alex']), 
     (double_data, ['taylor', 'morgan']) #() removed 
]) 
def test_name(classinfo, expected): 
    result = [student.name for student in classinfo.students] 

    assert result == expected 
... 

AttributeError: 'function' object has no attribute 'students'

それはむしろ、固定具よりも関数を参照して...それは動作しません。

さらに、test_nametest_ageのコードはほぼ同じです。私の実際のコードでは、私は約12属性のためにこれをやっています。これは単一の関数にマージできますか?どうやって?

テストコードをクリーンアップするにはどうすればよいですか?

ありがとうございます!

編集:

私はこれが関連している感じが、私はそれが私の状況のた​​めに動作させる方法がわからないよ:Can params passed to pytest fixture be passed in as a variable?

答えて

2

各テストのためのオブジェクトを作成する不要な傍受がありますが私の現在の設定は、動作します。私はそれらを一度作成したいと思います。

これは私には不要な最適化前のようなにおいがありますが、これを気にしている場合は、モジュールレベルでテストするために、あなたのデータを作成する機能を実行するので、彼らは一度だけ実行します。例えば

... 
def single_data(): 
    text = 'name=alex$age=20$' 
    return ClassList(text) 

def double_data(): 
    text = 'name=taylor$age=23$' \ 
      'name=morgan$age=25$' 
    return ClassList(text) 


double_data_object = double_data() 

single_data_object = single_data() 


@pytest.mark.parametrize('classinfo, expected', [ 
     (single_data_object, ['alex']), 
     (double_data_object, ['taylor', 'morgan']) 
]) 
def test_name(classinfo, expected): 
    result = [student.name for student in classinfo.students] 

    assert result == expected 

@pytest.mark.parametrize('classinfo, expected', [ 
     (single_data_object, [20]), 
     (double_data_object, [23, 25]) 
]) 
def test_age(classinfo, expected): 
... 

さらに、TEST_NAMEとtest_ageのコードがほぼ同じです。 私の実際のコードでは、私は約12属性のためにこれをやっています。/ これは単一の関数にマージできますか?どうやって?

テストコードをクリーンアップするにはどうすればよいですか?

これを行うが、あなたの例から、Studentクラスにequality magic methodを提供し、あなたのコードをテストするためにそれを使用するにはいくつかの方法(また、あなたのオブジェクトの健全な表現のためのreprを追加):

class Student: 
    def __init__(self, name, age): 
     self.name = name 
     self.age = age 

    def __eq__(self, other): 
     return (self.name, self.age) == (other.name, other.age) 

    def __repr__(self): 
     return 'Student(name={}, age={})'.format(self.name, self.age) 

次に、あなたのテストは次のようになります。

@pytest.mark.parametrize('classinfo, expected', [ 
     (single_data(), [Student('alex', 20)]), 
     (double_data(), [Student('taylor', 23), Student('morgan', 25)]), 
]) 
def test_student(classinfo, expected): 
    assert classinfo.students == expected 
+0

私は平等マジックメソッドを使ってみましたが、実際のコードでは約12の属性があります(その中にはかなり長いものがあります)ので、それらを比較して問題のどこにあるのかを正確に判断することは困難です。書式を改善するだけですか?それとも良い方法がありますか? – Jake

+0

@jakeは、等価メソッドまたはユニットテストを作成する際の問題ですか?クラスを使用してデータを格納しているように聞こえる場合は、[namedtuple](https://docs.python.org/3/library/collections.html#collections.namedtuple)または[attrs](https:// pypi.python.org/pypi/attrs/17.2.0)モジュールを使用しています。これらのメソッドは無料で提供されており、これらのユースケースに適しています。 – salparadise

2

あなたは、そのクラスのオブジェクトを返す1 fixtureを追加し、その前にフィクスチャを呼び出すことができますすべてのテスト。私はいくつかの変更を行い、get_objecttest_classlist.pyに、classlist.pyはそのままです。

get_objectは、そのクラスのオブジェクトを与え、requestモジュールを介してテスト機能でそのオブジェクトを使用できます。私はそのクラスオブジェクトをrequest.instance.cobjに割り当てました。同じことがテスト機能でアクセスできます。

ClassListのオブジェクトを作成したいとお考えの場合は、私が間違っていない場合は、以下の解決策があなたのために働くはずです。これを試して。

import pytest 
from classlist import ClassList 

def single_data(): 
    text = 'name=alex$age=20$' 
    print text 
    return ClassList(text) 

def double_data(): 
    text = 'name=taylor$age=23$' \ 
      'name=morgan$age=25$' 
    return ClassList(text) 

@pytest.fixture 
def get_object(request): 
    classobj= request.getfuncargvalue('classinfo')() 
    request.instance.cobj = classobj 


class Test_clist: 

    @pytest.mark.parametrize('classinfo, expected', [ 
      (single_data, ['alex']), 
      (double_data, ['taylor', 'morgan']) #() removed 
    ]) 
    @pytest.mark.usefixtures('get_object') 
    def test_name(self,classinfo,expected,request): 
     result = [student.name for student in request.instance.cobj.students] 
     print result 
     print expected 
     assert result == expected 
+0

ニース。これは私が後にしたものです。 2つの追加質問: 1.治具にスコープを追加する必要はありませんか? 2.これが機能するには、テストがなぜクラス内になければならないのですか? – Jake

+1

1.フィクスチャのスコープを定義しないと、デフォルトのスコープは 'function'になります。2.メソッドをクラスに入れると、テストのスコープも' function'になります。 testとfixtureの両方の 'request'スコープを使うには、同じでなければなりません。 –

関連する問題