完全なコードカバレッジ

単体テストを議論するとき、テストで完全なコードカバレッジを行う必要があるかどうかは、かなり頻繁で議論のあるトピックです。 ほとんどの開発者は、それを行う必要はなく、非効率的で役に立たないと考える傾向がありますが、私は反対意見を持っています(少なくともPythonで開発する場合)。 この記事では、コードを完全にカバーする方法の例を示し、開発経験に基づいた完全なカバーの欠点と利点を説明します。



鼻テストツール



単体テストと統計の収集には、 noseを使用します 。 他の手段と比較した場合の利点:



noseをインストールしても問題は発生しません。easy_installを介してインストールされるか、ほとんどのLinuxリポジトリにインストールされるか、ソースから簡単にインストールできます。 Python 3の場合、py3kブランチのクローンを作成し、ソースからインストールする必要があります。



初期コード例



階乗計算がテストされます:

#!/usr/bin/env python

import operator



def factorial (n):

if n < 0 :

raise ValueError ( "Factorial can't be calculated for negative numbers." )

if type (n) is float or type (n) is complex :

raise TypeError ( "Factorial doesn't use Gamma function." )

if n == 0 :

return 1

return reduce (operator . mul, range ( 1 , n + 1 ))



if __name__ == '__main__' :

n = input ( 'Enter the positive number: ' )

print '{0}! = {1}' . format(n, factorial( int (n)))







コードはPython 2.6でのみ機能し、Python 3との互換性はありません。コードはmain.pyファイルに保存されます。



単体テスト





簡単なテストから始めましょう:

import unittest

from main import factorial



class TestFactorial (unittest . TestCase):



def test_calculation ( self ):

self . assertEqual( 720 , factorial( 6 ))



def test_negative ( self ):

self . assertRaises( ValueError , factorial, -1 )



def test_float ( self ):

self . assertRaises( TypeError , factorial, 1.25 )



def test_zero ( self ):

self . assertEqual( 1 , factorial( 0 ))







これらのテストは機能のみをテストします。 コードカバレッジ-83%:

$ nosetests --with-coverage --cover-erase

....

Name Stmts Exec Cover Missing

-------------------------------------

main 12 10 83% 16-17

----------------------------------------------------------------------

Ran 4 tests in 0.021s



OK







100%のカバレッジのために別のクラスを追加します。

class TestMain (unittest . TestCase):



class FakeStream :



def __init__ ( self ):

self . msgs = []



def write ( self , msg):

self . msgs . append(msg)



def readline ( self ):

return '5'



def test_use_case ( self ):

fake_stream = self . FakeStream()

try :

sys . stdin = sys . stdout = fake_stream

execfile ( 'main.py' , { '__name__' : '__main__' })

self . assertEqual( '5! = 120' , fake_stream . msgs[ 1 ])

finally :

sys . stdin = sys . __stdin__

sys . stdout = sys . __stdout__







これで、コードはテストで完全にカバーされます。

$ nosetests --with-coverage --cover-erase

.....

Name Stmts Exec Cover Missing

-------------------------------------

main 12 12 100%

----------------------------------------------------------------------

Ran 5 tests in 0.032s



OK







結論



これで、実際のコードに基づいて、いくつかの結論を導き出すことができます。



Python 3への適応



Python 3の適応を使用して、コードの完全なカバレッジが作業にどのように役立つかを示したいと思います。 したがって、最初にPython 3でプログラムを実行すると、構文エラーがスローされます。

$ python3 main.py

File "main.py", line 17

print '{0}! = {1}'.format(n, factorial(int(n)))

^

SyntaxError: invalid syntax







修正します:

#!/usr/bin/env python

import operator



def factorial (n):

if n < 0 :

raise ValueError ( "Factorial can't be calculated for negative numbers." )

if type (n) is float or type (n) is complex :

raise TypeError ( "Factorial doesn't use Gamma function." )

if n == 0 :

return 1

return reduce (operator . mul, range ( 1 , n + 1 ))



if __name__ == '__main__' :

n = input ( 'Enter the positive number: ' )

print ( '{0}! = {1}' . format(n, factorial( int (n))))







これで、プログラムを起動できます。

$ python3 main.py

Enter the positive number: 0

0! = 1







これは、プログラムが機能しているということですか? いや! reduceが呼び出されるまでのみ機能します。テストから次のことがわかります。

$ nosetests3

E...E

======================================================================

ERROR: test_calculation (tests.TestFactorial)

----------------------------------------------------------------------

Traceback (most recent call last):

File "/home/nuald/workspace/factorial/tests.py", line 9, in test_calculation

self.assertEqual(720, factorial(6))

File "/home/nuald/workspace/factorial/main.py", line 12, in factorial

return reduce(operator.mul, range(1, n + 1))

NameError: global name 'reduce' is not defined



======================================================================

ERROR: test_use_case (tests.TestMain)

----------------------------------------------------------------------

Traceback (most recent call last):

File "/home/nuald/workspace/factorial/tests.py", line 38, in test_use_case

execfile('main.py', {'__name__': '__main__'})

NameError: global name 'execfile' is not defined



----------------------------------------------------------------------

Ran 5 tests in 0.010s



FAILED (errors=2)







この例では、これはすべて手動テストで検出できます。 ただし、大規模なプロジェクトでは、この種のエラーの検出に役立つのは単体テストのみです。 また、コードを完全に網羅することでのみ、コードとAPIのほぼすべての不整合が解決されたことを保証できます。



実は、実際のコードはPython 2.6とPython 3の間で完全に互換性があります:

#!/usr/bin/env python

import operator

from functools import reduce



def factorial (n):

if n < 0 :

raise ValueError ( "Factorial can't be calculated for negative numbers." )

if type (n) is float or type (n) is complex :

raise TypeError ( "Factorial doesn't use Gamma function." )

if n == 0 :

return 1

return reduce (operator . mul, range ( 1 , n + 1 ))



if __name__ == '__main__' :

n = input ( 'Enter the positive number: ' )

print ( '{0}! = {1}' . format(n, factorial( int (n))))









import sys

import unittest

from main import factorial



class TestFactorial (unittest . TestCase):



def test_calculation ( self ):

self . assertEqual( 720 , factorial( 6 ))



def test_negative ( self ):

self . assertRaises( ValueError , factorial, -1 )



def test_float ( self ):

self . assertRaises( TypeError , factorial, 1.25 )



def test_zero ( self ):

self . assertEqual( 1 , factorial( 0 ))



class TestMain (unittest . TestCase):



class FakeStream :



def __init__ ( self ):

self . msgs = []



def write ( self , msg):

self . msgs . append(msg)



def readline ( self ):

return '5'



def test_use_case ( self ):

fake_stream = self . FakeStream()

try :

sys . stdin = sys . stdout = fake_stream

obj_code = compile ( open ( 'main.py' ) . read(), 'main.py' , 'exec' )

exec (obj_code, { '__name__' : '__main__' })

self . assertEqual( '5! = 120' , fake_stream . msgs[ 1 ])

finally :

sys . stdin = sys . __stdin__

sys . stdout = sys . __stdout__









テストでは、さまざまなバージョンのPythonでのプログラムの完全なカバレッジとパフォーマンスが示されます。

$ nosetests --with-coverage --cover-erase

.....

Name Stmts Exec Cover Missing

-------------------------------------

main 13 13 100%

----------------------------------------------------------------------

Ran 5 tests in 0.038s



OK

$ nosetests3 --with-coverage --cover-erase

.....

Name Stmts Miss Cover Missing

-------------------------------------

main 13 0 100%

----------------------------------------------------------------------

Ran 5 tests in 0.018s



OK







おわりに



完全なコードカバレッジは、プログラムエラーから保護できる万能薬ではありません。 ただし、これは知って使用する必要があるツールです。 完全なカバレッジには多くの利点がありますが、本質的に1つの欠点しかありません。テストを書くのに必要な時間とリソースです。 ただし、テストを記述するほど、将来的にテストが簡単になります。 私たちのプロジェクトでは、1年以上にわたってコードを100%網羅しています。最初は多くの問題がありましたが、コードを完全に網羅することはまったく問題ではありません。 すべてのメソッドが完成し、すべての必要なパッケージが作成されました。 ここには魔法はありませんが(Pythonの魔法を使用する必要があります)、開始するだけです。

PS Fullカバレッジにはもう1つの利点があります。これは完全に明確ではありませんが、自分がプロであると考える人にとっては間違いなく重要です。 この種の知識は、すべての人、特にライブラリ開発者に役立ちます。



All Articles