window.pipedriveLeadboosterConfig = { bas: 'leadbooster-chat.pipedrive.com', företagId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(funktion () { var w = fönster if (w.LeadBooster) { console.warn('LeadBooster finns redan') } annars { w.LeadBooster = { q: [], on: funktion (n, h) { this.q.push({ t: "o", n: n, h: h }) }, trigger: funktion (n) { this.q.push({ t: 't', n: n }) }, } } })() TESTA JAVASCRIPT ... MED RUBY?! - Den kodaste
Codest
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Industrier
    • Fintech & bankverksamhet
    • E-commerce
    • Adtech
    • Hälsoteknik
    • Tillverkning
    • Logistik
    • Fordon
    • IOT
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
Pil tillbaka GÅ TILLBAKA
2019-02-04
Utveckling av programvara

TESTA JAVASCRIPT ... MED RUBY?!

Pawel Wal

Codest är främst en Ruby-butik, men ett av de många projekt som vi bygger är i JavaScript. Det är ett bibliotek för klientsidan som körs i en ganska utmanande miljö: det måste stödja i stort sett alla webbläsare som finns, inklusive mycket gamla, och dessutom interagerar det med en mängd externa skript och tjänster. Det är jättekul.

Det märkliga fallet med icke-bundlade beroenden

Med ovanstående krav kommer en hel uppsättning utmaningar som vanligtvis inte finns i en klientsida projektoch en del av dessa problem har att göra med testning. Naturligtvis har vi en oklanderlig uppsättning enhetstester och vi kör dem mot en mycket stor matris av webbläsare / operativsystemskombinationer i vår CI / CD-miljö, men det ensam utforskar inte allt som kan gå fel.

På grund av den övergripande arkitekturen i det ekosystem vi arbetar i är vi beroende av att vissa externa bibliotek laddas parallellt med våra egna - det vill säga att vi inte paketerar dem med vår kodDet kan vi inte, och det finns inget att göra åt det. Det innebär en intressant utmaning, eftersom dessa bibliotek:

  • kanske inte ens finns där - om någon strular till det med att implementera vårt bibliotek,
  • kan finnas där, men i felaktiga/inkompatibla versioner,
  • kan ha modifierats av någon annan kod som är med på resan i en viss implementering.

Detta visar tydligt varför enhetstester inte räcker: de testar isolerat från den verkliga världen. Säg att vi mockar upp någon del av något externt biblioteks offentliga API, baserat på vad vi har upptäckt i dess dokument, och kör ett enhetstest mot det. Vad bevisar det?

Du kanske frestas att säga "det betyder att det fungerar med det externa bibliotekets API", men du skulle - tyvärr - ha fel. Det betyder bara att det interagerar korrekt med en delmängd av det externa bibliotekets offentliga API, och även då bara med den version som vi har mockat upp.

Tänk om biblioteket bokstavligen förändras framför näsan på oss? Tänk om det - där ute i naturen - får några konstiga svar som gör att det träffar en annan, odokumenterad kodväg? Kan vi ens skydda oss mot det?

Rimligt skydd

Inte 100%, nej - miljön är för komplex för det. Men vi kan vara någorlunda säkra på att allt fungerar som det ska med några generaliserade exempel på vad som kan hända med vår kod i naturen: vi kan göra integrationstester. Enhetstesterna säkerställer att vår kod körs korrekt internt, och integrationstesterna måste säkerställa att vi "pratar" korrekt med de bibliotek som vi inte kan kontrollera. Och inte med stubbar av dem heller - faktiska, levande bibliotek.

Vi kan helt enkelt använda något av de tillgängliga ramverken för integrationstest för JavaScriptVi vill bara bygga en enkel HTML-sida, lägga till några anrop till vårt bibliotek och fjärrbiblioteken på den och ge den en rejäl omgång. Vi vill dock inte översvämma någon av fjärrtjänsternas slutpunkter med anrop som genereras av våra CI/CD-miljöer. Det skulle störa viss statistik, eventuellt förstöra vissa saker och - sist men inte minst - vi skulle inte vara särskilt trevliga om vi gjorde någons produktion till en del av våra tester.

Men var det ens möjligt att integrationstesta något så komplext? Eftersom Ruby är vår första och främsta kärlek tog vi hjälp av vår expertis och började fundera på hur vi brukar göra integrationstester med fjärrtjänster i Ruby-projekt. Vi kanske använder något som vcr gem för att spela in vad som händer en gång och sedan spela upp det för våra tester när det behövs.

Ange proxy

Internt uppnår VCR detta genom att proxyförfrågningar. Det var vår aha-upplevelse. Vi behövde proxy varje begäran som inte borde träffa något på det "riktiga" internet till några stubbed svar. Då kommer den inkommande datan att överlämnas till det externa biblioteket och vår kod körs som vanligt.

När vi prototyper något som verkar komplicerat, faller vi ofta tillbaka på Ruby som en tidsbesparande metod. Vi bestämde oss för att göra en prototyp test harness för vår JavaScript i Ruby för att se hur väl proxy-idén kommer att fungera innan vi åtar oss att bygga något mer komplicerat i (eventuellt) JavaScript. Det visade sig vara förvånansvärt enkelt. Faktum är att det är så enkelt att vi kommer att bygga en i den här artikeln tillsammans. 🙂

Ljus, kamera... vänta, vi glömde rekvisitan!

Naturligtvis kommer vi inte att ha att göra med den "riktiga saken" - att förklara ens lite av vad vi bygger är långt bortom ramen för ett blogginlägg. Vi kan bygga något snabbt och enkelt för att ersätta biblioteken i fråga och sedan fokusera mer på Ruby-delen.

För det första behöver vi något som kan ersätta det externa bibliotek som vi har att göra med. Vi behöver det för att uppvisa ett par beteenden: det ska kontakta en extern tjänst, avge en händelse här och där, och framför allt - inte byggas med tanke på enkel integration 🙂

Det här är vad vi kommer att använda:

/* global XMLHttpRequest, Event */

const URL = 'https://poloniex.com/public/?command=returnTicker'
const METHOD = 'GET'

module.exports = {
fetch: function () {
var req = new XMLHttpRequest()
req.responseType = 'json'
req.open(METHOD, URL, true)
var doneEvent = new Event('example:fetched')

req.onreadystatechange = function (aEvt) {
  if (req.readyState === 4) {
    if (req.status === 200) {
      this.data = req.response
    } else {
      this.error = true
    }
    window.dispatchEvent(doneEvent)
  }
}.bind(this)

req.send(null)

},
error: false,
data: {}
}

Du kommer att märka att det anropar ett öppet API för vissa data - i det här fallet några kryptovalutakurser, eftersom det är vad som är allt raseri nuförtiden Detta API exponerar inte en sandlåda och det är hastighetsbegränsat, vilket gör det till ett utmärkt exempel på något som faktiskt inte bör träffas i tester.

Du kanske märker att detta faktiskt är en NPM-kompatibel modul, medan jag har antytt att skriptet som vi normalt hanterar inte är tillgängligt på NPM för enkel paketering. För den här demonstrationen räcker det med att den uppvisar ett visst beteende, och jag vill hellre ha en enkel förklaring här på bekostnad av en alltför stor förenkling.

Bjuda in skådespelarna

Nu behöver vi också något som kan ersätta vårt bibliotek. Återigen håller vi kraven enkla: det måste anropa vårt "externa" bibliotek och göra något med utdata. För att hålla den "testbara" delen enkel låter vi den också göra dubbel loggning: både till konsolen, som är lite svårare att läsa i specifikationer, och till en globalt tillgänglig array.

window.remote = require('exempel på fjärrsamtal')

window.failedMiserably = sant
window.logs = []

funktion log (meddelande) {
window.logs.push(meddelande)
console.log(meddelande)
}

window.addEventListener('example:fetched', function () {
if (window.remote.error) {
log('[EXEMPEL] Fjärrhämtning misslyckades')
window.failedMiserably = true
} annars {
log('[EXEMPEL] Fjärrhämtning lyckades')
log([EXEMPEL] BTC till ETH: ${window.remote.data.BTC_ETH.last})
}
})

window.remote.fetch()

Jag håller också beteendet häpnadsväckande enkelt med avsikt. Som det är nu finns det bara två faktiskt intressanta kodvägar att specificera för, så vi kommer inte att svepas under en lavin av specifikationer när vi går vidare genom bygget.

Allt bara snäpper ihop

Vi lägger upp en enkel HTML-sida:

<code> <!DOCTYPE html>
 <html>
 <head>
   <title>Exempel på sida</title>
   <script type="text/javascript" src="./index.js"></script>
 </head>
 <body></body>
 </html>

I den här demonstrationen kommer vi att paketera vår HTML och JavaScript tillsammans med Tomten mycket enkel webbapp buntare. Jag gillar Parcel mycket för tider som dessa, när jag kastar ihop ett snabbt exempel eller hackar på en back-of-napkin klass idé. När du gör något så enkelt att det skulle ta längre tid att konfigurera Webpack än att skriva den kod du vill ha, är det det bästa.

Det är också tillräckligt diskret för att när jag vill byta till något som är lite mer stridstestat behöver jag inte göra nästan någon backpedaling från Parcel, vilket inte är något du kan säga om Webpack. Observera dock försiktighet - Parcel är i tung utveckling och problem kan och kommer att presentera sig; Jag har haft ett problem där den transpilerade JavaScript-utgången var ogiltig på en äldre Node.js. Slutsatsen: gör det inte till en del av din produktionspipeline ännu, men ge det en snurr ändå.

Utnyttja kraften i integrationen

Nu kan vi konstruera vårt testsystem.

För själva specifikationsramverket har vi använt rspec. I utvecklingsmiljöer testar vi med riktiga, icke-huvudlösa Chrome - uppgiften att köra och kontrollera det har fallit på Watir (och dess pålitliga sidekick watir-rspec). För vår proxy har vi bjudit in Puffing Billy och rack till festen. Slutligen vill vi köra om vår JavaScript-byggnad varje gång vi kör specifikationerna, och det uppnås med kokain.

Det är en hel del rörliga delar, och därför är vår spec-hjälpare något... involverad även i det här enkla exemplet. Låt oss ta en titt på det och plocka isär det.

Dir['./spec/support/*/.rb'].each { |f| require f }

TEST_LOGGER = Logger.new(STDOUT)

RSpec.configure do |config|
config.before(:suite) { Cocaine::CommandLine.new('npm', 'run build', logger: TEST_LOGGER).run }

config.include Watir::RSpec::Helper
config.include Watir::RSpec::Matchare

config.include ProxySupport

config.order = :slumpmässig
BrowserSupport.configure(config)
slut

Billy.configure gör |c|
c.cache = false
c.cacherequestheaders = false
c.persistcache = false
c.recordstubrequests = true
c.logger = Logger.new(File.expandpath('../log/billy.log', FILE))
slut

Innan hela sviten kör vi vårt anpassade byggkommando genom cocaine. Den TEST_LOGGER-konstanten kan vara lite överdriven, men vi är inte särskilt bekymrade över antalet objekt här. Vi kör naturligtvis specifikationer i slumpmässig ordning, och vi måste inkludera alla godsaker från watir-rspec. Vi måste också ställa in Billy så att den inte gör någon cachelagring, men omfattande loggning till spec/log/billy.log. Om du inte vet om en begäran faktiskt stubbas eller om den träffar en live-server (hoppsan!) är den här loggen rena guldet.

Jag är säker på att dina skarpa ögon redan har sett ProxySupport och BrowserSupport. Du kanske tror att våra anpassade godsaker sitter där inne ... och du skulle ha helt rätt! Låt oss se vad BrowserSupport gör först.

En webbläsare, kontrollerad

Låt oss först presentera TempBrowser:

klass TempBrowser
def hämta
@webbläsare ||= Watir::Browser.new(web_driver)
slut

def kill
@browser.close if @browser
@webbläsare = nil
slut

privat

def web_driver
Selenium::WebDriver.for(:chrome, alternativ: alternativ)
slut

def alternativ
Selenium::WebDriver::Chrome::Options.new.tap do |options|
options.addargument '--auto-öppna-devtools-för-tabs'
options.addargument "--proxy-server=#{Billy.proxy.host}:#{Billy.proxy.port}"
slut
slut
slut

När vi arbetar bakåt genom samtalsträdet kan vi se att vi ställer in en Selenium-webbläsaralternativuppsättning för Chrome. Ett av alternativen vi passerar in i det är instrumentellt i vår installation: det instruerar Chrome-instansen att proxy allt genom vår Puffing Billy-instans. Det andra alternativet är bara trevligt att ha - varje instans vi kör som inte är huvudlös kommer att ha inspektionsverktygen automatiskt öppna. Det sparar oss otaliga mängder Cmd + Alt + I per dag 😉

När vi har ställt in webbläsaren med dessa alternativ skickar vi den vidare till Watir och det är i stort sett allt. Den döda metoden är lite socker som gör att vi upprepade gånger kan stoppa och starta om drivrutinen om vi behöver utan att kasta bort TempBrowser-instansen.

Nu kan vi ge våra rspec-exempel ett par superkrafter. Först och främst får vi ett smidigt webbläsare hjälpmetod som våra specifikationer mestadels kommer att kretsa kring. Vi kan också använda oss av en praktisk metod för att starta om webbläsaren för ett visst exempel om vi gör något superkänsligt. Naturligtvis vill vi också döda webbläsaren efter att testsviten är klar, eftersom vi under inga omständigheter vill ha långvariga Chrome-instanser - för vårt RAM-minnes skull.

modul BrowserSupport
def self.browser
@webbläsare ||= TempBrowser.new
slut

def self.configure(config)
config.around(:each) do |exempel|
BrowserSupport.browser.kill if exempel.metadata[:clean]
@browser = BrowserSupport.browser.get
@browser.cookies.clear
@browser.driver.manage.timeouts.implicit_wait = 30
exempel.kör
slut

config.after(:suite) do
  BrowserSupport.browser.kill
slut

slut
slut

Koppla upp proxyn

Vi har konfigurerat en webbläsare och spec-hjälpare, och vi är redo att börja proxiera förfrågningar till vår proxy. Men vänta, vi har inte satt upp den än! Vi skulle kunna göra upprepade anrop till Billy för varje exempel, men det är bättre att skaffa ett par hjälpmetoder och spara ett par tusen tangenttryckningar. Det är vad ProxySupport gör.

Den som vi använder i vår testuppsättning är något mer komplex, men här är en allmän idé:

frozenstringliteral: true

kräver 'json'

modul ProxySupport
HEADERS = {
'Access-Control-Allow-Methods' => 'GET',
'Access-Control-Allow-Headers' => 'X-Requested-With, X-Prototype-Version, Content-Type',
'Access-Control-Allow-Origin' => '*'
}.freeze

def stubjson(url, fil)
Billy.proxy.stub(url).andreturn({
body: open(file).read,
kod: 200,
headers: HEADERS.dup
})
slut

def stubstatus(url, status)
Billy.proxy.stub(url).andreturn({
kropp: '',
kod: status,
headers: HEADERS.dup
})
slut

def stubpage(url, fil)
Billy.proxy.stub(url).andreturn(
body: open(file).read,
content_type: 'text/html',
kod: 200
)
slut

def stubjs(url, fil)
Billy.proxy.stub(url).andreturn(
body: open(file).read,
content_type: 'applikation/javascript',
kod: 200
)
slut
slut

Vi kan stubba:

  • HTML-sidförfrågningar - för vår huvudsida "playground",
  • JS-förfrågningar - för att betjäna vårt paketerade bibliotek,
  • JSON-förfrågningar - för att stubba förfrågan till fjärr-API:et,
  • och bara en "vad som helst"-förfrågan där vi bara bryr oss om att returnera ett visst HTTP-svar som inte är 200.

Detta räcker gott och väl för vårt enkla exempel. På tal om exempel - vi borde sätta upp ett par!

Testa den goda sidan

Vi måste först koppla ihop ett par "rutter" för vår proxy:

let(:pageurl) { 'http://myfancypage.local/index.html' }
let(:jsurl) { 'http://myfancypage.local/dist/remote-caller-example.js' }

let(:pagepath) { './dist/index.html' }
let(:jspath) { './dist/remote-caller-example.js' }

innan do
stubpage pageurl, sidväg
stubjs jsurl, jspath
slut

Det är värt att notera att från rspecs perspektiv hänvisar de relativa sökvägarna här till huvudprojektkatalogen, så vi laddar vår HTML och JS direkt från dist katalog - som byggts av Parcel. Du kan redan nu se hur dessa stub_* hjälpredor kommer väl till pass.

Det är också värt att notera att vi placerar vår "falska" webbplats på en .lokal TLD. På så sätt bör inte några okontrollerade förfrågningar undkomma vår lokala miljö om något skulle gå fel. Som en allmän praxis skulle jag rekommendera att åtminstone inte använda "riktiga" domännamn i stubbar om det inte är absolut nödvändigt.

En annan anmärkning vi bör göra här handlar om att inte upprepa oss. När proxy-routingen blir mer komplex, med många fler sökvägar och webbadresser, kommer det att finnas ett verkligt värde i att extrahera den här installationen till en delad kontext och helt enkelt inkludera den efter behov.

Nu kan vi spekulera i hur vår "bra" väg ska se ut:

sammanhang "med korrekt svar" do
före do
stubjson %r{http://poloniex.com/public(.*)}, './spec/fixtures/remote.json'
goto pageurl
Watir::Wait.until { browser.execute_script('return window.logs.length === 2') }
slut

it 'loggar korrekta data' do
expect(browser.execute_script('return window.logs')).to(
eq(['[EXEMPEL] Fjärrhämtning framgångsrik', '[EXEMPEL] BTC till ETH: 0,03619999'])
)
slut
slut

Det är ganska enkelt, eller hur? Lite mer inställning här - vi stubbar JSON-svaret från fjärr-API:et med en fixtur, går till vår huvud-URL och sedan... väntar vi.

Den längsta väntetiden

Väntetiderna är ett sätt att arbeta runt en begränsning som vi har stött på med Watir - vi kan inte på ett tillförlitligt sätt vänta på t.ex. JavaScript-händelser, så vi måste fuska lite och "vänta" tills skripten har flyttat något objekt som vi kan komma åt till ett tillstånd som är av intresse för oss. Nackdelen är att om det tillståndet aldrig kommer (till exempel på grund av en bugg) måste vi vänta på att watir-servitören ska ta timeout. Detta driver upp specifikationstiden lite. Specifikationen misslyckas dock fortfarande på ett tillförlitligt sätt.

När sidan har "stabiliserats" i det tillstånd som vi är intresserade av kan vi utföra lite mer JavaScript i samband med sidan. Här ringer vi upp loggarna som skrivits till den offentliga arrayen och kontrollerar om de är vad vi förväntade oss.

Som en sidoanteckning - det är här som stubbning av fjärrförfrågan verkligen lyser. Svaret som loggas till konsolen är beroende av växelkursen som returneras av fjärr-API: et, så vi kunde inte på ett tillförlitligt sätt testa logginnehållet om de fortsatte att förändras. Det finns naturligtvis sätt att arbeta runt det, men de är inte särskilt eleganta.

Testa den dåliga grenen

Ytterligare en sak att testa: grenen "failure".

context "med misslyckat svar" do
före do
stubstatus %r{http://poloniex.com/public(.*)}, 404
goto pageurl
Watir::Wait.until { browser.execute_script('return window.logs.length === 1') }
slut

it 'fel i loggar' do
expect(browser.execute_script('return window.logs')).to(
eq(['[EXEMPEL] Fjärrhämtning misslyckades'])
)
slut
slut

Det är mycket likt ovanstående, med skillnaden att vi stubbar svaret så att det returnerar en 404 HTTP-statuskod och förväntar oss en annan logg.

Låt oss köra våra specifikationer nu.

% bunt exekvering rspec
Slumpmässigt med frö 63792
I, [2017-12-21T14:26:08.680953 #7303] INFO -- : Kommando :: npm run build

Anrop på distans
med korrekt svar
loggar korrekt data
med misslyckat svar
loggar misslyckande

Avslutades på 23,56 sekunder (filerna tog 0,86547 sekunder att ladda)
2 exempel, 0 misslyckanden

Woohoo!

Slutsats

Vi har kort diskuterat hur JavaScript kan integrationstestas med Ruby. Även om det ursprungligen ansågs vara mer av en nödlösning, är vi ganska nöjda med vår lilla prototyp nu. Vi överväger naturligtvis fortfarande en ren JavaScript-lösning, men under tiden har vi ett enkelt och praktiskt sätt att reproducera och testa några mycket komplexa situationer som vi har stött på i naturen.

Om du funderar på att bygga något liknande själv, bör det noteras att det inte är utan sina begränsningar. Till exempel om det du testar blir riktigt AJAX-tungt, kommer Puffing Billy att ta lång tid att svara. Även om du måste stubba några SSL-källor kommer det att krävas lite mer krångel - titta på watir-dokumentationen om det är ett krav du har. Vi kommer säkert att fortsätta utforska och leta efter de bästa sätten att hantera vårt unika användningsfall - och vi kommer att se till att låta dig veta vad vi fick reda på också.

Relaterade artiklar

Utveckling av programvara

Bygg framtidssäkrade webbappar: Insikter från The Codest:s expertteam

Upptäck hur The Codest utmärker sig genom att skapa skalbara, interaktiva webbapplikationer med banbrytande teknik som ger sömlösa användarupplevelser på alla plattformar. Läs om hur vår expertis driver digital omvandling och affärsutveckling...

DEKODEST
Utveckling av programvara

Topp 10 Lettlandsbaserade mjukvaruutvecklingsföretag

Läs mer om Lettlands främsta mjukvaruutvecklingsföretag och deras innovativa lösningar i vår senaste artikel. Upptäck hur dessa teknikledare kan hjälpa till att lyfta ditt företag.

thecodest
Lösningar för företag och uppskalningsföretag

Java Software Development Essentials: En guide till framgångsrik outsourcing

Utforska denna viktiga guide om framgångsrik outsourcing av Java-programvaruutveckling för att förbättra effektiviteten, få tillgång till expertis och driva projektframgång med The Codest.

thecodest
Utveckling av programvara

Den ultimata guiden till outsourcing i Polen

Den kraftiga ökningen av outsourcing i Polen drivs av ekonomiska, utbildningsmässiga och tekniska framsteg, vilket främjar IT-tillväxt och ett företagsvänligt klimat.

TheCodest
Lösningar för företag och uppskalningsföretag

Den kompletta guiden till verktyg och tekniker för IT-revision

IT-revisioner säkerställer säkra, effektiva och kompatibla system. Läs mer om hur viktiga de är genom att läsa hela artikeln.

Codest
Jakub Jakubowicz CTO och medgrundare

Prenumerera på vår kunskapsbas och håll dig uppdaterad om expertisen från IT-sektorn.

    Om oss

    The Codest - Internationellt mjukvaruutvecklingsföretag med teknikhubbar i Polen.

    Förenade kungariket - Huvudkontor

    • Kontor 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokala tekniknav

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polen

      Codest

    • Hem
    • Om oss
    • Tjänster
    • Fallstudier
    • Vet hur
    • Karriär
    • Ordbok

      Tjänster

    • Det rådgivande
    • Utveckling av programvara
    • Backend-utveckling
    • Frontend-utveckling
    • Staff Augmentation
    • Backend-utvecklare
    • Ingenjörer inom molntjänster
    • Dataingenjörer
    • Övriga
    • QA-ingenjörer

      Resurser

    • Fakta och myter om att samarbeta med en extern partner för mjukvaruutveckling
    • Från USA till Europa: Varför väljer amerikanska startup-företag att flytta till Europa?
    • Jämförelse av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinerna), Eurasien (Turkiet)
    • Vilka är de största utmaningarna för CTO:er och CIO:er?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Användarvillkor för webbplatsen

    Copyright © 2025 av The Codest. Alla rättigheter reserverade.

    sv_SESwedish
    en_USEnglish de_DEGerman da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek sv_SESwedish