ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Programming Ruby (25) 단위테스트 - 1
    Ruby 2016. 11. 10. 20:13
    [출처] Programming Ruby 
    (본 게시물은 저작권의 문제 발생시 출판사의 요청에 의해 삭제될 수 있습니다.)




    단위 테스트는 작은 코드 덩어리, 즉 일반적인 각 메서드와 메서드에 포함된 각 줄을 대상으로 테스트를 수행한다. 시스템 전체를 하나의 테스트 대상으로 보는 다른 테스트와는 대조된다. 

    소프트웨어는 결국 계층 구조를 이루고 있기 때문에 테스트 대상을 작게 잡아야 한다. 특정 위치에 있는 코드들은 그 아래 계층의 코드가 정상으로 작동해야만 문제없이 작동한다. 아래 계층의 코드에 버그가 있다면 그 위의 계층 역시 이에 영향을 받을 수 있다. 


    단위 테스트는 프로그래머가 더 좋은 코드를 작성하는 데 도움을 준다. 이는 코드를 실제로 작성하기 전에 테스트에 대해 생각해 보게 함으로써 자연스럽게 더 좋은, 더 느슨한 설계를 유도한다. 또한 코드를 작성하는 동안 코드가 얼마나 정확한지 그때그때 결과를 확인할 수 있어서 도움이 된다. 코드를 작성한 이후에도 코드가 여전히 동작하는지 확인할 수 있고 다른 사람들이 코드를 어떻게 사용하는지 이해할 수 있도록 도와준다. 


    로마 숫자(Roman number) 클래스를 테스트한다고 가정해보자. 아래의 예제는 특정 숫자를 표현하는 객체를 생성하고 그 객체를 로마 숫자 표기법으로 출력한다.



    # 이 코드는 버그가 있다.

    class Roman

        MAX_ROMAN = 4000


        def initialize(value)

            if value <= 0 | | value > MAX_ROMAN

                fail "Roman values must be > 0 and <= #{MAX_ROMAN}"

            end

            @value = value

        end


        FACTORS = [ [ "m", 1000], [ "cm", 900], ["d", 500], ["cd", 400], 

                                [ "c", 100], ["xc", 90], ["l", 50], ["xl", 40],

                                [ "x", 10], [ "ix", 9], ["v", 5], [ "iv", 4],

                                [ "i", 1]]


        def to_s

            value = @value

            roman = ""

            for code, factor in FACTORS

                count, value = value.divmod(factor)

                roman < code unless count.zero?

            end

            roman 

        end

    end


    위의 코드를 테스트하기 위해 다음과 같은 코드를 하나 더 작성했다.


    require_relative 'romanbug'


    r = Roman.new(1)

    fail " 'i' exprected" unless r.to_s == "i"


    r = Roman.new(9)

    fail " 'ix' expected" unless r.to_s == "ix"



    위와 같은 테스트코드는 테스트 개수가 늘어나면 너무 복잡해져 관리가 어려울 수 있다. 테스트 과정을 구조화 하기 위한 다양한 단위 테스트 프레임워크가 등작했고 루비 배포판에는 이미 라이언 데이비스(Ryan Davis)의 MiniTest가 포함되어 있다. 


    Minitest는 이전의 Test::Unit과 거의 같지만 필수적이지 않은 테스트케이스 러너, GUI 지원 등이 제거되었다. 이 블로그에  포스팅되는 테스트는 Test::Unit 래퍼를 사용한다. 




    테스트 프레임워크


    테스트 프레임워크 기본적인 기능 세가지


    1) 개별 테스트를 표현하는 방법을 제공

    2) 테스트를 구조화하는 프레임워크 제공

    3) 테스트를 수행하기 위한 유연한 방법 제공



    단언(assertion) == 기대 결과


    테스트 안에서 if 문을 길게 나열하는 것을 피하기 위해 테스트 프레임워크는 같은 일을 하는 데 단언(assertion)을 사용한다. 단언문 형식은 다양하지만 이것들을 따르는 패턴은 기본적으로 같다. 단언문은 기대되는 결과를 적을 수 있는 방법과 실제 결과를 전달하는 방법을 제공한다. 실제 결과와 기대한 결과가 다르다면, 단언문은 멋진 문구와 함께 이 사실을 실패(failure)로 기록한다.


    위의 예제인 Roman 클레스 테스트를 다시 작성한 예제이다. 단지 assert_equal 메서드만을 살펴보라.


    require_relative 'romanbug'

    require 'test/unit'

    class TestRoman < Test::Unit::TestCase

        

        def test_simple

            assert_equal("i", Roman.new(1).to_s)

            assert_equal("ix", Roman.new(9).to_s)

        end


    end



    실행결과

    Run options:

    # Running tests:

    .

    Finished tests in 0.0000372s, 296.6479 tests/s, 593.2958 assertions/s.

    1 tests, 2 assertions, 0 failures, 0 erros, 0 skips



    생성자는 넘겨받은 숫자를 로마 숫자로 표현할 수 있는지 확인하고, 만일 표현할 수 없다면 예외를 던진다. 이런 예외를 테스트해보자.


    require_relative 'roman3'

    require 'test/unit'


    class TestRoman < Test::Unit::TestCase

        NUMBERS = {1 => "i", 2 => "ii", 3 => "iii", 4 => "iv", 5 => "v", 9 => "ix" }


        def test_simple

            NUMBERS.each do | arabic, roman |

                r = Roman.new(arabic)

                assert_equal(roman, r.to_s)

            end

        end

        

        def test_range

            # 아래 두 줄에서는 예외가 발상하지 않는다.

            Roman.new(1)

            Roman.new(4999)

            # 아래 두줄에서는 예외가 발생한다.

            assert_raises(RuntimeError) { Roman.new(0) } 

            assert_raises(RuntimeError) { Roman.new(5000) }

        end

    end


    실행결과

    Run options:

    #Running tests:

    ..

    Finished tests in 0.003058s, blabla

    2 tests, 8 assertions, 0 failures, 0errors, 0 skips


    assert_euqal의 부정 단언문은 refute_equal이다. 각 단언문에 넘겨지는 마지막 매개 변수는 모든 실패 메시지 이전에 출력할 메시지다. 실패 메시지는 거의가 확인할 수 있기 때문에 이 매개변수를 사용하지 않지만, 실패 메시지가 필요한 한가지 예외는 refute_nil이다.


    require 'test/unit'

    class ATestThatFails < Test::Unit::TestCase

        def test_user_created

            user = User.find(1)

            refute_nil(user, "User with ID=1 should exist")

        end

    end


    실행결과

    Run options:

    # Running tests:

    F

    Finished test

        1) Failure:

    ATestThatFails#test_user_created [prog.rb:11]:

    User with ID=1 should exist.

    Excepted nil to not be nil.


    1 tests, 1 assertions, 1 failures, 0 erros, 0 skips



    끄읕.



Designed by Tistory.