Teste başlamak için bir tasarıma ihtiyacım olursa TDD'nin iyi bir tasarım elde etmeme nasıl yardımcı olduğunu anlamıyorum


50

Başımı TDD'nin etrafına, özellikle de geliştirme kısmına sarmaya çalışıyorum. Bazı kitaplara baktım, ancak bulduklarım çoğunlukla test bölümü ile ilgileniyor - NUnit'in Tarihi, testin neden iyi, Kırmızı / Yeşil / Refaktör ve bir String Hesaplayıcısı nasıl oluşturulacağı.

İyi şeyler, ama bu "sadece" Birim Testi, TDD değil. Özellikle, TDD'yi test etmeye başlamak için bir Tasarım'a ihtiyacım olursa nasıl iyi bir tasarım elde etmeme yardımcı olduğunu anlamıyorum.

Göstermek için şu 3 gereksinimi hayal edin:

  • Bir kataloğun bir ürün listesine sahip olması gerekir
  • Katalog bir kullanıcının hangi ürünleri görüntülediğini hatırlamalıdır.
  • Kullanıcılar bir ürün arayabilmeli

Bu noktada, pek çok kitap sihirli bir tavşanı şapkadan çıkarır ve sadece "Ürün Hizmetini Test Etme" ye dalar, ancak ilk başta bir Ürün Hizmeti olduğu sonucuna nasıl geldiklerini açıklamazlar. TDD'de anlamaya çalıştığım “Gelişme” kısmı budur.

Var olan bir tasarım olması gerekir, ancak varlık-hizmetlerin dışındaki şeyler (yani: Bir Ürün var, bu nedenle bir Ürün Hizmeti olmalıdır) hiçbir yerde bulunamaz. Kullanıcı, ancak hatırlatmak için işlevselliği nereye koyacaktım? Arama, Ürün Hizmetinin bir özelliği mi yoksa ayrı bir Arama Hizmetinin özelliği midir? Hangisini seçmem gerektiğini nasıl bilebilirim?)

Göre KATI , bir UserService gerekir, ama ben TDD olmadan bir sistem tasarlamak, ben Tek Yöntem Hizmetlerinin bir sürü ile bitirmek olabilir. TDD benim tasarımımı ilk önce keşfetmemi sağlamıyor mu?

Bir .net geliştiricisiyim, fakat Java kaynakları da işe yarayacak. Gerçek bir iş başvurusu ile ilgilenen gerçek bir örnek uygulama veya kitap gibi görünmediğini hissediyorum. Birisi TDD kullanarak bir tasarım oluşturma sürecini gösteren net bir örnek verebilir mi?


2
TDD, tüm geliştirme metodolojisinin bir parçasıdır. Elbette her şeyi bir araya getirmek için bir çeşit tasarım (önceden veya daha iyi evrimsel) kullanmanız gerekecek.
Öforik

3
@gnat: TDD kitaplarının tasarım sürecini neden daha açık hale getirmediğine dair bir soruşturma.
Robert Harvey,

4
@gnat: Senin düzenlemendi, benim değil. :) Sorunun başlığına ve bedene yaptığım değişikliği görün.
Robert Harvey,

9
Robert C. Martin'in çalışmasını okuduysanız veya videolarından birini izlediyseniz, sık sık bir tasarıma sahip olduğunu ancak onunla evli olmadığını göreceksiniz. O inandığı doğru tasarımın onun ön fikre onun testlerden çıkacak, ama zorlamak gelmez. Ve sonunda, bazen bu tasarım yapar, bazen de yapmaz. Buradaki amacım, kendi önceki deneyimlerinizin size rehberlik edeceği, ancak testlerin sizi yönlendirmesi gerektiği. Testler, tasarımınızı geliştirebilmeli ya da alçaltabilmeli.
Anthony Pegram

3
Bu yüzden gerçekten testle ilgili değil, tasarımla ilgili. Sadece tasarımda size gerçekten yardımcı değil, tasarımın doğrulanmasına yardımcı olmak kadar. Ama bu değil mi? @ # $ İng test?
Erik,

Yanıtlar:


17

TDD'nin fikri, teste başlamak ve bununla çalışmaktır. Bu nedenle, "Bir kataloğun bir ürün listesine sahip olması gerekir" örneğini almak için "katalogdaki ürünleri kontrol et" testi yapılmış gibi görülebilir ve bu nedenle bu ilk testtir. Şimdi, kataloğu ne? Ürünü ne tutar? Bunlar bir sonraki parçalardır ve fikir, bir araya getirilmesi gereken ilk testi yaptırmaktan doğacak bir ProductService gibi bir şey olacak bazı parçaların ve parçaların bir araya getirilmesidir.

TDD'nin fikri bir teste başlamak ve sonra bu testi ilk aşama olarak yapan kodu yazmaktır. Birim testler, bu evet'in bir parçası, ancak testler ile başlayan ve daha sonra kodu yazarak oluşturulan genel resme bakmıyorsunuz, bu nedenle henüz bu noktada herhangi bir kod bulunmadığından bu noktada kör noktalar olmayacak.


20-22 arasındaki slaytların kilitleri olduğu Test Tahrikli Geliştirme Eğitimi . Amaç, fonksiyonelliğin sonuç olarak ne yapması gerektiğini bilmek, bunun için bir test yazıp bir çözüm oluşturmaktır. Tasarım kısmı, yapılması gerekenin ne olduğuna bağlı olarak değişebilir veya yapılması o kadar basit olmayabilir. Kilit nokta, projeye geç tanıtmak yerine TDD'yi baştan kullanmaktır. İlk önce testlerle başlarsanız, bu yardımcı olabilir ve bir anlamda dikkat çekmeye değer. Testleri daha sonra eklemeye çalışırsanız, ertelenebilecek veya ertelenebilecek bir şey haline gelir. Sonraki slaytlar da faydalı olabilir.


TDD'nin ana yararı, testlere başladığınızda, başlangıçta bir tasarıma kilitlenmemiş olmanızdır. Bu nedenle, fikir testleri oluşturmak ve bu testleri bir geliştirme yöntemi olarak geçecek kodu oluşturmaktır. Büyük Bir Tasarım Ön Cephesi , sistemin sonunda inşa edilmesini daha az çevik hale getirecek bir şeyleri yerlerine kilitleme fikrini verdiği için sorunlara neden olabilir.


Robert Harvey bunu cevabında belirttiği değerlere ekledi:

Ne yazık ki, bunun TDD ile ilgili yaygın bir yanılgı olduğunu düşünüyorum: sadece birim testleri yazarak ve bunları geçerek bir yazılım mimarisi yetiştiremezsiniz. Birim testleri yazmak, tasarımı etkiler, ancak tasarımı oluşturmaz . Bunu yapmak zorundasın.


31
@MichaelStum: Maalesef bunun TDD ile ilgili yaygın bir yanılgı olduğunu düşünüyorum: sadece birim testleri yazıp geçmelerini sağlayarak bir yazılım mimarisi oluşturamazsınız. Birim testleri yazmak, tasarımı etkiler, ancak tasarımı oluşturmaz . Bunu yapmak zorundasın.
Robert Harvey,

4
@RobertHarvey, JimmyHoffa: Yorumlarınızı 100 kere oylayabilirsem, yapardım!
Doc Brown

9
@Robert Harvey: Bu yaygın yanılgı hakkında yazdığınıza sevindim: Çok sık sık duyuyorum ki, herkesin oturup her türlü birim testini yazması gerekir ve bir tasarım kendiliğinden "ortaya çıkacaktır". Ve tasarımınız kötüyse, bunun için yeterince birim testi yazmamış olmanızdır. Testlerin tasarımınız için gereksinimleri belirlemek ve doğrulamak için bir araç olduğunu kabul ediyorum, ancak tasarımı kendiniz yapmalısınız. Tamamen katılıyorum.
Giorgio

4
@Giorgo, RobertHarvey: +1000'den RobertHarvey'e benden de. Ne yazık ki, bu yanlış anlama bazı "uzman" TDD / Çevik uygulayıcıların doğru olduğuna inandığı kadar yaygındır. Örneğin, onlar alan bilgisi veya herhangi bir analiz yapmadan, TDD'den bir sudoku çözücüsünü "geliştirebileceğinizi" iddia ediyorlar . Ron Jeffries'in TDD'nin sınırlamaları hakkında bir takip yayınlayıp yayınlamadığını merak ediyorum ya da neden bir an önce herhangi bir sonuç ya da ders alınmadan denemesini durdurduğunu açıkladı.
Andres F.

3
@ Andrea F: Sudoku hakkındaki hikayeyi biliyorum ve bunun çok ilginç olduğunu düşünüyorum. Bazı geliştiricilerin bir aracın (örneğin TDD veya SCRUM) alan bilgisini ve kendi çabalarını değiştirebileceğini düşünme hatası yaptığını ve belirli bir yöntemi mekanik olarak uygulayarak iyi bir yazılımın sihirli bir şekilde "ortaya çıkacağını" beklediğini düşünüyorum. Genellikle analiz ve tasarımda çok fazla zaman harcamak istemeyen ve doğrudan bir şeyi kodlamayı tercih eden insanlardır. Onlar için, belirli bir metodolojiyi takip etmek, uygun tasarım yapmamanın bir mazeretidir. Ancak bu IMHO'nun TDD'yi kötüye kullanmasıdır.
Giorgio

8

Ne pahasına olursa olsun, TDD en iyi tasarıma TDD yapmamaktan daha hızlı gelmeme yardımcı oluyor . Muhtemelen en iyi tasarıma onunla veya onsuz gelirim. Ama bunu düşünerek harcayacağım ve kodda birkaç bıçak alarak harcadığım o zaman bunun yerine testler yazıyor. Ve daha az zaman. Benim için. Herkes için değil. Ve aynı miktarda zaman alsa bile, beni bir takım testler bırakacaktı, böylece yeniden yapılanma daha güvenli olacaktı, hatta daha iyi kodlamaya yol açacaktı.

Bunu nasıl yapıyor?

İlk olarak, her sınıfı bazı müşteri kodlarına hizmet olarak düşünmemi teşvik ediyor. Kodun kendisinin nasıl görünmesi gerektiğinden endişelenmek yerine çağıran kodun API'yı nasıl kullanmak istediğini düşünmekten daha iyi bir kod gelir.

İkincisi, beni düşünürken çok fazla döngüsel karmaşıklık yazmamı engelliyor. Bir yöntemden geçen her ilave yol, yapmam gereken test sayısını iki katına çıkarır. Tamamen tembellik, çok fazla mantık ekledikten sonra, bir koşul eklemek için 16 test yazmam gerektiğini, bunun bir kısmını başka bir yönteme / sınıfa çekip ayrı ayrı test etmenin zamanı geldiğini söylüyor.

Gerçekten bu kadar basit. Bu sihirli bir tasarım aracı değil.


6

Başımı TDD'nin etrafına sarmaya çalışıyorum ... Örnek olarak, şu 3 gereksinimi hayal edin:

  • Bir kataloğun bir ürün listesine sahip olması gerekir
  • Katalog bir kullanıcının hangi ürünleri görüntülediğini hatırlamalıdır.

Bu şartlar insani olarak yeniden ifade edilmelidir. Kullanıcının daha önce hangi ürünleri gördüğünü kim bilmek ister? Kullanıcı? Bir satış elemanı?

  • Kullanıcılar bir ürün arayabilmeli

Nasıl? İsimle? Markaya göre mi? Test odaklı geliştirmedeki ilk adım, bir testi tanımlamaktır, örneğin:

browse to http://ourcompany.com
enter "cookie" in the product search box
page should show "chocolate-chip cookies" and "oatmeal cookies"

>

Bu noktada, pek çok kitap sihirli bir tavşanı şapkadan çıkarır ve sadece "Ürün Hizmetini Test Etme" ye dalar, ancak ilk başta bir Ürün Hizmeti olduğu sonucuna nasıl geldiklerini açıklamazlar.

Bunlar sadece şartlarsa, kesinlikle bir ProductService oluşturmak için sıçramayacağım. Statik ürün listesi ile çok basit bir web sayfası oluşturabilirim. Ürün ekleme ve silme gereksinimlerine ulaşana kadar bu mükemmel sonuç verir. Bu noktada ilişkisel veritabanı ve ORM kullanmanın ve tek bir tabloya eşlenmiş bir Ürün sınıfı oluşturmanın en basit olduğuna karar verebilirim. Hala ürün servisi yok. ProductService gibi sınıflar gerektiğinde ve gerektiğinde oluşturulur. Aynı sorguları veya güncellemeleri gerçekleştirmesi gereken birden fazla web isteği olabilir. Ardından, kod çoğaltılmasını önlemek için ProductService sınıfı oluşturulur.

Özet olarak, TDD yazılacak kodu çalıştırır. Tasarım, uygulama seçimleri yaparken gerçekleşir ve ardından çoğaltmayı ve kontrol bağımlılıklarını ortadan kaldırmak için kodu sınıflara yeniden yansıtın. Kod eklerken, SOLID kodunu tutmak için yeni sınıflar oluşturmanız gerekir. Ancak, önceden bir Ürün sınıfına ve bir ProductService sınıfına ihtiyacınız olacağına karar vermenize gerek yoktur. Hayatın sadece bir Ürün sınıfı ile mükemmel olduğunu görebilirsiniz.


Tamam, hayır ProductServiceo zaman. Peki TDD size bir veritabanına ve bir ORM'ye ihtiyacınız olduğunu nasıl söyledi?
Robert Harvey,

4
@Robert: olmadı. Bu gereksinimi karşılamanın en etkili yoluna dair kararımı temel alan bir tasarım kararıdır. Ancak karar değişebilir.
kevin cline

1
İyi tasarım asla bazı keyfi işlemlerin bir yan etkisi olarak üretilmez. Üzerinde çalışacak ve çerçevelenecek bir sisteme veya modele sahip olmak harikadır fakat ilk önce test-TDD olan IMO, insanların beklenmedik bir şekilde kötülerin yan etkileri yüzünden ısırılmayacağını garanti eden bir şey olarak satarak çıkar çatışmasına neden olur Her şeyden önce olmaması gereken kodu. Tasarım yansıma, farkındalık ve öngörü gerektirir. Bunları ağaçtaki otomatik keşfedilen semptomları budamaktan öğrenmiyorsunuz. Kötü mutant dalların ilk etapta nasıl önleneceğini öğrenerek onları öğrenirsiniz.
Erik Reppen

Testin bir ürün eklediğini düşünüyorum; bilgisayarı yeniden başlatın ve sistemi yeniden başlatın; eklenen ürün hala görünür olmalı. ' bir tür veritabanına olan ihtiyacın nereden geldiğini gösterir (ancak bu hala düz bir dosya veya XML olabilir).
yatima2975

3

Diğerleri aynı fikirde olmayabilir, ancak bana göre, yeni metodolojilerin çoğu, eski metodolojilerin alışkanlık veya kişisel gururdan dile getirdiği şeylerin çoğunu yapacağına, geliştiricinin genellikle oldukça açık olan bir şey yaptığı fikrine güveniyor. onlar için ve iş temiz bir dilde ya da biraz dağınık bir dilin daha temiz kısımlarında saklanır, böylece tüm test işlerini yapabilirsiniz.

Geçmişte buna karıştığım bazı örnekler:

  • Bir sürü spec-yükleniciye katılın ve onlara ekibinin Çevik ve İlk Test olduğunu söyleyin. Spesifik olarak çalışmaktan başka bir alışkanlıklarına sahip değillerdir ve projeyi bitirmek için yeterince uzun sürdüğü müddetçe işin kalitesiyle ilgili endişeleri yoktur.

  • Önce yeni bir test deneyin ve yapın, çeşitli yaklaşımlar ve arayüzler saçma saptadıkça testler için zaman ayırın.

  • Düşük seviyede bir şey kodlayın ve kapsama eksikliğinden dolayı tokatlayın ya da çok değerli olmayan testler yazın, çünkü bağlı olduğunuz davranışlarla alay edemezsiniz.

  • Disk altsistemi veya bir tcpip seviyesi iletişim arayüzü gibi, ilk önce altta yatan denemesiz bitleri yazmadan, test edilebilir bir özellik eklemek için vaktinin başında temel mekaniklerin yeterli olmadığı herhangi bir durum.

TDD yapıyorsanız ve sizin için çalışıyorsa, sizin için iyi, ama bunun sadece basit bir değer katmadığı birçok şey var (bütün işler veya bir projenin aşamaları).

Örneğiniz, henüz bir tasarıma bile değilmiş gibi görünmüyor, yani bir mimari konuşmanız olması gerekiyor veya prototip oluşturuyorsunuz. Benim düşünceme göre, ilk önce bunlardan bazılarını atlamanız gerekir.


1

TDD'nin sistemin detaylı tasarımına - yani API'ler ve nesne modeli için çok değerli bir yaklaşım olduğuna inanıyorum . Bununla birlikte, TDD'yi kullanmaya başlayacağınız bir projedeki noktaya gelmek için, tasarımın büyük bir resmini halihazırda bir moda model almış ve mimarinin büyük bir resmini zaten bir moda modellenmiştir. @ user414076, Robert Martin'i akılda tutarak tasarım fikri olduğunu, ancak onunla evli olmadığını söyler. Kesinlikle. Sonuç: TDD devam eden tek tasarım etkinliği değil, tasarımın ayrıntılarının nasıl ortaya çıktığını da gösteriyor. TDD, diğer tasarım faaliyetlerinden önce gelmeli ve genel tasarımın nasıl yaratıldığını ve geliştirildiğini ele alan genel bir yaklaşıma (Çevik) uygun olmalıdır.

FYI - Maddi ve gerçekçi örnekler veren konuyla ilgili tavsiye ettiğim iki kitap:

Testlerle Yönlendirilen Büyüyen Nesne Yönelimli Yazılımlar - tam bir proje örneği açıklar ve verir. Bu, test hakkında değil, tasarım hakkında bir kitaptır . Test, tasarım etkinlikleri sırasında beklenen davranışı belirleme aracı olarak kullanılır.

test odaklı geliştirme Pratik Rehber - küçük bir uygulama bile olsa, eksiksiz ve yavaş adım adım yürüyen bir uygulama.


0

TTD tasarım keşfini başarı ile değil test hatasıyla yönlendirir, bu nedenle bilinmeyenleri test edebilir ve yineleyerek tekrar test edebilirsiniz, çünkü bilinmeyenler sonuçta ortaya çıkar, ünite testlerinin eksiksiz bir şekilde çalışmasına neden olur - devam eden bakım için çok güzel bir şey ve denenmesi çok zor bir şey Kod yazıldıktan sonra serbest bırakılır.

Örneğin, bir gereklilik, girdilerin henüz bilinmediği, birkaç farklı formatta olabilmesi olabilir. TDD'yi kullanarak önce herhangi bir giriş formatı verilen uygun çıkışın sağlandığını doğrulayan bir test yazarsınız . Açıkçası bu test başarısız olacak, bu nedenle bilinen formatları işlemek ve yeniden test etmek için kod yazıyorsunuz. Bilinmeyen formatlar gereksinim toplama yoluyla ortaya çıktığından, kod yazılmadan önce yeni testler yazılır, bunlar da başarısız olur. Daha sonra, yeni formatları desteklemek için yeni kod yazılır ve tüm testler tekrar regresyon şansını azaltır.

Birim arızasını "bozuk" kod yerine "bitmemiş" kod olarak düşünmek de faydalıdır. TDD bitmemiş birimlere (beklenen arızalar) izin verir, ancak kırılmış birimlerin oluşumunu azaltır (beklenmeyen arızalar).


1
Bunun geçerli bir iş akışı olduğunu kabul ediyorum, ancak üst düzey mimarlığın böyle bir iş akışından nasıl ortaya çıkabileceğini gerçekten açıklamıyor.
Robert Harvey,

1
Doğru, MVC paterni gibi üst düzey bir mimari, yalnızca TDD'den çıkmayacak. Ancak, TDD'den ortaya çıkabilen, kolayca test edilebilecek şekilde tasarlanan koddur; bu, kendi başına bir tasarım düşüncesidir.
Daniel Pereira

0

Soruda belirtildiği gibi:

... pek çok kitap sihirli bir tavşanı şapkadan çıkarır ve "Ürün Hizmetini Test Etme" ye dalırlar, ancak ilk başta bir Ürün Hizmeti olduğu sonucuna nasıl geldiklerini açıklamazlar.

Bu sonuca, bu ürünü nasıl test edeceklerini düşünerek geldiler. "Bu ne tür bir ürün yapar?" "Peki, bir hizmet yaratabiliriz". "Tamam, böyle bir hizmet için bir test yazalım"


0

Bir işlevsellik birçok tasarıma sahip olabilir ve TDD size hangisinin en iyisi olduğunu tamamen söylemeyecektir. Testler, daha fazla modüler kod oluşturmanıza yardımcı olsa bile, üretim gerçeğine değil, test gereksinimlerine uygun modüller oluşturmanıza da yol açabilir. Bu yüzden nereye gittiğinizi ve işlerin tüm resme nasıl sığması gerektiğini anlamalısınız. Aksi taktirde, İşlevsel ve İşlevsel Olmayan gereklilikler vardır, sonuncuyu unutma.

Tasarım ile ilgili olarak Robert C. Martin kitaplarına (Çevik Kalkınma) ve aynı zamanda Martin Fowler'in Kurumsal Uygulama Mimarisi ve Etki Alanı Sürücüsü Tasarım Modellerine de atıfta bulunuyorum. Daha sonraları, Varlıkların ve İlişkilerin gerekliliklerin dışına çıkarılmasında özellikle sistematiktir.

Ardından, bu varlıkları nasıl yöneteceğiniz konusunda mevcut seçeneklere dair iyi bir fikir edindiğinizde, size TDD yaklaşımını besleyebilirsiniz.


0

TDD benim tasarımımı ilk önce keşfetmemi sağlamıyor mu?

Hayır.

İlk önce tasarlamadığınız bir şeyi nasıl test edebilirsiniz?

Göstermek için şu 3 gereksinimi hayal edin:

  • Bir kataloğun bir ürün listesine sahip olması gerekir
  • Katalog bir kullanıcının hangi ürünleri görüntülediğini hatırlamalıdır.
  • Kullanıcılar bir ürün arayabilmeli

Bunlar şart değildir, bunlar veri tanımlarıdır. Yazılımınızın işi nedir bilmiyorum, ancak analistlerin bu şekilde konuşması muhtemel değildir.

Sisteminizin değişmezlerinin ne olduğunu bilmeniz gerekir.

Bir gereksinim şöyle bir şey olurdu:

  • Stokta bu ürünün yeterli olması durumunda, müşteri belirli bir miktarda ürün sipariş edebilir.

Öyleyse tek şart buysa, şöyle bir sınıfınız olabilir:

public class Product {

  private int quantity;

  public Product(int initialQuantity) {
    this.quantity = initialQuantity;
  }

  public void order(int quantity) {
    // To be implemented.
  }

}

Sonra TDD'yi kullanarak order () yöntemini uygulamadan önce bir test senaryosu yazarsınız.

public void ProductTest() {

    public void testCorrectOrder() {

        Product p = new Product(10);
        p.order(3);
        p.order(4);

    }

    @Expect(ProductOutOfStockException)
    public void testIncorrectOrder() {

        Product p = new Product(10);
        p.order(7);
        p.order(4);

    }

}

Böylece ikinci test başarısız olur, sonra order () yöntemini istediğiniz gibi uygulayabilirsiniz.


0

Oldukça haklısın TDD belirli bir tasarımın iyi bir şekilde uygulanmasına neden olacaktır . Tasarım sürecinize yardımcı olmaz.


Ancak, çalışma kodunu bozmadan tasarımınızı geliştirmeniz için güvenlik ağı sağlar. Bu çoğu insanın atladığı refactoring.
Adrian Schneider,

-3

TDD çok yardımcı olur, ancak yazılım geliştirmede önemli bir yer vardır. Geliştirici, yazılan kodu dinlemelidir . Yeniden kaplama TDD döngüsünde 3. kısımdır. Bu, bir sonraki kırmızı teste geçmeden önce geliştiricinin odaklanıp düşünmesi gereken ana adımdır. Herhangi bir çoğaltma var mı? SOLID ilkeleri uygulanır mı? Yüksek uyum ve düşük bağlanma ne olur? Peki ya isimler? Testlerden çıkan kodu yakından inceleyin ve değiştirilmesi, yeniden tasarlanması gereken bir şey olup olmadığına bakın. Kodu ve kodu sormak, nasıl tasarlanacağını size söyleyecektir. Genellikle çoklu test setleri yazarım, o listeyi inceleyin ve ilk basit tasarımı yarattım, "final" olması gerekmiyor, genellikle değil, çünkü yeni testler eklenirken değişti. Tasarımın geldiği yer burasıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.