쓰기 테스트와 BDD

9405 단어 bddtesting
이 글은 부작용이 없는 환경 모듈 테스트를 중점적으로 소개할 것이다. 코드는 어떤 프로그래밍 언어로 작성되지 않았다.: P

간단한 소개


우선 테스트를 작성하는 간단한 방법을 살펴보자. 나는 사람들이 현재 통상적으로 이렇게 하는 것을 보았다
모듈(또는 클래스) 카트가 있다고 가정해 보세요.
우리는 상태를 사유로 봉인하는 OO 개념을 따르고 공공 방법을 사용할 것이다
module Cart
  private items

  public fn isEmpty()
  public fn addItem(item)
  public fn checkOut()
간단히 말하면, 우리는 상품을 문자열 "A", "B"로 삼아, 계산할 때 구매 상품의 양식 목록만 인쇄할 것이다
> cart.addItem("A")
> cart.checkout()
Order: A
> cart.addItem("B")
> cart.checkout()
Order: A, B
이제 함수마다 테스트를 작성해 보도록 하겠습니다. 아주 간단한 첫 번째 테스트부터isEmpty()
module CartTest
  describe "isEmpty()"
    test "it return true when no item"
      cart = new Cart()
      assert cart.isEmpty() == true
이것은 매우 간단하다.다음 방법 보여주세요addItem()
describe "addItem()"
  test "it return ..."

더욱 복잡하다


우리는 이미 테스트된 이름에 곤란해졌다.이 방법은 어떤 결과도 반환하지 않고 카트에 항목을 추가하기만 하면 됩니다.
부울 값을 반환하여 성공/실패의 결과를 나타낼 수 있지만 이것은 우리가 기대하는 방법이 아닙니다. 즉, 실제 상품을 카트에 넣는 것입니다.
우리는 결과를 검사하고 싶지 않고 카트의 물품 상태를 검사하고 싶지만 items 속성은 사유이다
가장 쉬운 방법은요.
module Cart
-  private items
+  public items
또는
module Cart
+  public fn getItems()
우리는 프로젝트를 공개 범위로 변경할 것이다.이제 저희가 테스트를 해볼 수 있어요.
describe "addItem()"
  test "it add item to cart's items list"
    cart = new Cart()
    assert cart.items == []   
    cart.addItem("A")
    assert cart.items == ["A"]
그러나 봉인 개념을 깨고 테스트를 백합 테스트로 만들었다.우리는 블랙박스 방법을 더욱 좋아한다. 왜냐하면 우리는 모듈의 세부 사항과 논리가 자신을 뛰어넘어 이해하기 어려워지는 것을 원하지 않기 때문이다
!
ORM 모델로 사용하고 데이터베이스 행이 이 문제를 해결했다고 단언하는 것은 아니다
이대로 다음 테스트를 보도록 하겠습니다.
우리가 작성한 isEmpty() 테스트는 모든 가능한 상황을 포함하지 않았다.그 밖에 우리는 addItem()와 여러 항목을 테스트하고 싶다.
우리는 이미 추가된 프로젝트 설정 테스트를 사용해야 한다.누군가가 이 해결 방안을 생각해 낼 수도 있다
describe "isEmpty()"
  test "it return true when no item"
    ...

  test "it return false when cart have items"
    cart = new Cart()
    cart.items = ["A"]
    assert cart.isEmpty() == false

describe "addItem()"
  test "it add item to cart"
    ...

  test "it can add more item to cart"
    cart = new Cart()
    cart.items = ["A"]
    cart.addItem("B")
    assert cart.items == ["A", "B"]
항목을 직접 설정하거나 public fn setItems를 만들어 설정합니다.너는 하나의 테스트를 분리해서 그것의 테스트 방법만 고려할 수 있다.
또한 일부 방법이 더 많은 사전 설정 상태를 필요로 한다면 직접 상태를 설정하는 것이 가장 빠른 방법이다.
문제는 모듈의 실제 사용 상황이 아니라는 점이다.직접 상태를 설정하면 '존재하지 말아야 할 상태' 가 될 수 있습니다. 이것은 실제 코드에 존재하지 않는 것을 테스트하고 있음을 의미합니다.
어리석다
  module Cart
    public isActive
    public isDelete

    public fn activate()
      this.isActive = true
      this.isDelete = false

  public fn delete()
      this.isActive = false
      this.isDelete = true
퍼블릭 방법을 사용하면 두 표지가 같은 진/가 상태가 생기지 않습니다.하지만 수동으로 설정하면
cart = new Cart()
cart.isActive = true
cart.isDelete = true
마찬가지로 앞에서 말한 바와 같이 상태는 모듈 밖으로 노출되어서는 안 된다.그래서 이런 설정은 우리가 원하는 방식이 아니다.
!
ORM 모델로 하고 데이터베이스 줄을 미리 삽입하는 것은 이 문제를 해결했다는 것을 의미하지 않는다

한 걸음 뒤로 물러서다


코드와 조작 방법만 알고 싶으면 이 부분을 건너뛰십시오: P
지금 저희가 두 가지 문제가 있어요.
저희가 이 상태를 폭로하고 싶지 않아요.
  • 우리는 예상태를 설정하고 싶지만 공개하고 싶지 않다
  • 이것은 해결할 수 없는 역설처럼 느껴질 수도 있다.하지만 한 걸음 뒤로 물러나 모듈과 방법, 상태의 전체적인 상황을 살펴봅시다.
    만약 우리가 보일러 기계를 건설한다면 상상해 보세요.
    우리는 이 기계가 버튼을 켜지 않은 상태에서 끓는 물을 끓일 수 있는지 시험하기 위해 가열기 부품에 전원을 연결하고 싶지 않을 수도 있다.
    이런 상황에서, 우리는 조립품이 아니라 완전한 기계를 시험하기를 바란다
    모듈도 마찬가지다. 모든 방법이 단독으로 존재할 수 없고 하나의 상태도로 함께 작업할 때가 많다.

    이 예시 상태 그래프를 보세요.
    모든 전환이 초기 상태에서 추출된 것은 아니기 때문에 이전의 전환이 없는 상황에서 일을 해서는 안 되고, 전환이 없는 상태에서 어떤 상태로 넘어가서는 안 된다
    코드 모듈에 적용합니다. 다른 방법의 설정이 없으면 모든 방법이 작동할 수 없고 상태를 직접 설정할 수 없습니다.
    이를 위해 단일 테스트 방법을 잊고 전체 모듈 또는 BDD 동작을 테스트하는 데 전념하시기 바랍니다
    예, BDD는 단순히 테스트 이름을 강제로 지정하는 것이 아니라 최고 수준의 엔드 투 엔드 테스트나 기능 테스트를 수행할 필요가 없습니다.

    BDD 해달래요.


    위의 예에서 알 수 있듯이 테스트 단일 방법은 봉인된 블랙박스 논리와 함께 잘 작동하지 못한다. 왜냐하면 전체 모듈은 단일 방법으로만 사용하는 것을 의미하지 않기 때문이다.
    우리가 발견한 문제를 다시 한 번 봅시다.
    describe "addItem()"
      test "it add item to cart's items list"
        cart = new Cart()
        assert cart.items == []   
        cart.addItem("A")
        assert cart.items == ["A"]
    
    우리는 addItem()의 결과를 단독으로 검증할 수 없지만 다른 측면에서 볼 때 addItem()는 실제로checkout()의 통로이기 때문이다.그래서 저희가 다 같이 테스트를 해보도록 하겠습니다.
    describe "checkout()"
      test "it print order with single item"
        cart = new Cart()
        cart.addItem("A")
        assert cart.checkout == "Order: A"
    
    우리는 addItem() 배후에서 무엇을 했는지 묘사하고 싶지 않다.우리는 우리가 이전에 그것을 필요로 했던 것만 알 뿐이다. checkout()그래서 테스트에 list나 내부 물건이 전혀 없었어요.
    다음 문제는 사전 상태 설정에 관한 것입니다. 마찬가지로, 우리는addItem을 함께 사용할 수 있습니다
    describe "isEmpty()"
      test "it return false when cart have items"
        cart = new Cart()
        cart.addItem("A")
        assert cart.isEmpty() == false
    
    describe "checkout()"
      test "it print order with single item"
        ...
    
      test "it print order with multiple item"
        cart = new Cart()
        cart.addItem("A")
        cart.addItem("B")
        assert cart.checkout == "Order: A, B"
    
    우리는 테스트의 중점을 단일 방법에 두지 않기 때문에 테스트에서 describe라는 방법의 테스트를 삭제하고 일부 테스트의 이름을 바꾸도록 합니다
    module CartTest
    - describe "isEmpty()"
        test "new cart is empty"
        test "cart with single item is not empty"
        test "cart with multiple items is not empty"
    
    - describe "checkout()"
        test "new cart cannot checkout"
        test "print order from cart with single item"
        test "print order from cart with multiple items"
    
    우리는 몇 가지 모델을 볼 수 있다.각 (삭제된) 설명 새 카트의 범위는 단일 항목과 여러 항목을 포함한 3개의 테스트가 있습니다.팀을 나눠보도록 하겠습니다.
    describe "new cart"
      cart = new Cart()
      test "should be empty"
      test "cannot checkout"
    
    describe "cart with single item"
      cart = new Cart()
      cart.addItem("A")
      test "should not be empty"
      test "should print order with single item"
    
    describe "cart with multiple items"
      cart = new Cart()
      cart.addItem("A")
      cart.addItem("B")
      test "should not be empty"
      test "should print order with multiple items"
    
    그것들을 한데 조합함으로써 우리는 비교적 짧은 명칭을 얻었을 뿐만 아니라.우리는 또한 모든 설명 블록의 설정 코드를 공유할 수 있다. 왜냐하면 그것은 같은 상태에서 테스트한 것이기 때문이다
    만약 네가 충분히 노력한다면, 너는 주어진 when-then 모드로 그것을 표현할 수 있다.
    given new cart
      when check empty
        then it return true 
      when checkout
        then it return error
    
    given 설정 상태로 직접 매핑when Dell이 수행하는 방법 또는 작업then 예상 결과 매핑
    나는 이 테스트 모드가 이전의 모든 방법 모드보다 이런 GWT 포맷에 더욱 쉽게 비치는 것으로 생각한다. 왜냐하면 우리는 실제Behavior 테스트 모듈을 통과했기 때문에 BDD를 사용하기 때문이다.

    요약

  • 모든 방법의 테스트는 매우 어렵다. 왜냐하면 일부 방법은 자동으로 완성되지 않기 때문이다
  • 반대로 우리는 공공 방법(블랙박스)만 사용하여 전체 모듈의 행위를 테스트하기를 희망한다
  • 설정 코드를 공유하기 위해 미리 설정된 상태로 그룹을 나누어 테스트합니다
  • 이 테스트는 실제 세계의 용례를 더욱 잘 반영한다
  • !
    이 예는 결코 이상적이지 않을 수도 있다.UI가 목록을 표시해야 하기 때문에 실제 상황에서, 우리는 items가 개인적이기를 원하지 않을 수도 있습니다.
    그리고 계산은 사실상 카트(또 다른 서비스/모듈) 이외에 해야 한다. 의존관계는 Order -> Cart해야 카트가 하느님의 대상이 되지 않기 때문이다.

    초과의


    필요하면 매번 여러 개itthen를 테스트하는 것을 권장합니다.이렇게 하면 모듈의 행위를 더욱 쉽게 묘사할 수 있다
    예컨대

    when cancel an order, mark status as 'canceled', update timestamp and fire event order canceled


    상태에 따라 그룹을 나누고 테스트 이름에 논리를 설명하더라도 테스트 이름과 본문이 너무 길어질 수 있습니다
    describe "created order"
      it "cancel order mark status as 'canceled', update timestamp and fire event"
    
      // and because its very long, we most likely do
      it "can cancel successfully"
    
    더 좋은 예: 행위, 묘사적 명칭에 따라 테스트를 하고 단언마다 분리한다
    describe "created order"
      describe "when cancel"
        test "it mark status as 'canceled"
        test "it update timestamp"
        test "it fire event"
    
    각 주제를 분리test/it/then하면 새로운 수요를 추가하거나 기존의 수요를 조정하기 쉽고 테스트 보고서는 매우 깨끗해 보이며 코드의 행위를 더욱 잘 설명할 수 있다.

    좋은 웹페이지 즐겨찾기