Bagaimana saya menggunakan Assert untuk memverifikasi bahwa pengecualian telah dilemparkan?


830

Bagaimana cara saya menggunakan Assert(atau kelas Tes lainnya?) Untuk memverifikasi bahwa pengecualian telah dilemparkan?


Kerangka kerja pengujian unit mana yang Anda gunakan?
Kevin Pullin

3
Visual Studio Terpadu
Alex

4
Apakah atribut ExpectedException membantu? ref: msdn.microsoft.com/en-us/library/…
shahkalpesh

2
Lucu, saya baru saja selesai mencari jawaban untuk ini, menemukannya di stackoverflow.com/questions/741029/testing-exceptions .
dfjacobs

Jawaban:


978

Untuk "Tes Tim Visual Studio" tampaknya Anda menerapkan atribut ExpectedException ke metode pengujian.

Contoh dari dokumentasi di sini: Panduan Pengujian Unit dengan Tes Tim Visual Studio

[TestMethod]
[ExpectedException(typeof(ArgumentException),
    "A userId of null was inappropriately allowed.")]
public void NullUserIdInConstructor()
{
   LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
}

25
Atribut ExpectedException di atas juga berfungsi di NUnit (tetapi [TestMethod] harus [Test]).
dbkk

5
@dbkk: Tidak berfungsi sama persis di NUnit - pesan diperlakukan sebagai string yang perlu matcvh pesan pengecualian (dan IU berpikir itu lebih masuk akal)
Ruben Bartelink

29
Atribut ini menyelesaikan pekerjaan dan merupakan fitur bawaan untuk programmer c #, tapi saya tidak menyarankan menggunakannya karena tidak cukup fleksibel. Pertimbangkan apa yang terjadi jika jenis pengecualian dilemparkan oleh kode pengaturan pengujian Anda: tes lulus, tetapi tidak benar-benar melakukan apa yang Anda harapkan. Atau bagaimana jika Anda ingin menguji keadaan objek pengecualian. Saya biasanya ingin menggunakan StringAssert.Contains (e.Message ...) daripada menguji seluruh pesan. Gunakan metode yang tegas seperti dijelaskan dalam jawaban lain.
steve

3
Hindari penggunaan ExpectedException di NUnit, karena akan dijatuhkan di NUnit 3.0. Saya lebih suka menggunakan Assert.Throws <SpecificException> ()
Terence

5
Anda dapat menggunakan Assert.ThrowsException <T> dan Assert.ThrowsExceptionAsync <T> dalam MsTest.
Gopal Krishnan

257

Biasanya kerangka pengujian Anda akan memiliki jawaban untuk ini. Tetapi jika itu tidak cukup fleksibel, Anda selalu dapat melakukan ini:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // If it gets to this line, no exception was thrown
} catch (GoodException) { }

Seperti yang ditunjukkan oleh @Jonas, ini TIDAK berfungsi untuk menangkap basis Pengecualian:

try {
    somethingThatShouldThrowAnException();
    Assert.Fail(); // raises AssertionException
} catch (Exception) {
    // Catches the assertion exception, and the test passes
}

Jika Anda benar-benar harus menangkap Pengecualian, Anda harus memikirkan kembali Assert.Fail (). Tapi sungguh, ini pertanda kamu tidak boleh menulis ini dengan tangan; periksa kerangka pengujian Anda untuk opsi, atau lihat apakah Anda dapat melemparkan pengecualian yang lebih bermakna untuk diuji.

catch (AssertionException) { throw; }

Anda harus dapat menyesuaikan pendekatan ini dengan apa pun yang Anda suka - termasuk menentukan jenis pengecualian apa yang harus ditangkap. Jika Anda hanya mengharapkan tipe tertentu, selesaikan catchblok dengan:

} catch (GoodException) {
} catch (Exception) {
    // not the right kind of exception
    Assert.Fail();
}

20
+1, saya menggunakan cara ini alih-alih atribut ketika saya perlu membuat pernyataan di luar hanya tipe pengecualian. Sebagai contoh, bagaimana jika seseorang perlu memeriksa bahwa bidang-bidang tertentu dalam instance pengecualian diatur ke nilai-nilai tertentu.
Pavel Repin

2
Anda tidak diharuskan untuk menentukan pesan kesalahan. Ini sudah cukup: [ExpectedException (typeof (ArgumentException))]
mibollma

5
Saya pikir solusi ini adalah yang terbaik. [ExpectedException (typeof (ArgumentException))] memiliki kegunaannya, jika tesnya sederhana, tetapi menurut saya solusi yang malas dan menjadi nyaman dengan dapat menyebabkan jebakan. Solusi ini memberi Anda kontrol khusus untuk melakukan tes yang lebih benar, plus Anda dapat menguji Writeline ke laporan uji coba Anda, bahwa pengecualian memang dilemparkan seperti yang diharapkan.
evilfish

12
Hati-hati dengan itu karena Assert.Fail () menaikkan pengecualian, jika Anda menangkapnya, tes lulus!
Jonas

4
@ Vinnyq12 Yang saya maksud adalah bahwa tes pertama dalam contoh di atas tidak akan pernah gagal. Tes gagal jika pengecualian dilemparkan (dan tidak "ditangkap" oleh ExpectedExceptionAttribute)
Jonas

113

Metode pilihan saya untuk mengimplementasikan ini adalah menulis metode yang disebut Throws, dan menggunakannya sama seperti metode Assert lainnya. Sayangnya, .NET tidak memungkinkan Anda untuk menulis metode ekstensi statis, jadi Anda tidak dapat menggunakan metode ini seolah-olah itu milik build di kelas Assert; buat saja yang disebut MyAssert atau yang serupa. Kelasnya terlihat seperti ini:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        public static void Throws<T>( Action func ) where T : Exception
        {
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }

            if ( !exceptionThrown )
            {
                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but not thrown", typeof(T))
                    );
            }
        }
    }
}

Itu berarti unit test Anda terlihat seperti ini:

[TestMethod()]
public void ExceptionTest()
{
    String testStr = null;
    MyAssert.Throws<NullReferenceException>(() => testStr.ToUpper());
}

Yang terlihat dan berperilaku jauh lebih seperti sintaks tes unit Anda.


1
Singkirkan bool flag dan letakkan lemparan pada garisnya langsung setelah dipanggil untuk implementasi yang lebih ringkas.
gt

11
Satu-satunya hal yang membuat ini lebih baik adalah memiliki fungsi mengembalikan pengecualian yang tertangkap sehingga Anda dapat terus menyatakan bahwa hal-hal seperti atribut pada pengecualian itu benar.
Mark Hildreth

2
Terima kasih! Ini sepertinya pendekatan terbaik bagi saya karena ini adalah cara singkat untuk menguji beberapa pengecualian dalam satu metode. Ini juga jauh lebih mudah dibaca.
David Sherret

2
Atribut @MickeyPerlstein melanggar aturan AAA untuk pengujian. Secara khusus, jika Arrange Anda kebetulan melempar pengecualian bahkan sebelum Anda sampai ke Act, maka tes Anda lulus ... eek!
freedomn-m

2
Microsoft akhirnya mendapat bulat memperbarui MSTest - mendukung v2 Assert.ThrowsException<T>dan Assert.ThrowsExceptionAsync<T>- melihat blogs.msdn.microsoft.com/visualstudioalm/2017/02/25/...
quango

62

jika Anda menggunakan NUNIT, Anda dapat melakukan sesuatu seperti ini:

Assert.Throws<ExpectedException>(() => methodToTest());


Dimungkinkan juga untuk menyimpan pengecualian yang dilemparkan untuk memvalidasi lebih lanjut:

ExpectedException ex = Assert.Throws<ExpectedException>(() => methodToTest());
Assert.AreEqual( "Expected message text.", ex.Message );
Assert.AreEqual( 5, ex.SomeNumber);

Lihat: http://nunit.org/docs/2.5/exceptionAsserts.html


60

Jika Anda menggunakan MSTest, yang awalnya tidak memiliki ExpectedExceptionatribut, Anda bisa melakukan ini:

try 
{
    SomeExceptionThrowingMethod()
    Assert.Fail("no exception thrown");
}
catch (Exception ex)
{
    Assert.IsTrue(ex is SpecificExceptionType);
}

3
Ini berhasil, tetapi saya tidak merekomendasikan ini secara umum karena logikanya terlalu rumit. Bukan mengatakan itu berbelit-belit, tetapi pertimbangkan jika Anda menulis blok kode ini untuk beberapa tes - tes 10s, tes 100an. Logika ini perlu dikembangkan ke metode tegas yang dirancang dengan baik. Lihat jawaban lain.
steve

35

Berhati-hatilah dalam menggunakan ExpectedException, karena dapat menyebabkan beberapa perangkap seperti yang ditunjukkan di sini:

http://geekswithblogs.net/sdorman/archive/2009/01/17/unit-testing-and-expected-exceptions.aspx

Dan di sini:

http://xunit.github.io/docs/comparisons.html

Jika Anda perlu menguji pengecualian, ada beberapa cara yang tidak disukai. Anda dapat menggunakan metode coba {act / fail} catch {assert}, yang dapat berguna untuk kerangka kerja yang tidak memiliki dukungan langsung untuk tes pengecualian selain ExpectedException.

Alternatif yang lebih baik adalah dengan menggunakan xUnit.NET, yang merupakan kerangka pengujian unit yang sangat modern, berwawasan ke depan, dan dapat diperluas yang telah belajar dari semua kesalahan yang lain, dan ditingkatkan. Salah satu peningkatan tersebut adalah Assert.Throws, yang menyediakan sintaks yang jauh lebih baik untuk menyatakan pengecualian.

Anda dapat menemukan xUnit.NET di github: http://xunit.github.io/


4
Perhatikan bahwa NUnit 2.5 juga mendukung sintaks gaya Assert.Throws sekarang juga - nunit.com/index.php?p=releaseNotes&r=2.5
Alconja

Cara tes unit berhenti untuk memberi tahu Anda tentang pengecualian saat menggunakan ExpectedException membuat saya gila. Mengapa MS berpikir itu ide yang baik untuk memiliki langkah manual dalam tes otomatis? Terima kasih atas tautannya.
Semut

@ Ant: MS menyalin NUnit ... jadi pertanyaan sebenarnya adalah, mengapa NUnit berpikir itu ide yang bagus?
jrista

28

MSTest (v2) sekarang memiliki fungsi Assert.ThrowsException yang dapat digunakan seperti ini:

Assert.ThrowsException<System.FormatException>(() =>
            {
                Story actual = PersonalSite.Services.Content.ExtractHeader(String.Empty);
            }); 

Anda dapat menginstalnya dengan nuget: Install-Package MSTest.TestFramework


Pada 2018 ini dianggap praktik terbaik karena hanya memeriksa unit yang diuji melempar dan bukan kode lain.
CM

24

Dalam proyek saya sedang mengerjakan kami punya solusi lain melakukan ini.

Pertama, saya tidak suka ExpectedExceptionAttribute karena tidak mempertimbangkan metode panggilan mana yang menyebabkan Exception.

Saya melakukan ini dengan metode helpermet sebagai gantinya.

Uji

[TestMethod]
public void AccountRepository_ThrowsExceptionIfFileisCorrupt()
{
     var file = File.Create("Accounts.bin");
     file.WriteByte(1);
     file.Close();

     IAccountRepository repo = new FileAccountRepository();
     TestHelpers.AssertThrows<SerializationException>(()=>repo.GetAll());            
}

Metode Helper

public static TException AssertThrows<TException>(Action action) where TException : Exception
    {
        try
        {
            action();
        }
        catch (TException ex)
        {
            return ex;
        }
        Assert.Fail("Expected exception was not thrown");

        return null;
    }

Rapi, bukan;)


14

Ini adalah atribut pada metode pengujian ... Anda tidak menggunakan Assert. Terlihat seperti ini:

[ExpectedException(typeof(ExceptionType))]
public void YourMethod_should_throw_exception()

13

Anda dapat mengunduh paket dari Nuget menggunakan: PM> Instal-Paket MSTestExtensions yang menambahkan Assert.Throws () sintaks dalam gaya nUnit / xUnit ke MsTest.

Instruksi tingkat tinggi: unduh perakitan dan mewarisi dari BaseTest dan Anda dapat menggunakan Assert.Throws () sintaks .

Metode utama untuk implementasi Throws terlihat sebagai berikut:

public static void Throws<T>(Action task, string expectedMessage, ExceptionMessageCompareOptions options) where T : Exception
{
    try
    {
        task();
    }
    catch (Exception ex)
    {
        AssertExceptionType<T>(ex);
        AssertExceptionMessage(ex, expectedMessage, options);
        return;
    }

    if (typeof(T).Equals(new Exception().GetType()))
    {
        Assert.Fail("Expected exception but no exception was thrown.");
    }
    else
    {
        Assert.Fail(string.Format("Expected exception of type {0} but no exception was thrown.", typeof(T)));
    }
}

Pengungkapan: Saya mengumpulkan paket ini.

Info Lebih Lanjut: http://www.bradoncode.com/blog/2012/01/asserting-exceptions-in-mstest-with.html


Terima kasih untuk contohnya. Apakah Anda memiliki contoh cara menguji Assert.DoesNotThrow () atau yang setara?
Lane Goolsby

10

Anda dapat mencapai ini dengan satu garis sederhana.

Jika operasi Anda foo.bar()async:

await Assert.ThrowsExceptionAsync<Exception>(() => foo.bar());

Jika foo.bar()tidak async

Assert.ThrowsException<Exception>(() => foo.bar());

1
Ada banyak jawaban lain, bagi saya saya sedang mencari cara singkat untuk menguji kondisi gagal yang diketahui hanya dengan Jenis Pengecualian, ini membuat kasus uji yang paling mudah dibaca. CATATAN: Tipe Pengecualian tidak cocok dengan kelas pengecualian bawaan seperti try-catch standar, jadi contoh di atas tidak akan menjebak ArgumentExceptionmisalnya. Try Catch yang lama dan uji respon pengecualian masih lebih disukai jika Anda memiliki kriteria canggih untuk diuji, tetapi untuk banyak kasus saya, ini sangat membantu!
Chris Schaller

5

Saya tidak merekomendasikan menggunakan atribut ExpectedException (karena terlalu membatasi dan rawan kesalahan) atau untuk menulis blok try / catch di setiap tes (karena terlalu rumit dan rawan kesalahan). Gunakan metode penegasan yang dirancang dengan baik - baik yang disediakan oleh kerangka pengujian Anda atau tulis sendiri. Inilah yang saya tulis dan gunakan.

public static class ExceptionAssert
{
    private static T GetException<T>(Action action, string message="") where T : Exception
    {
        try
        {
            action();
        }
        catch (T exception)
        {
            return exception;
        }
        throw new AssertFailedException("Expected exception " + typeof(T).FullName + ", but none was propagated.  " + message);
    }

    public static void Propagates<T>(Action action) where T : Exception
    {
        Propagates<T>(action, "");
    }

    public static void Propagates<T>(Action action, string message) where T : Exception
    {
        GetException<T>(action, message);
    }

    public static void Propagates<T>(Action action, Action<T> validation) where T : Exception
    {
        Propagates(action, validation, "");
    }

    public static void Propagates<T>(Action action, Action<T> validation, string message) where T : Exception
    {
        validation(GetException<T>(action, message));
    }
}

Contoh menggunakan:

    [TestMethod]
    public void Run_PropagatesWin32Exception_ForInvalidExeFile()
    {
        (test setup that might propagate Win32Exception)
        ExceptionAssert.Propagates<Win32Exception>(
            () => CommandExecutionUtil.Run(Assembly.GetExecutingAssembly().Location, new string[0]));
        (more asserts or something)
    }

    [TestMethod]
    public void Run_PropagatesFileNotFoundException_ForExecutableNotFound()
    {
        (test setup that might propagate FileNotFoundException)
        ExceptionAssert.Propagates<FileNotFoundException>(
            () => CommandExecutionUtil.Run("NotThere.exe", new string[0]),
            e => StringAssert.Contains(e.Message, "NotThere.exe"));
        (more asserts or something)
    }

CATATAN

Mengembalikan pengecualian alih-alih mendukung validasi panggilan balik adalah ide yang masuk akal kecuali bahwa melakukan hal itu membuat sintaks panggilan pernyataan ini sangat berbeda dari pernyataan lain yang saya gunakan.

Tidak seperti yang lain, saya menggunakan 'propagasi' bukan 'melempar' karena kami hanya dapat menguji apakah pengecualian merambat dari panggilan. Kami tidak dapat menguji secara langsung bahwa pengecualian dilemparkan. Tapi saya kira Anda bisa membayangkan melempar artinya: dibuang dan tidak tertangkap.

PIKIRAN FINAL

Sebelum beralih ke pendekatan semacam ini, saya mempertimbangkan untuk menggunakan atribut ExpectedException ketika tes hanya memverifikasi tipe pengecualian dan menggunakan blok coba / tangkap jika diperlukan lebih banyak validasi. Tetapi, saya tidak hanya harus memikirkan teknik mana yang akan digunakan untuk setiap tes, tetapi mengubah kode dari satu teknik ke teknik lainnya karena kebutuhan berubah bukanlah usaha yang sepele. Menggunakan satu pendekatan yang konsisten menghemat upaya mental.

Jadi secara ringkas, pendekatan ini olahraga: kemudahan penggunaan, fleksibilitas dan ketahanan (sulit untuk melakukannya salah).


4

Helper yang disediakan oleh @Richiban di atas berfungsi dengan baik kecuali itu tidak menangani situasi di mana pengecualian dilemparkan, tetapi bukan tipe yang diharapkan. Berikut alamat yang:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace YourProject.Tests
{
    public static class MyAssert
    {
        /// <summary>
        /// Helper for Asserting that a function throws an exception of a particular type.
        /// </summary>
        public static void Throws<T>( Action func ) where T : Exception
        {
            Exception exceptionOther = null;
            var exceptionThrown = false;
            try
            {
                func.Invoke();
            }
            catch ( T )
            {
                exceptionThrown = true;
            }
            catch (Exception e) {
                exceptionOther = e;
            }

            if ( !exceptionThrown )
            {
                if (exceptionOther != null) {
                    throw new AssertFailedException(
                        String.Format("An exception of type {0} was expected, but not thrown. Instead, an exception of type {1} was thrown.", typeof(T), exceptionOther.GetType()),
                        exceptionOther
                        );
                }

                throw new AssertFailedException(
                    String.Format("An exception of type {0} was expected, but no exception was thrown.", typeof(T))
                    );
            }
        }
    }
}

2
Hmmm ... Saya mengerti ide itu, tapi saya tidak yakin saya setuju itu lebih baik. Hanya karena kami ingin memastikan pengecualian spesifik dimunculkan, tidak berarti semua yang lain harus dibungkus sebagai kegagalan pernyataan. IMHO pengecualian yang tidak diketahui seharusnya hanya menggelembungkan tumpukan seperti yang akan terjadi dalam operasi penegasan lainnya.
Crono

@ Martin Saya akan menghapus kode yang melibatkan exceptionOther dan hanya memikirkan kembali dari klausa tangkapan kedua
Tom Lint

4

Karena Anda menyebutkan menggunakan kelas tes lain, opsi yang lebih baik daripada ExpectedExceptionatribut adalah menggunakan Shoudly 's Should.Throw .

Should.Throw<DivideByZeroException>(() => { MyDivideMethod(1, 0); });

Katakanlah kita memiliki persyaratan bahwa pelanggan harus memiliki alamat untuk membuat pesanan . Jika tidak, CreateOrderForCustomermetode harus menghasilkan ArgumentException. Lalu kita bisa menulis:

[TestMethod]
public void NullUserIdInConstructor()
{
  var customer = new Customer(name := "Justin", address := null};

  Should.Throw<ArgumentException>(() => {
    var order = CreateOrderForCustomer(customer) });
}

Ini lebih baik daripada menggunakan ExpectedException atribut karena kita lebih spesifik tentang apa yang harus membuang kesalahan. Ini membuat persyaratan dalam tes kami lebih jelas dan juga membuat diagnosis lebih mudah ketika tes gagal.

Catatan ada juga Should.ThrowAsyncuntuk pengujian metode asinkron.


4

Sebagai alternatif, Anda dapat mencoba menguji pengecualian yang sebenarnya dilemparkan dengan 2 baris berikutnya dalam pengujian Anda.

var testDelegate = () => MyService.Method(params);
Assert.Throws<Exception>(testDelegate);

4

Dalam pengujian unit bawaan VS jika Anda hanya ingin memverifikasi bahwa "pengecualian" dilemparkan, tetapi Anda tidak tahu jenisnya, Anda dapat menggunakan tangkapan semua:

[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void ThrowExceptionTest()
{
    //...
}

3

Yah saya akan cukup meringkas apa yang orang lain katakan di sini sebelumnya ... Ngomong-ngomong, ini kode yang saya buat sesuai dengan jawaban yang baik :) Yang tersisa hanyalah menyalin dan menggunakan ...

/// <summary>
/// Checks to make sure that the input delegate throws a exception of type TException.
/// </summary>
/// <typeparam name="TException">The type of exception expected.</typeparam>
/// <param name="methodToExecute">The method to execute to generate the exception.</param>
public static void AssertRaises<TException>(Action methodToExecute) where TException : System.Exception
{
    try
    {
        methodToExecute();
    }
    catch (TException) {
        return;
    }  
    catch (System.Exception ex)
    {
        Assert.Fail("Expected exception of type " + typeof(TException) + " but type of " + ex.GetType() + " was thrown instead.");
    }
    Assert.Fail("Expected exception of type " + typeof(TException) + " but no exception was thrown.");  
}


2

Ini akan tergantung pada kerangka tes apa yang Anda gunakan?

Di MbUnit, misalnya, Anda bisa menentukan pengecualian yang diharapkan dengan atribut untuk memastikan bahwa Anda mendapatkan pengecualian yang benar-benar Anda harapkan.

[ExpectedException(typeof(ArgumentException))]

2

Jika menggunakan NUnit , coba ini:

Assert.That(() =>
        {
            Your_Method_To_Test();
        }, Throws.TypeOf<Your_Specific_Exception>().With.Message.EqualTo("Your_Specific_Message"));

2

Ada perpustakaan luar biasa bernama NFluent yang mempercepat dan memudahkan cara Anda menulis pernyataan .

Cukup mudah untuk menulis pernyataan untuk melemparkan pengecualian:

    [Test]
    public void given_when_then()
    {
        Check.ThatCode(() => MethodToTest())
            .Throws<Exception>()
            .WithMessage("Process has been failed");
    }

1

Meskipun ini adalah pertanyaan lama, saya ingin menambahkan pemikiran baru ke dalam diskusi. Saya telah memperpanjang Arrange, Act, Assert pattern menjadi Expected, Arrange, Act, Assert. Anda dapat membuat pointer pengecualian yang diharapkan, lalu menegaskan itu ditugaskan. Ini terasa lebih bersih daripada melakukan Asserts Anda di blok tangkap, meninggalkan bagian Act Anda sebagian besar hanya untuk satu baris kode untuk memanggil metode yang sedang diuji. Anda juga tidak perlu Assert.Fail();atau returndari banyak titik dalam kode. Pengecualian lain yang dilemparkan akan menyebabkan tes gagal, karena itu tidak akan tertangkap, dan jika pengecualian dari tipe yang Anda harapkan dilemparkan, tetapi itu bukan yang Anda harapkan, Menegaskan terhadap pesan atau properti lain dari pengecualian membantu memastikan tes Anda tidak akan lulus secara tidak sengaja.

[TestMethod]
public void Bar_InvalidDependency_ThrowsInvalidOperationException()
{
    // Expectations
    InvalidOperationException expectedException = null;
    string expectedExceptionMessage = "Bar did something invalid.";

    // Arrange
    IDependency dependency = DependencyMocks.Create();
    Foo foo = new Foo(dependency);

    // Act
    try
    {
        foo.Bar();
    }
    catch (InvalidOperationException ex)
    {
        expectedException = ex;
    }

    // Assert
    Assert.IsNotNull(expectedException);
    Assert.AreEqual(expectedExceptionMessage, expectedException.Message);
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.