단위 테스트는 어떻게 작동합니까?


23

코드를 더 강력하게 만들고 단위 테스트에 대해 읽었지만 실제로 유용한 용도를 찾는 것이 매우 어렵다는 것을 알게되었습니다. 예를 들어, Wikipedia 예는 다음과 같습니다.

public class TestAdder {
    public void testSum() {
        Adder adder = new AdderImpl();
        assert(adder.add(1, 1) == 2);
        assert(adder.add(1, 2) == 3);
        assert(adder.add(2, 2) == 4);
        assert(adder.add(0, 0) == 0);
        assert(adder.add(-1, -2) == -3);
        assert(adder.add(-1, 1) == 0);
        assert(adder.add(1234, 988) == 2222);
    }
}

원하는 결과를 수동으로 계산하고 테스트해야하기 때문에이 테스트는 전혀 쓸모가 없다고 생각합니다.

assert(adder.add(a, b) == (a+b));

그러나 이것은 테스트에서 함수 자체를 코딩하는 것입니다. 누군가가 단위 테스트가 실제로 유용한 예를 제공 할 수 있습니까? 참고로 나는 현재 ~ 10 부울과 몇 가지 정수를 가져 와서 이것을 기반으로 int 결과를 제공하는 주로 "절차"함수를 코딩하고 있습니다. 단순히 단위 테스트를 통해 테스트. 편집 : 또한 루비 코드 (내가 만들지 않은)를 포팅하는 동안 이것이 정확해야합니다.


14
How does unit testing work?아무도 정말로 모른다 :)
yannis

30
"원하는 결과를 수동으로 계산해야합니다". 어떻게 "완전히 쓸모가 없습니까?" 그 대답이 옳다는 것을 어떻게 확신 할 수 있습니까?
S.Lott

9
@ S.Lott : 사람들은 컴퓨터를 사용하여 숫자를 크런치하고 시간을 절약하기 위해 고대에 진보라고 불렀습니다. 현대 사람들은 컴퓨터가 숫자를 크런치 할 수 있도록 시간을 보냅니다. : D
Coder

2
@ 코더 : 단위 테스트의 목적은 "숫자를 경감하고 시간을 절약하기위한 것이 아니다";)
Andres F.

7
@ lezebulon : Wikipedia의 예제는 그리 좋지 않지만 일반적으로 단위 테스트가 아닌 특정 테스트 사례의 문제입니다. 예제의 테스트 데이터의 약 절반은 새로운 것을 추가하지 않으므로 중복되지 않습니다 (그 테스트의 저자가 더 복잡한 시나리오로 무엇을 할 것인지 생각하는 것은 무섭습니다). 보다 의미있는 테스트는 최소한 다음 시나리오에서 테스트 데이터를 분할합니다. "음수를 추가 할 수 있습니까?", "0은 중립입니까?", "음수 및 양수를 추가 할 수 있습니까?"
Andres F.

답변:


26

작은 단위를 테스트하는 경우 단위 테스트는 항상 맹목적으로 명백합니다.

그 이유 add(x, y)언젠가 나중에 누군가가 들어갈 때문에도, 유닛 테스트의 언급을 얻는다는 add코드가 모든 곳에서 사용되는 추가 기능을 실현하지 처리 특별 세금 로직을 넣어.

단위 테스트는 매우 연관 원칙에 대해 많은 : A가 B를 수행하고, B는 C를 않는 경우, A는 C. "A는 C를하지"않는 높은 수준의 테스트입니다. 예를 들어 다음과 같은 합법적 인 비즈니스 코드를 고려하십시오.

public void LoginUser (string username, string password) {
    var user = db.FetchUser (username);

    if (user.Password != password)
        throw new Exception ("invalid password");

    var roles = db.FetchRoles (user);

    if (! roles.Contains ("member"))
        throw new Exception ("not a member");

    Session["user"] = user;
}

언뜻보기에 이것은 매우 명확한 목적을 가지고 있기 때문에 단위 테스트를위한 멋진 방법처럼 보입니다. 그러나 약 5 가지 다른 기능을 수행합니다. 각각의 경우는 유효하고 유효하지 않은 대소 문자를 가지며 단위 테스트를 크게 순열시킵니다. 이상적으로 이것은 더 세분화됩니다.

public void LoginUser (string username, string password) {

    var user = _userRepo.FetchValidUser (username, password);

    _rolesRepo.CheckUserForRole (user, "member");

    _localStorage.StoreValue ("user", user);
}

이제 우리는 단위로 내려갑니다. 하나의 단위 테스트는에 _userRepo대해 유효한 동작을 고려 하는 것을 신경 쓰지 않고 FetchValidUser호출됩니다. 다른 테스트를 사용하여 유효한 사용자가 정확히 무엇을 구성하는지 확인할 수 있습니다. 마찬가지로 CheckUserForRole... 당신은 역할 구조가 어떻게 보이는지 알기 위해 테스트를 분리했습니다. 또한 전체 프로그램이에 묶이지 않도록 분리했습니다 Session. 여기에 빠진 조각이 모두 다음과 같이 보일 것입니다.

class UserRepository : IUserRepository
{
    public User FetchValidUser (string username, string password)
    {
        var user = db.FetchUser (username);

        if (user.Password != password)
            throw new Exception ("invalid password");

        return user;
    }
}

class RoleRepository : IRoleRepository
{
    public void CheckUserForRole (User user, string role)
    {
        var roles = db.FetchRoles (user);

        if (! roles.Contains (role))
            throw new Exception ("not a member");
    }
}

class SessionStorage : ILocalStorage
{
    public void StoreValue (string key, object value)
    {
        Session[key] = value;
    }
}

리팩토링하여 한 번에 여러 가지 작업을 수행했습니다. 이 프로그램은 기본 구조를 찢어 버리거나 (NoSQL의 데이터베이스 계층을 버릴 수 있음 Session) 스레드 안전 또는 기타 사항이 아니라면 잠금을 매끄럽게 추가하는 방법을보다 잘 지원 합니다. 또한이 세 가지 종속성을 작성하기 위해 매우 간단한 테스트를 수행했습니다.

희망이 있습니다 :)


13

나는 현재 ~ 10 부울과 몇 개의 정수를 취하는 "절차"함수를 코딩하고 있으며 이것을 기반으로 int 결과를 제공합니다. 단순히 테스트에서 알고리즘을 다시 코딩하는 것이 유일한 단위 테스트처럼 느껴집니다

나는 당신의 절차 함수 각각이 결정 론적이라고 확신합니다. 그래서 그것은 주어진 입력 값 세트마다 특정한 int 결과를 반환합니다. 이상적으로는 특정 입력 값 세트에 대해 어떤 결과를 받아야하는지 파악할 수있는 기능 사양이 있습니다. 부족하면 특정 입력 값 세트에 대해 루비 코드 (정확하게 작동하는 것으로 추정 됨)를 실행하고 결과를 기록 할 수 있습니다. 그런 다음 결과를 테스트에 코딩해야합니다. 테스트는 코드가 실제로 올바른 것으로 알려진 결과를 생성한다는 증거 입니다 .


기존 코드를 실행하고 결과를 기록하려면 +1입니다. 이 상황에서는 아마도 실용적인 방법 일 것입니다.
MarkJ

12

다른 사람이 실제 예제를 제공하지 않은 것 같습니다.

    public void testRoman() {
        RomanNumeral numeral = new RomanNumeral();
        assert( numeral.toRoman(1) == "I" )
        assert( numeral.toRoman(4) == "IV" )
        assert( numeral.toRoman(5) == "V" )
        assert( numeral.toRoman(9) == "IX" )
        assert( numeral.toRoman(10) == "X" )
    }
    public void testSqrt() {
        assert( sqrt(4) == 2 )
        assert( sqrt(9) == 3 )
    }

당신은 말한다 :

원하는 결과를 수동으로 계산하고 테스트해야하기 때문에이 테스트는 전혀 쓸모가 없다고 생각합니다.

그러나 요점은 코딩 할 때 수동 계산을 수행 할 때 실수를 할 가능성이 훨씬 적다는 것입니다.

십진수를 로마자로 변환하는 코드에서 실수를 할 가능성이 얼마나됩니까? 가능성이 높습니다. 십진수를 로마 숫자로 직접 변환 할 때 실수를 저지를 가능성이 얼마나됩니까? 별로. 우리가 수동 계산에 대해 테스트하는 이유입니다.

제곱근 함수를 구현할 때 실수를 저지를 가능성은 얼마나됩니까? 가능성이 높습니다. 손으로 제곱근을 계산할 때 실수를 할 가능성이 얼마나됩니까? 아마 더 가능성이 높습니다. 그러나 sqrt를 사용하면 계산기를 사용하여 답을 얻을 수 있습니다.

참고로 나는 현재 ~ 10 부울과 몇 가지 정수를 가져 와서 이것을 기반으로 int 결과를 제공하는 주로 "절차"함수를 코딩하고 있습니다. 단순히 단위 테스트를 통해 테스트

저는 여기서 무슨 일이 일어나고 있는지 추측 할 것입니다. 함수는 다소 복잡하므로 입력에서 출력이 무엇인지 파악하기가 어렵습니다. 그렇게하려면 출력이 무엇인지 알아 내기 위해 수동으로 함수를 실행해야합니다. 당연히, 그것은 쓸모없고 오류가 발생하기 쉬운 것 같습니다.

열쇠는 올바른 출력을 찾으려는 것입니다. 그러나 올바른 것으로 알려진 출력에 대해 해당 출력을 테스트해야합니다. 그것을 계산하기 위해 자신의 알고리즘을 작성하는 것은 좋지 않습니다. 이 경우 값을 수동으로 계산하기가 너무 어렵습니다.

루비 코드로 돌아가서 다양한 매개 변수를 사용하여 이러한 원래 기능을 실행합니다. 루비 코드의 결과를 가져 와서 단위 테스트에 넣었습니다. 그렇게하면 수동 계산을 수행 할 필요가 없습니다. 그러나 원래 코드를 테스트하고 있습니다. 결과를 동일하게 유지하는 데 도움이되지만 원본에 버그가 있으면 도움이되지 않습니다. 기본적으로 sqrt 예제에서 원래 코드를 계산기처럼 취급 할 수 있습니다.

실제 코드를 보여 주면 문제에 접근하는 방법에 대한 자세한 피드백을 제공 할 수 있습니다.


루비 코드에 새 코드에없는 버그가 있고 루비 출력을 기반으로 코드가 단위 테스트에 실패하면 실패한 이유를 조사하면 결과적으로 결과를 입증 할 수 있습니다. 잠재 된 루비 버그가 발견되었습니다. 꽤 멋지다.
Adam Wuerl

11

내가 할 수있는 유일한 단위 테스트처럼 단순히 테스트에서 알고리즘을 다시 코딩하는 것입니다.

당신은 그런 간단한 수업에 거의 맞습니다.

더 복잡한 계산기를 사용해보십시오. 볼링 점수 계산기처럼.

테스트 할 시나리오가 다른 복잡한 "비즈니스"규칙이 있으면 단위 테스트의 가치를보다 쉽게 ​​확인할 수 있습니다.

나는 당신이 밀 계산기 실행을 테스트해서는 안된다고 말하는 것이 아닙니다 (계산기가 계정을 1/3과 같은 값으로 나타낼 수 없습니까? 0으로 나누면 어떻게됩니까?). 더 많은 지점을 가지고 무언가를 테스트하면 더 많은 가치를 얻을 수 있습니다.


4
복잡한 기능에는 더욱 유용합니다. adder.add ()를 부동 소수점 값으로 확장하기로 결정한 경우 어떻게해야합니까? 매트릭스? 레거 계정 값?
joshin4colours

6

100 % 코드 적용 범위에 대한 종교적 열의에도 불구하고, 모든 방법이 단위 테스트되지는 않는다고 말할 것입니다. 의미있는 비즈니스 로직을 포함하는 기능 만 단순히 숫자를 더하는 함수는 테스트 할 무의미합니다.

나는 현재 ~ 10 부울과 몇 개의 정수를 취하는 "절차"함수를 코딩하고 있으며 이것을 기반으로 int 결과를 제공합니다.

거기에 당신의 진짜 문제가 있습니다. 단위 테스트가 부자연 스럽거나 무의미 해 보이면 설계 결함으로 인한 것일 수 있습니다. 더 객체 지향적 인 경우 메소드 서명은 그렇게 크지 않으며 테스트 할 입력이 적습니다.

OO에 갈 필요는 없습니다. 절차 적 프로그래밍보다 우수합니다 ...


이 경우 메소드의 "서명"은 방대하지 않으며 클래스 멤버 인 std :: vector <bool>에서 읽습니다. 또한 루비 코드 (내가 만들지 않은)를 포팅 (아마도 잘못 설계 한 것)하고 있습니다
lezebulon

2
@lezebulon 해당 단일 메소드에 대해 가능한 많은 입력이 있는지 여부에 관계없이 해당 메소드가 너무 많은 작업을 수행합니다 .
maple_shaft

3

내 관점에서 단위 테스트는 작은 가산기 클래스에서도 유용합니다. 알고리즘을 "레코딩"한다고 생각하지 말고 알고있는 유일한 지식을 가진 블랙 박스로 생각하십시오. 빠른 곱셈을 사용하면 "a * b")와 공용 인터페이스를 사용하는 것보다 더 빠르지 만 더 복잡한 시도를 알고 있습니다. "도대체 무엇이 잘못 될 수 있는가?"

대부분의 경우 테두리에서 발생합니다 (이 패턴을 ++,-, +-, 00-시간을 추가하여 테스트하여-+, 0+, 0-, +0, -0으로 완료했습니다). 거기에 더하거나 뺄 때 (음수 추가); MAX_INT와 MIN_INT에서 어떤 일이 발생하는지 생각해보십시오. 또는 테스트가 0에서 발생하는 상황을 정확히 확인하십시오.

간단한 클래스의 경우 모든 비밀은 매우 간단합니다 (아마도 복잡한 클래스도 가능합니다)) : 클래스의 계약에 대해 생각하고 (계약 별 디자인 참조) 테스트하십시오. 당신의 inv 's, pre 's and post가 테스트가 완료 될 "완전한"것을 더 잘 알수록 좋습니다.

테스트 클래스에 대한 힌트 : 메소드에 단 하나의 assert 만 작성하십시오. 코드 변경 후 테스트가 실패 할 때 최상의 피드백을 얻을 수 있도록 메소드에 올바른 이름 (예 : "testAddingToMaxInt", "testAddingTwoNegatives")을 지정하십시오.


2

수동으로 계산 된 반환 값을 테스트하거나 테스트의 논리를 복제하여 예상 반환 값을 계산하는 대신 예상 속성에 대한 반환 값을 테스트하십시오.

예를 들어, 행렬을 반전시키는 메소드를 테스트하려는 경우 입력 값을 수동으로 반전시키지 않으려면 반환 값에 입력을 곱하고 항등 행렬을 가져 오는지 확인해야합니다.

이 방법을 메서드에 적용하려면 반환 값이 입력에 대해 어떤 속성을 가질 것인지 식별하기 위해 그 목적과 의미를 고려해야합니다.


2

단위 테스트는 생산성 도구입니다. 변경 요청을 받아 구현 한 다음 단위 테스트 gambit를 통해 코드를 실행하십시오. 이 자동 테스트는 시간을 절약합니다.

I feel that this test is totally useless, because you are required to manually compute the wanted result and test it, I feel like a better unit test here would be

ot 포인트. 이 예제의 테스트는 클래스를 인스턴스화하고 일련의 테스트를 통해 실행하는 방법을 보여줍니다. 단일 구현의 미세한 부분에 집중하면 나무의 숲이 사라집니다.

Can someone provide me with an example where unit testing is actually useful?

직원 엔터티가 있습니다. 엔티티는 이름과 주소를 포함합니다. 클라이언트는 ReportsTo 필드를 추가하기로 결정합니다.

void TestBusinessLayer()
{
   int employeeID = 1234
   Employee employee = Employee.GetEmployee(employeeID)
   BusinessLayer bl = new BusinessLayer()
   Assert.isTrue(bl.Add(employee))//assume Add returns true on pass
}

직원과의 작업에 대한 BL의 기본 테스트입니다. 코드는 방금 변경 한 스키마 변경을 통과 / 실패합니다. 어설 션은 테스트 만하는 것이 아니라는 점을 기억하십시오. 코드를 실행하면 예외가 발생하지 않습니다.

시간이 지남에 따라 테스트를 실시하면 일반적으로 변경하기가 쉬워집니다. 코드는 예외 및 사용자가 작성한 어설 션에 대해 자동으로 테스트됩니다. 이를 통해 QA 그룹의 수동 테스트로 인한 오버 헤드를 피할 수 있습니다. UI는 여전히 자동화하기가 어렵지만 액세스 수정자를 올바르게 사용한다고 가정하면 다른 계층은 일반적으로 매우 쉽습니다.

I feel like the only unit testing I could do would be to simply re-code the algorithm in the test.

절차 적 논리조차도 함수 안에 쉽게 캡슐화됩니다. 테스트 할 int / primitive (또는 모의 객체)를 캡슐화, 인스턴스화 및 전달합니다. 코드를 단위 테스트에 붙여 넣지 마십시오. 그것은 DRY를 물리칩니다. 또한 코드를 테스트하지 않고 코드 사본을 테스트하기 때문에 테스트를 완전히 무효화합니다. 테스트해야 할 코드가 변경되면 테스트는 여전히 통과합니다!


<pedantry> "gambit"가 아닌 "gamut". </
pedantry

@chao lol은 매일 새로운 것을 배웁니다.
P.Brian.Mackey

2

예를 들어 (리팩터링으로)

assert(a + b, math.add(a, b));

도움이되지 않습니다 :

  • math.add내부적으로 어떻게 행동하는지 이해 하고
  • 엣지 케이스에서 어떤 일이 일어날 지 알아

다음과 같이 말합니다.

  • 메소드의 기능을 알고 싶다면 수백 줄의 소스 코드를 직접보십시오 (예, 수백 개의 LOC를 포함 math.add 할 수 있기 때문에 아래 참조).
  • 방법이 올바르게 작동하는지 알고 싶지 않습니다. 예상 값과 실제 값이 실제로 예상 한 값과 다를 경우 괜찮습니다 .

또한 다음과 같은 테스트를 추가 할 필요가 없습니다.

assert(3, math.add(1, 2));
assert(4, math.add(2, 2));

그들은 일단 당신이 첫 번째 주장을 한 후에도 도움이되지 않습니다.

대신 다음은 어떻습니까?

const numeric Pi = 3.1415926535897932384626433832795;
const numeric Expected = 4.1415926535897932384626433832795;
assert(Expected, math.add(Pi, 1),
    "Adding an integer to a long numeric doesn't give a long numeric result.");
assert(Expected, math.add(1, Pi),
    "Adding a long numeric to an integer doesn't give a long numeric result.");

이것은 자명하며 나중에 소스 코드를 유지할 사람에게 도움이됩니다. 이 사람이 math.add코드를 단순화하고 성능을 최적화하기 위해 코드를 약간 수정하고 다음 과 같은 테스트 결과를 봅니다.

Test TestNumeric() failed on assertion 2, line 5: Adding a long numeric to an
integer doesn't give a long numeric result.

Expected value: 4.1415926535897932384626433832795
Actual value: 4

이 사람은 새로 수정 된 방법이 인수의 순서에 따라 다르다는 것을 즉시 이해할 것입니다. 첫 번째 인수가 정수이고 두 번째 인수가 긴 숫자 인 경우 결과는 정수가되고 긴 숫자는 예상됩니다.

같은 방식으로, 4.141592첫 번째 주장에서 실제 가치를 얻는 것은 자명하다 : 당신은 그 방법이 큰 정밀도를 다룰 것으로 예상 되지만 실제로는 실패 한다는 것을 알고있다 .

같은 이유로 일부 언어에서는 다음 두 가지 주장이 의미가 있습니다.

// We don't expect a concatenation. `math` library is not intended for this.
assert(0, math.add("Hello", "World"));

// We expect the method to convert every string as if it was a decimal.
assert(5, math.add("0x2F", 5));

또한, 어떻습니까 :

assert(numeric.Infinity, math.add(numeric.Infinity, 1));

자체 설명도 : 메소드가 무한대를 올바르게 처리 할 수 ​​있기를 원합니다. 가는 무한 넘어 하거나 예외를 던지는 것은 예상되는 동작하지 않습니다.

아니면 언어에 따라 더 의미가 있습니까?

/**
 * Ensures that when adding numbers which exceed the maximum value, the method
 * fails with OverflowException, instead of restarting at numeric.Minimum + 1.
 */
TestOverflow()
{
    UnitTest.ExpectException(ofType(OverflowException));

    numeric result = math.add(numeric.Maximum, 1));

    UnitTest.Fail("The tested code succeeded, while an OverflowException was
        expected.");
}

1

add와 같은 매우 간단한 함수의 경우 테스트가 불필요한 것으로 간주 될 수 있지만 함수가 복잡 해짐에 따라 테스트가 필요한 이유가 점점 더 명확 해집니다.

프로그래밍 할 때 수행하는 작업 (유닛 테스트없이)에 대해 생각해보십시오. 일반적으로 코드를 작성하고 실행하고 작동하는지 확인한 후 다음으로 넘어갑니다. 더 큰 코드, 특히 매우 큰 시스템 / GUI / 웹 사이트에서 더 많은 코드를 작성할수록 점점 더 "실행하고 작동하는지 확인"해야합니다. 이것을 시도하고 시도해야합니다. 그런 다음 몇 가지 사항을 변경하면 같은 일을 다시 시도해야합니다. 전체 "실행 및 작동 여부"부분을 자동화하는 단위 테스트를 작성하여 시간을 절약 할 수 있다는 것은 매우 분명합니다.

프로젝트가 점점 커짐에 따라 "실행하고 작동하는지 확인"해야 할 일이 비현실적이됩니다. 따라서 GUI / 프로젝트의 몇 가지 주요 구성 요소를 실행하고 시도한 다음 다른 모든 것이 잘되기를 바랍니다. 이것은 재난의 요리법입니다. 물론 사람으로서 GUI는 말 그대로 수백 명의 사람들이 사용하는 경우 고객이 사용할 수있는 모든 단일 상황을 반복적으로 테스트 할 수 없습니다. 단위 테스트를 수행 한 경우 안정 버전을 제공하기 전에 또는 중앙 저장소에 커밋하기 전에 (직장에서 사용하는 경우) 간단히 테스트를 실행할 수 있습니다. 나중에 발견 된 버그가 있으면 나중에 유닛 테스트를 추가하여 점검 할 수 있습니다.


1

단위 테스트 작성의 이점 중 하나는 엣지 케이스에 대해 생각하도록하여보다 강력한 코드를 작성하는 데 도움이된다는 것입니다. 정수 오버플로, 소수 자르기 또는 매개 변수에 대한 null 처리와 같은 일부 경우에 대한 테스트는 어떻습니까?


0

add ()가 ADD 명령어로 구현되었다고 가정 할 수 있습니다. 일부 주니어 프로그래머 또는 하드웨어 엔지니어가 ANDS / ORS / XORS, 비트 반전 및 시프트를 사용하여 add () 함수를 다시 구현 한 경우 ADD 명령어에 대해 단위 테스트를 수행 할 수 있습니다.

일반적으로 add () 또는 테스트중인 장치의 내장을 임의의 숫자 또는 출력 생성기로 바꾸면 어떤 것이 깨 졌는지 어떻게 알 수 있습니까? 해당 지식을 단위 테스트에 인코딩하십시오. 아무도 부서 졌는지 알 수 없다면 rand ()에 대한 코드를 확인하고 집에 가면 작업이 완료됩니다.


0

나는 모든 답장에서 그것을 놓쳤을 수도 있지만, 나에게 단위 테스팅의 주된 이유는 오늘날 방법의 정확성을 입증 하는 것이 아니라 당신이 그것을 바꿀 때마다 그 방법의 지속적인 정확성을 입증한다는 것입니다.

일부 컬렉션의 항목 수를 반환하는 것과 같은 간단한 기능을 수행하십시오. 오늘날 목록이 잘 알고있는 하나의 내부 데이터 구조를 기반으로 할 때이 방법이 너무 명백하여 테스트 할 필요가 없다고 생각할 수 있습니다. 그런 다음 몇 개월 또는 몇 년 안에 귀하 (또는 다른 사람 ) 가 내부 목록 구조 를 교체 하기로 결정 합니다. getCount ()가 올바른 값을 리턴한다는 것을 여전히 알아야합니다.

그것은 당신의 단위 테스트가 실제로 그들 스스로 오는 곳입니다.

코드의 내부 구현을 변경할 수 있지만 해당 코드의 소비자는 결과 동일하게 유지 됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.