BDD JavaScript-ben CucumberJS-sel

0

írta: Todd Anderson

korábban írtam a tesztvezérelt fejlesztésről (TDD) a JavaScript-ben, nevezetesen a viselkedésvezérelt fejlesztés (BDD) stílusú könyvtár Jasmine használatával egy Tesztvezérelt élelmiszerbolt-alkalmazás felépítéséről szóló sorozatban. Ebben a bejegyzéssorozatban a funkciók és a forgatókönyvek felhasználói történeteire gondoltam, mint tényleges fejlesztési feladatokra, és – visszaolvasva-mindez nagyon zöld (nincs szójáték), amennyiben megtalálom a módját, hogy tesztvezérelt kódot szállítsak. Nincs ezzel semmi baj, és valószínűleg ugyanúgy fogom megvizsgálni ezt és a következő hozzászólásokat. Ennek ellenére továbbra is igaz, hogy a TDD a legjobb módja annak, hogy tömör, tesztelt és jól átgondolt kódot szolgáltasson.

azóta azonban beépítettem egy másik eszközt a TDD munkafolyamatomba a JavaScript-alapú projektekhez, amely lehetővé teszi számomra, hogy a funkciók specifikációit jobban integráljam a fejlesztésemhez, és valóban magában foglalja a Viselkedésvezérelt fejlesztés jelenlegi ideálját: CucumberJS. Lényegében, ez lehetővé teszi számomra, hogy valóban betartsam a TDD – t, miközben kívülről fejlesztem a futó automatizált teszteket, amelyek kudarcot vallanak, amíg meg nem írom a funkciót támogató kódot.

feltételezések és megjegyzések

az ebben a bejegyzésben szereplő példákhoz feltételezzük, hogy ismeri a NodeJS-t, az npm-et, a Csomópontmodulok fejlesztését és a közös egységtesztelési gyakorlatokat, mivel ezek a témák túl nagyok ahhoz, hogy megvitassák ezt a bejegyzést.

a témához kapcsolódó támogató fájlok a következő címen érhetők el::
https://github.com/bustardcelly/cucumberjs-examples

CucumberJS

a CucumberJS a népszerű BDD eszköz JavaScript portja uborka (amely maga is az RSpec átírása volt). Ez lehetővé teszi, hogy meghatározza a funkció SPECIFIKÁCIÓK egy Domain-specifikus nyelv (DSL)-úgynevezett Gherkin–, és futtassa a specifikációk egy parancssori eszköz, amely beszámol a múló és/vagy nem a forgatókönyvek és a lépéseket azok állnak.

fontos megjegyezni, hogy a uborka maga nem nyújt alapértelmezett állítási könyvtárat. Ez egy tesztelési keretrendszer, amely parancssori eszközt biztosít, amely meghatározott funkciókat fogyaszt, és a JavaScript-ben írt lépések futtatásával ellenőrzi a forgatókönyveket. Ez a fejlesztők választás, hogy tartalmazza a kívánt állítást Könyvtár használt annak érdekében, hogy ezeket a lépéseket át, vagy nem. Szándékom, hogy példával tisztázzam a folyamatot egyetlen funkcióval, több forgatókönyvvel ebben a bejegyzésben.

telepítés

telepítheti a CucumberJS-t a projektbe az npm használatával:

$ npm install cucumber --save-dev

uborka

ha az előző TDD sorozatban követte, akkor az abban a sorozatban meghatározott specifikációkat az Uborkához hasonlóan találja meg. Valójában, én lesz újra hash egy funkció spec, hogy a sorozat bizonyítani dolgozik az első cuke (aka, múló funkció spec).

ha mi voltunk, hogy remake az élelmiszerbolt lista alkalmazás alatt TDD/BDD segítségével uborka, akkor először kezdeni egy funkció segítségével uborka szintaxis:

/features/add-item.funkció

Feature: Shopper can add an item to their Grocery List As a grocery shopper I want to add an item to my grocery list So that I can remember to buy that item at the grocery store Scenario: Item added to grocery list Given I have an empty grocery list When I add an item to the list Then The grocery list contains a single item Scenario: Item accessible from grocery list Given I have an empty grocery list When I add an item to the list Then I can access that item from the grocery list

a funkció meghatározza az üzleti értéket, míg a forgatókönyvek meghatározzák az értéket biztosító lépéseket. A szoftverfejlesztés világában leggyakrabban ezekből a forgatókönyvekből veszik fel a fejlesztési feladatokat és határozzák meg a minőségbiztosítási teszteket.

két forgatókönyvnél álltam meg, de nagyon könnyen hozzáadhatunk további forgatókönyveket ehhez a funkcióhoz; azonnal eszembe jutnak az elem beillesztési szabályai és a tulajdonságok érvényesítése, amelyek lehetővé teszik egy elem hozzáadását vagy elutasítását. Utólag, lehet, hogy több értelme, hogy hozzon létre külön funkció specifikációk az ilyen részleteket. Mi lehetne tölteni egy egész bejegyzést az ilyen témákról, bár, így térjünk vissza a funkció már meghatározott.

belül minden forgatókönyv egy listát a szekvenciális lépéseket: adott, mikor és akkor. Ezeket a lépéseket hajtja végre a CucumberJS, miután elfogyasztotta ezt a funkciót. Miután minden ilyen, akkor opcionálisan és De, azonban-bár szükséges és elkerülhetetlen időnként-igyekszem távol maradni az ilyen további lépés záradékok.

futtatása

Miután elmentette, hogy le egy fájlt a / jellemzők könyvtárban, akkor majd futtatni uborka:

$ node_modules/.bin/cucumber-js

alapértelmezés szerint a CucumberJS a relative /features könyvtárban található összes jellemző specifikációt elfogyasztja.

az aktuális konzol kimenet fog kinézni, mint a következő, ami lényegében azt jelenti, hogy az összes lépést nem található, vagy meghatározott:

UUUUUU2 scenarios (2 undefined)6 steps (6 undefined)You can implement step definitions for undefined steps with these snippets:this.Given(/^I have an empty grocery list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.When(/^I add an item to the list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.Then(/^The grocery list contains a single item$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.Then(/^I can access that item from the grocery list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});

tehát 6 meghatározatlan lépésünk van, amelyek 2 forgatókönyvet alkotnak, és a CucumberJS ci eszköz még példákat is tartalmaz azok meghatározására!

a részlet megértésének fontos része, hogy csak 4 lépést kell végrehajtani. A mi funkció van 2 Scenerios mindegyik 3 lépésben. Összesen 6 lépés van, de csak 4-et kell meghatároznunk. Ennek az az oka, hogy minden forgatókönyv ugyanazt a megadott és mikor lépést osztja meg; ezeket csak egyszer kell meghatározni, és minden forgatókönyvre külön kell futtatni. Lényegében, ha hasonló lépéseket határoz meg ugyanazzal a kontextussal, akkor az egyes forgatókönyveken belül egyetlen lépéshez újra felhasználja a “beállítást”.

a “Beállítás” szót idézőjelekben használom, mert inkább a kontextus meghatározásának szerepében értem, amikor, majd lépéseket.

nem akarom összekeverni más egységtesztelési gyakorlatok beállítási/lebontási módszereivel – amelyek a CucumberJS – ben a támogatási feladatok előtt/után ismertek -, és több kontextust hordoznak egy olyan környezet létrehozásához, amelyben a teszteket végrehajtják (például kitöltik a felhasználók DB-jét), majd lebontják ezt a beállítást.

Lépésdefiníciók

az előző részben láttuk, hogy a CucumberJS futtatása az Elem hozzáadása funkcióval szemben arra figyelmeztetett minket, hogy meghatározatlan (és bár nem piros színnel nyomtatva, nem sikerült) forgatókönyvek vannak a funkció támogatására. Alapértelmezés szerint a CucumberJS a /features könyvtár összes funkciójában elolvassa a parancs futtatásának helyét, de nem találta meg azokat a támogatott lépésfájlokat, amelyekben ezek a módszerek meg vannak határozva.

mint korábban említettük, a CucumberJS nem nyújt állítási könyvtárat. Az egyetlen feltételezés ezen a ponton – mivel a CucumberJS eszköz a NodeJS alatt fut – az, hogy a támogatott lépések csomópontmodulként kerülnek betöltésre egy végrehajtandó exportált funkcióval. Amint elkezdjük végrehajtani a lépéseket, el kell döntenünk a logikánk érvényesítéséhez használt állításkönyvtárról. Ezt a döntést pillanatnyilag a polcra tesszük, és a barebones beállítása meghiúsul.

először vegyük a CucumberJS ci eszköz által biztosított lépésdefiníciókat, és dobjuk őket egy csomópont modulba:

/features/stepdefinitions/add-item.lépések.js_

'use strict';module.exports = function() { this.Given(/^I have an empty grocery list$/, function(callback) { callback.pending(); }); this.When(/^I add an item to the list$/, function(callback) { callback.pending(); }); this.Then(/^The grocery list contains a single item$/, function(callback) { callback.pending(); }); this.Then(/^I can access that item from the grocery list$/, function(callback) { callback.pending(); });};

alapértelmezés szerint a CucumberJS megkeresi a betöltendő lépéseket egy step_definitions nevű mappába a /features könyvtárban a parancs kiadásának helyéhez képest. Opcionálisan használhatja a -r opciót, hogy a CucumberJS betöltse a lépéseket egy másik helyről. Az alapértelmezett Futtatás megegyezik a következő lépés definíciós könyvtár beállításával:

./node_modules/.bin/cucumber-js -r features/step_definitions

a konzol kimenete most a következőképpen néz ki:

P--P--2 scenarios (2 pending)6 steps (2 pending, 4 skipped)

nem túl meglepő, mivel értesítjük a pending állapot visszahívását. CucumberJS belép az első lépés (adott), és azonnal vissza egy függőben lévő értesítést. Mint ilyen, nem zavarja a következő lépések beírását, és kihagyottként jelöli meg őket.

Megjegyzés: túl sok ahhoz, hogy a kliens oldali modulokról és az AMD vs CommonJS-ről beszéljünk. E példa alkalmazásában a CommonJS-t fogom használni, mivel jelenlegi érdekeim a browserify kliens oldali fejlesztéshez való felhasználása. Hosszú ideig a RequireJS és az AMD támogatója voltam. Újra, ez egy teljesen más vita.

adott

ahhoz, hogy közelebb kerüljünk a zöldhez, először az adott lépéssel foglalkozunk:

/features/stepdefinitions/add-item.lépés.js_

'use strict';var GroceryList = require(process.cwd() + '/script/model/grocery-list');module.exports = function() { var myList; this.Given(/^I have an empty grocery list$/, function(callback) { myList = GroceryList.create(); callback(); }); ...};

ha újra futtatnánk, azonnal kivételt kapnánk:

$ ./node_modules/.bin/cucumber-jsmodule.js:340 throw err; ^Error: Cannot find module './script/model/grocery-list' at Function.Module._resolveFilename (module.js:338:15) at Function.Module._load (module.js:280:25) at Module.require (module.js:364:17) at require (module.js:380:17) at Object.<anonymous> (/Users/toddanderson/Documents/workspace/custardbelly/cucumberjs-example/features/step_definitions/add-item.steps.js:3:19)

ami érthető, mivel nem definiáltunk más kódot, csak ezt a lépésdefiníciós modult, és olyan modult próbálunk megkövetelni, amely nem létezik. Ha ragaszkodunk a TDD-hez, ez jó dolog – tudjuk, miért nem sikerül, és számítunk rá – kihúznám a hajam, ha nem dobna kivételt!

annak érdekében, hogy ez átmenjen, létrehozunk egy csomópont modult a megadott könyvtárban, amely egy objektumot exportál egy create módszerrel:

/script/model/grocery-list.js

'use strict';module.exports = { create: function() { return Object.create(null); }};

megadtuk a minimális követelményt, hogy megkapjuk az adott lépést. Majd aggódni a részleteket, ahogy közeledünk az utóbbi lépéseket.

futtassa újra, és a CucumberJS belép az egyes forgatókönyvek mikor lépésébe, és a függőben lévő visszatérés miatt megszakad.

$ ./node_modules/.bin/cucumber-js.P-.P-2 scenarios (2 pending)6 steps (2 pending, 2 skipped, 2 passed)

amikor

az előző részben, hogy az adott lépés át minden forgatókönyv megvalósítottuk a kezdetektől egy élelmiszerbolt lista modell generált gyári módszer, create, a grocery-list modul. Nem akarok belemenni egy vitába az objektum létrehozásáról, a new operátorról, osztályokról és prototípusokról – legalábbis nem ebben a bejegyzésben -, és azt feltételezem, hogy ismerős és kényelmes (legalábbis a kód olvasásában) az objektummal.hozzon létre meghatározott ECMAScript 5.

a forgatókönyvek mikor lépésének áttekintésében:

When I add an item to the list

…meg kell adnunk egy módot arra, hogy egy elemet hozzáadjunk az adott Élelmiszerboltlistához – és ezt olyan kis kóddal kell megtenni, hogy a lépés áthaladjon.

először is, mi határozza meg az elvárás a make up és add aláírás a élelmiszerbolt lista a lépés definíciók:

/jellemzők/stepdefinitions/add-item.lépés.js_

...module.exports = function() { var myList, listItem = 'apple'; this.Given(/^I have an empty grocery list$/, function(callback) { myList = GroceryList.create(); callback(); }); this.When(/^I add an item to the list$/, function(callback) { myList.add(listItem); callback(); }); ...};

ha újra futtatjuk:

$ ./node_modules/.bin/cucumber-js.F-.F-(::) failed steps (::)TypeError: Object object has no method 'add'

Oooo-weee! Most már beszélünk. Nagy, élénkpiros F ‘ S. 6

annak érdekében, hogy ez újra elmúljon, a lehető legkevesebb kóddal módosítjuk az grocery-list – et:

/forgatókönyv/modell/élelmiszerbolt-lista.js

'use strict';var groceryList = { add: function(item) { // }};module.exports = { create: function() { return Object.create(groceryList); }};

futtassa újra, és a CucumberJS az akkori lépésekhez haladt, amelyek pending állapotot jelentenek.

$ ./node_modules/.bin/cucumber-js..P..P2 scenarios (2 pending)6 steps (2 pending, 4 passed)

ezután

haladtunk a lépés implementációinkon, és elértük azokat a lépéseket, amelyeknél olyan műveleteket és tulajdonságokat állítunk be, amelyek bizonyítják, hogy a forgatókönyvünk megadja a kívánt értéket. Mint korábban említettük, a CucumberJS nem nyújt állítási könyvtárat. Az állításkönyvtárakban a chai, a Sinon és a Sinon-Chai kombinációját részesítem előnyben, de az ebben a bejegyzésben szereplő példákhoz csak a NodeJS-hez tartozó assert modult fogom használni. Azt javasoljuk, hogy nézd meg a többi állítás könyvtárak és hagy egy megjegyzést, ha van egy kedvenc.

Megjegyzés: Ez a rész egy kicsit nehéz lesz, mivel gyorsan váltunk a kód módosításával, és gyakran futtatjuk a spec runnert.

első forgatókönyv

az első forgatókönyv következő lépésének áttekintésében:

Then The grocery list contains a single item

…be kell bizonyítanunk, hogy az élelmiszerbolt-lista példánya minden hozzáadott új elemnél 1-szeresére nő.

frissítse a lépést annak meghatározásához, hogy miként várjuk el a specifikáció érvényesítését:

/feature/stepdefinitions/add-item.lépés.js_

...var assert = require('assert');...module.exports = function() {... this.Then(/^The grocery list contains a single item$/, function(callback) { assert.equal(myList.getAll().length, 1, 'Grocery List should grow by one item.'); callback(); });...};...

behúztuk a assert modult, és megpróbáltuk ellenőrizni, hogy a bevásárlólista hossza 1 – es értékkel nőtt – e az előző lépés futtatása után-amikor-az elem hozzáadásakor.

futtassa ezt, és kivételt kapunk:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)TypeError: Object #<Object> has no method 'getAll'

adjuk hozzá ezt a módszert az élelmiszerbolt-lista modelljéhez:

/forgatókönyv/modell/élelmiszerbolt-lista.js

'use strict';var groceryList = { add: function(item) { // }, getAll: function() { // }};module.exports = { create: function() { return Object.create(groceryList); }};

és vissza a specifikációk futtatásához:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)TypeError: Cannot read property 'length' of undefined

mivel a kód nem ad vissza semmit a getAll() – ból, nem tudunk hozzáférni a length tulajdonsághoz az állítás tesztünkhöz.

ha módosítjuk a kódot, hogy visszatérjen egy tömb:

/feature/stepdefinitions/add-item.lépés.js_

...getAll: function() { return ;}...

és futtassa újra a specifikációkat, megkapjuk az általunk megadott állítási hibaüzenetet:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)AssertionError: Grocery List should grow by one item.

most egy megfelelő kudarcot jelentünk nekünk egy olyan állításból, amely miatt a lépés nem megy át. Hurrá!

Vegyünk egy szusszanást

álljunk meg itt egy pillanatra, mielőtt további kódot adunk hozzá, hogy ez a lépés átmenjen. A szóban forgó probléma valójában nem egy elem hozzáadása a visszaküldött tömbhöz, hanem annak biztosítása, hogy egy elem hozzáadódjon a add metóduson keresztül, és a getAll eredménye az adott elemhez kibővített lista legyen.

az implementáció részleteit, amelyek részt vesznek abban, hogy ez a teszt pass, ahol a csapat használja az architektúra tapasztalat, de ügyelni kell arra, hogy csak a legfontosabb kódot adunk hozzá. Kerülje a túlzásba kerülést, ha az élelmiszerbolt-lista-gyűjtemény modelljének belső részeire gondol. Ez egy csúszós, feszes kötél, amely könnyen leeshet egy nyúllyukba – csakúgy, mint az a rosszul megfogalmazott metafora ..

vissza dolgozni!

ennek a példának az alkalmazásában a propertiesObject Object.create Argumentumát használjuk egy list getter meghatározásához, amely változtatható tömbként szolgál az élelmiszerbolt-lista elemeihez:

/script/model/grocery-list.js

'use strict';var groceryList = { add: function(item) { this.list.push(item); }, getAll: function() { return this.list; }};module.exports = { create: function() { return Object.create(groceryList, { 'list': { value: , writable: false, enumerable: true } }); }};

ha ezt futtatjuk, azt találjuk, hogy az első forgatókönyv most halad!

$ ./node_modules/.bin/cucumber-js.....P2 scenarios (1 pending, 1 passed)6 steps (1 pending, 5 passed)

második forgatókönyv

a 2. forgatókönyv utolsó lépésének áttekintésekor a függőben lévő megvalósítás a hozzáadott elem elérése:

Then I can access that item from the grocery list

ennek a lépésnek a teljesítéséhez ellenőriznünk kell, hogy hozzáférhetünk-e az élelmiszerbolt-listához csatolt elemhez a add() hivatkozással egy elemmel.

mint az élelmiszerbolt lista hosszának elérésekor, számos módon megtehetjük ezt a tesztet a kódban. Ismét úgy érzem, hogy itt jön létre a szoftverfejlesztési tapasztalat és az ízlés az építészet tekintetében, de én is inkább a lehető legkevesebb kódot próbálom előállítani; és én leszek az első, aki elismeri, hogy néha egy kicsit szórakozottan megyek, és több kódot hozok létre, mint amennyire szükség van… ezért próbálkozom.

ez azt jelenti, hogy figyelembe kell vennünk a nyelvi és környezeti specifikációkat is, hogy hogyan kezeljük az állítás átadását – és a böngészőnek, annak történetével, sok mindent figyelembe kell vennie. Ez nem enyhe, ez csak egy előrelátás a követelmények elvárásainak meghatározásában.

konkrétan: tegyük fel, hogy azt mondjuk, hogy a lépés ellenőrizhető a Array.indexOf() módszerrel a gyűjtemény visszaadott ‘getAll()’ az élelmiszerbolt lista objektum? Polifill nélkül, akkor korlátozzuk magunkat az IE 9 vagy annál idősebb állítások átadására. Az ilyen megfontolások csak a jéghegy csúcsa, amikor arról döntenek, hogy mit vezessenek be a kódbázisba a tesztek átadása érdekében, és valóban egy csapat megbeszélésére kell bízni, hogy mit tartanak szükségesnek a termék előállításához.

folytathatnám és folytathatnám, de tegyük fel, hogy a böngészők esetében minden alapot le akarunk fedni (azaz 6 és fel, borzongás). Véleményem szerint, hogy ez a második forgatókönyv zöldre váltson, hozzáadunk egy getItemIndex() módszert a következő aláírással:

+ getItemIndex(itemValue):int

először módosítjuk a lépést, hogy sikertelen legyen:

/feature/stepdefinitions/add-item.lépés.js_

this.Then(/^I can access that item from the grocery list$/, function(callback) { assert.notEqual(myList.getItemIndex(listItem), -1, 'Added item should be found at non-negative index.'); callback();});

ennek a tesztnek az elfogadása az, hogy az az index, amelynél a hozzáadott elem a gyűjteményben található, nem negatív. Ehhez a forgatókönyvhöz nem próbálunk érvényesíteni egy specifikációt arról, hogy egy új elem hol kerül hozzáadásra egy listához (pl.

futás, amely kivételt eredményez:

$ ./node_modules/.bin/cucumber-js.....F(::) failed steps (::)TypeError: Object #<Object> has no method 'getItemIndex'

módosítsuk a bevásárlólista objektumunkat a getItemIndex módszer támogatására:

/script/model/grocery-list.js

'use strict';var groceryList = { add: function(item) { this.list.push(item); }, getAll: function() { return this.list; }, getItemIndex: function(value) { var index = this.list.length; while(--index > -1) { if(this.list === value) { return index; } } return -1; }};module.exports = { create: function() { return Object.create(groceryList, { 'list': { value: , writable: false, enumerable: true } }); }};

a mi végrehajtása getItemIndex, a lista áthalad, és ha elem található, az index vissza. Ellenkező esetben -1 értéket ad vissza. Lényegében, hogy a Array.indexOf módszer működik ECMAScript 5.

Megjegyzés: tudom, hogy ostobaságnak tűnhet az objektum használata.hozzon létre ECMAScript 5, de nem tömb.index. Ennek oka-többnyire-az, hogy általában mindig tartalmazok egy polifillet az objektumhoz.létrehozás és nem tömb.index. Azt hiszem, szokás.

most, ha újra futtatjuk a specifikációkat a CucumberJS alatt:

$ ./node_modules/.bin/cucumber-js......2 scenarios (2 passed)6 steps (6 passed)

a cukorkáink zöldek! (Ez az a pont, hogy törölje a homlokát, és lassú taps).

következtetés

ebben a bejegyzésben bemutattam, hogyan használom a BDD eszközt CucumberJS annak érdekében, hogy betartsam a Tesztvezérelt fejlesztést a JavaScript-ben. Végigmentem egy példa egyetlen funkcióra két forgatókönyvvel, és a sikertelen lépéseket zöld cukorra fordítottam. Ha nem ismeri azt a folyamatot, amikor a tesztek először kudarcot vallanak, csak azért, hogy kódot állítson elő a teszt teljesítéséhez, remélem, hogy követte; Lehet, hogy bőbeszédű vagyok, és úgy tűnik, hogy a folyamat sok időt vesz igénybe, de az ilyen gyakorlatok során a fejlődés zökkenőmentesen mozog, amint belépsz a horonyba. Ezenkívül azt hiszem, hatalmas jutalom van abban, hogy a kódod tesztköteg alatt van, amikor a refaktorálásról és a hibajavításról van szó – mind a fejlesztői egészségügyben, mind az üzleti életben.

ezt a cikket eredetileg a következő címen tették közzé: http://custardbelly.com/blog/blog-posts/2014/01/08/bdd-in-js-cucumberjs/index.html

Kép jóvoltából http://commons.wikimedia.org/wiki/File:Og%C3%B3rki…jpg

e6b2cff6a55928c8f62b9d602add3fed?s=100D = mmr = g

Todd Anderson egy alkalmazásfejlesztő, akinek szenvedélye az építészet és a fejlesztési munkafolyamatok.

az agilis gyakorlatok és a Tesztvezérelt fejlesztés erős támogatójaként több mint egy évtizedes tapasztalattal segített webes, mobil és asztali megoldások szállításában számos vállalati és szórakoztatóipari vállalattal, köztük az Adobe-val, a THQ-val, A Condo Nast Publications-szel és a Motorolával.

blogján gyakran ír a szoftverekről és a technológiáról, támogatja a nyílt forráskódú szoftvereket, és a Githubon keresztül a legjobbakat adja vissza a fejlesztői közösségnek, és nagy öröm volt, hogy társszerzője lehet az O ‘ Reilly és a Wiley Wrox: Amazon profile számos címének.

kövesse Todd-ot a Twitteren

látogasson el Todd oldalára

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.