The Codest
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Indústrias
    • Fintech e Banca
    • E-commerce
    • Adtech
    • Tecnologia da saúde
    • Fabrico
    • Logística
    • Automóvel
    • IOT
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
  • Sobre nós
  • Serviços
    • Desenvolvimento de software
      • Desenvolvimento de front-end
      • Desenvolvimento backend
    • Staff Augmentation
      • Programadores Frontend
      • Programadores de back-end
      • Engenheiros de dados
      • Engenheiros de nuvem
      • Engenheiros de GQ
      • Outros
    • Aconselhamento
      • Auditoria e consultoria
  • Valor para
    • CEO
    • CTO
    • Gestor de entregas
  • A nossa equipa
  • Case Studies
  • Saber como
    • Blogue
    • Encontros
    • Webinars
    • Recursos
Carreiras Entrar em contacto
Seta para trás VOLTAR
2019-02-04
Desenvolvimento de software

TESTAR JAVASCRIPT... COM RUBY?!

Pawel Wal

Enquanto a Codest é primariamente uma loja de Ruby, um dos muitos projetos que estamos construindo é em JavaScript. É uma biblioteca client-side, que roda num ambiente bem desafiador: tem que suportar praticamente todos os navegadores existentes, incluindo os mais antigos, e ainda por cima interage com um monte de scripts e serviços externos. É muito divertido.

O caso curioso das dependências não agrupadas

Com os requisitos acima referidos, surge um conjunto de desafios que não estão normalmente presentes numa solução do lado do cliente projetoe uma classe dessas questões tem a ver com testes. Claro que temos um conjunto impecável de testes unitários e estamos a executá-los contra uma matriz muito grande de combinações de browser/sistema operativo no nosso ambiente CI/CD, mas só isso não explora tudo o que pode correr mal.

Devido à arquitetura global do ecossistema em que estamos a funcionar, dependemos de algumas bibliotecas externas que são carregadas juntamente com as nossas - ou seja, não as juntamos às nossas códigonão podemos e não há nada a fazer quanto a isso. Isso representa um desafio interessante, porque essas bibliotecas:

  • pode nem sequer estar lá - se alguém fizer asneiras na implementação da nossa biblioteca,
  • pode estar lá, mas em versões erradas/incompatíveis,
  • pode ter sido modificado por algum outro código que está junto para o passeio em uma implementação particular.

Isso mostra claramente por que os testes unitários não são suficientes: eles testam isoladamente do mundo real. Digamos que simulamos alguma parte da API pública de alguma biblioteca externa, com base no que descobrimos nos seus documentos, e executamos um teste unitário contra isso. O que é que isso prova?

Poderá sentir-se tentado a dizer "isso significa que funciona com a API da biblioteca externa", mas estaria - infelizmente - errado. Significa apenas que interage corretamente com um subconjunto da API pública da biblioteca externa e, mesmo assim, apenas com a versão que criámos.

E se a biblioteca mudar literalmente de sítio? nós? E se - lá fora, em estado selvagem - receber algumas respostas estranhas que o fazem atingir um caminho de código diferente e não documentado? Podemos sequer proteger-nos contra isso?

Proteção razoável

Não 100%, não - o ambiente é demasiado complexo para isso. Mas podemos ter uma certeza razoável de que tudo funciona como é suposto com alguns exemplos generalizados do que pode acontecer ao nosso código na natureza: podemos fazer testes de integração. Os testes unitários garantem que o nosso código corre corretamente internamente, e os testes de integração precisam de garantir que "falamos" corretamente com as bibliotecas que não podemos controlar. E não com stubs delas, também - bibliotecas reais e vivas.

Poderíamos simplesmente utilizar uma das estruturas de teste de integração disponíveis para JavaScriptPara isso, criamos uma página HTML simples, lançamos algumas chamadas para a nossa biblioteca e para as bibliotecas remotas e damos-lhe um bom trabalho. No entanto, não queremos inundar nenhum dos endpoints dos serviços remotos com chamadas geradas pelos nossos ambientes CI/CD. Isso iria mexer com algumas estatísticas, possivelmente quebrar algumas coisas, e - por último, mas não menos importante - não seríamos muito simpáticos fazendo a produção de alguém uma parte de nossos testes.

Mas será que era possível testar a integração de algo tão complexo? Desde Rubi é o nosso primeiro e principal amor, recorremos à nossa experiência e começámos a pensar em como normalmente fazemos testes de integração com serviços remotos em projectos Ruby. Podemos usar algo como o vcr gem para registar o que está a acontecer uma vez e depois continuar a reproduzi-lo nos nossos testes sempre que necessário.

Introduzir proxy

Internamente, o vcr consegue isso fazendo proxy dos pedidos. Esse foi o nosso momento "a-ha! Precisávamos fazer proxy de todas as requisições que não deveriam atingir nada na internet "real" para algumas respostas esboçadas. Então, esses dados recebidos serão entregues à biblioteca externa e nosso código será executado como de costume.

Quando fazemos protótipos de algo que parece complicado, recorremos muitas vezes ao Ruby como um método que poupa tempo. Decidimos fazer um protótipo para o nosso JavaScript em Ruby para ver se a ideia do proxy funcionará bem antes de se comprometer a construir algo mais complicado em (possivelmente) JavaScript. Acabou sendo surpreendentemente simples. Na verdade, é tão simples que vamos construir um neste artigo juntos. 🙂

Luzes, câmara... espera, esquecemo-nos dos adereços!

Claro que não vamos lidar com a "coisa real" - explicar até mesmo um pouco do que estamos construindo está muito além do escopo de um post de blog. Podemos construir algo rápido e fácil para substituir as bibliotecas em questão e então focar mais na parte de Ruby.

Primeiro, precisamos de algo para substituir a biblioteca externa com que estamos a lidar. Precisamos que apresente alguns comportamentos: deve contactar um serviço externo, emitir um evento aqui e ali e, acima de tudo, não deve ser construído com uma integração fácil em mente 🙂

Eis o que vamos utilizar:

/* 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: {}
}

Repare que chama uma API aberta para obter alguns dados - neste caso, algumas taxas de câmbio de criptomoedas, uma vez que é o que está na moda atualmente. Esta API não expõe uma caixa de areia e tem uma taxa limitada, o que a torna um excelente exemplo de algo que não deve ser realmente atingido nos testes.

Você pode notar que este é de fato um módulo compatível com o NPM, embora eu tenha sugerido que o script com o qual normalmente lidamos não está disponível no NPM para facilitar o empacotamento. Para esta demonstração, é suficiente que ele exiba certo comportamento, e eu prefiro ter facilidade de explicação aqui ao custo de simplificação excessiva.

Convidar os actores

Agora também precisamos de algo para substituir a nossa biblioteca. Novamente, vamos manter os requisitos simples: ele precisa chamar nossa biblioteca "externa" e fazer algo com a saída. Para manter a parte "testável" simples, também faremos com que ela faça logging duplo: tanto para o console, que é um pouco mais difícil de ler nas especificações, quanto para um array disponível globalmente.

window.remote = require('remote-calling-example')

window.failedMiserably = true
window.logs = []

function log (message) {
window.logs.push(mensagem)
consola.log(mensagem)
}

window.addEventListener('example:fetched', function () {
se (window.remote.error) {
log('[EXEMPLO] Fetch remoto falhou')
window.failedMiserably = true
} else {
log('[EXEMPLO] Obtenção remota bem sucedida')
log([EXEMPLO] BTC para ETH: ${window.remote.data.BTC_ETH.last})
}
})

window.remote.fetch()

Eu também estou mantendo o comportamento incrivelmente simples de propósito. Assim, há apenas dois caminhos de código interessantes para especificar, para que não sejamos varridos por uma avalanche de especificações à medida que avançamos na construção.

Tudo se encaixa perfeitamente

Vamos criar uma página HTML simples:

<code> <!DOCTYPE html>
 <html>
 <head>
   <title>Página de exemplo</title>
   <script type="text/javascript" src="./index.js"></script>
 </head>
 <body></body>
 </html>

Para efeitos desta demonstração, vamos juntar o nosso HTML e o JavaScript com Parcela, um muito simples aplicação web bundler. Eu gosto muito do Parcel para momentos como esse, quando estou montando um exemplo rápido ou pensando em uma ideia de classe. Quando você está fazendo algo tão simples que configurar o Webpack levaria mais tempo do que escrever o código que você quer, ele é o melhor.

Também é discreto o suficiente para que, quando eu quiser mudar para algo que é um pouco mais testado, eu não tenha que fazer quase nenhum retrocesso do Parcel, o que não é algo que se possa dizer sobre o Webpack. Nota de cautela, no entanto - Parcel está em desenvolvimento pesado e problemas podem e irão se apresentar; eu tive um problema onde a saída transpilada JavaScript era inválida em um Node.js. Resumindo: não o torne ainda parte do seu fluxo de produção, mas experimente-o mesmo assim.

Aproveitar o poder da integração

Agora podemos construir o nosso conjunto de testes.

Para a própria estrutura de especificações, utilizámos rspec. Em ambientes de desenvolvimento, testamos utilizando o Chrome real, sem cabeça - a tarefa de executar e controlar isso recaiu sobre água (e o seu fiel companheiro watir-rspec). Para o nosso proxy, convidámos Puffing Billy e estante para a festa. Por fim, queremos executar novamente a nossa compilação JavaScript sempre que executarmos as especificações, e isso é conseguido com cocaína.

Isso é um monte de partes móveis e, portanto, nosso ajudante de especificação é um pouco... envolvido, mesmo neste exemplo simples. Vamos dar uma olhada nele e analisá-lo.

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::Matchers

config.include ProxySupport

config.order = :random
BrowserSupport.configure(config)
fim

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

Antes de todo o conjunto, estamos a executar o nosso comando de construção personalizado através de cocaína. Essa constante TEST_LOGGER pode ser um pouco exagerada, mas não estamos muito preocupados com o número de objetos aqui. É claro que estamos rodando as especificações em ordem aleatória, e precisamos incluir todas as coisas boas do watir-rspec. Nós também precisamos configurar o Billy para que ele não faça cache, mas faça um extenso logging para spec/log/billy.log. Se não souber se um pedido está realmente a ser esboçado ou se está a atingir um servidor ativo (whoops!), este registo é ouro puro.

Tenho a certeza que os seus olhos atentos já viram o ProxySupport e o BrowserSupport. Poderá pensar que os nossos produtos personalizados se encontram aí... e teria toda a razão! Vamos ver primeiro o que o BrowserSupport faz.

Um navegador, controlado

Primeiro, vamos apresentar TempBrowser:

classe TempBrowser
def get
@browser ||= Watir::Browser.new(web_driver)
fim

def kill
@browser.close if @browser
@navegador = nulo
fim

privado

def web_driver
Selenium::WebDriver.for(:chrome, options: options)
fim

def opções
Selenium::WebDriver::Chrome::Options.new.tap do |options|
options.addargument '--auto-open-devtools-for-tabs'
options.addargument "--proxy-server=#{Billy.proxy.host}:#{Billy.proxy.port}"
end
fim
fim

Trabalhando de trás para frente na árvore de chamadas, podemos ver que estamos configurando um conjunto de opções do navegador Selenium para o Chrome. Uma das opções que estamos passando para ele é instrumental em nossa configuração: ela instrui a instância do Chrome a fazer proxy de tudo através de nossa instância do Puffing Billy. A outra opção é apenas agradável de se ter - toda instância que rodamos que não seja sem cabeça fará com que as ferramentas de inspeção sejam abertas automaticamente. Isto poupa-nos incontáveis quantidades de Cmd+Alt+I por dia 😉

Depois de configurarmos o browser com estas opções, passamo-lo para o Watir e é tudo. O matar é um pouco de açúcar que nos permite parar e reiniciar repetidamente o driver, se necessário, sem jogar fora a instância TempBrowser.

Agora podemos dar aos nossos exemplos de rspec alguns superpoderes. Primeiro de tudo, nós temos um elegante navegador que é o método auxiliar em torno do qual as nossas especificações vão girar. Também podemos usar um método útil para reiniciar o navegador para um exemplo em particular se estivermos fazendo algo super sensível. É claro que também queremos matar o navegador depois que o conjunto de testes for concluído, porque em nenhuma circunstância queremos instâncias persistentes do Chrome - para o bem da nossa RAM.

módulo BrowserSupport
def self.browser
@browser ||= TempBrowser.new
fim

def self.configure(config)
config.around(:each) do |example|
BrowserSupport.browser.kill if exemplo.metadata[:clean]
@browser = BrowserSupport.browser.get
@browser.cookies.clear
@browser.driver.manage.timeouts.implicit_wait = 30
exemplo.run
fim

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

fim
fim

Ligar o proxy

Temos um navegador e ajudantes de especificação configurados, e estamos prontos para começar a fazer proxy de pedidos para o nosso proxy. Mas espere, nós ainda não o configuramos! Podíamos fazer chamadas repetidas ao Billy para cada um dos exemplos, mas é melhor arranjarmos alguns métodos auxiliares e pouparmos alguns milhares de toques no teclado. É isso que ProxySupport faz.

A que utilizamos na nossa configuração de teste é ligeiramente mais complexa, mas eis uma ideia geral:

frozenstringliteral: true

requerer 'json'

módulo 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, ficheiro)
Billy.proxy.stub(url).andreturn({
corpo: open(file).read,
code: 200,
headers: HEADERS.dup
})
fim

def stubstatus(url, status)
Billy.proxy.stub(url).andreturn({
corpo: '',
código: status,
headers: HEADERS.dup
})
fim

def stubpage(url, file)
Billy.proxy.stub(url).andreturn(
corpo: open(file).read,
content_type: 'text/html',
code: 200
)
fim

def stubjs(url, ficheiro)
Billy.proxy.stub(url).andreturn(
corpo: open(ficheiro).read,
content_type: 'application/javascript',
código: 200
)
fim
fim

Podemos espetar:

  • Pedidos de páginas HTML - para a nossa página principal "playground",
  • Pedidos JS - para servir a nossa biblioteca agregada,
  • Pedidos JSON - para encabeçar o pedido à API remota,
  • e apenas um pedido "qualquer" em que apenas nos interessa devolver uma resposta HTTP específica, não-200.

Isto serve perfeitamente para o nosso exemplo simples. Por falar em exemplos - devíamos criar alguns!

Testar o lado bom

Primeiro, precisamos de ligar um par de "rotas" para o nosso 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' }

before do
stubpage pageurl, pagepath
stubjs jsurl, jspath
fim

Vale a pena notar que, da perspetiva do rspec, os caminhos relativos aqui referem-se ao diretório principal do projeto, por isso estamos a carregar o nosso HTML e JS diretamente do diretório dist como construído pela Parcel. Já se pode ver como esses stub_* os ajudantes são muito úteis.

Também vale a pena notar que estamos a colocar o nosso site "falso" num .local TLD. Dessa forma, qualquer pedido em fuga não deve escapar ao nosso ambiente local se algo correr mal. Como prática geral, eu recomendaria pelo menos não usar nomes de domínio "reais" em stubs, a menos que seja absolutamente necessário.

Outra observação que devemos fazer aqui é sobre não nos repetirmos. À medida que o encaminhamento de proxy se torna mais complexo, com muito mais caminhos e URLs, haverá algum valor real em extrair esta configuração para um contexto partilhado e simplesmente incluí-la conforme necessário.

Agora podemos especificar como deve ser o nosso "bom" caminho:

contexto "com resposta correta" do
antes do do
stubjson %r{http://poloniex.com/public(.*)}, './spec/fixtures/remote.json'
ir para pageurl
Watir::Wait.until { browser.execute_script('return window.logs.length === 2') }
end

it 'regista dados adequados' do
expect(browser.execute_script('return window.logs')).to(
eq([''[EXEMPLO] Busca remota bem-sucedida', '[EXEMPLO] BTC para ETH: 0.03619999'])
)
fim
fim

Isso é bem simples, não é? Mais um pouco de configuração aqui - nós fazemos o stub da resposta JSON da API remota com um fixture, vamos para o nosso URL principal e então... esperamos.

A espera mais longa

As esperas são uma forma de contornar uma limitação que encontramos com o Watir - não podemos esperar de forma confiável por, por exemplo, eventos JavaScript, então precisamos trapacear um pouco e "esperar" até que os scripts tenham movido algum objeto que possamos acessar para um estado que seja de nosso interesse. A desvantagem é que, se esse estado nunca chegar (devido a um bug, por exemplo), precisamos de esperar que o waiter espere pelo tempo limite. Isso aumenta um pouco o tempo da especificação. No entanto, a especificação ainda falha de forma confiável.

Após a página ter "estabilizado" no estado que nos interessa, podemos executar mais alguns JavaScript no contexto da página. Aqui chamamos os logs escritos no array público e verificamos se eles são o que esperávamos.

Como uma nota lateral - aqui é onde o stubbing do pedido remoto realmente brilha. A resposta que é registada na consola depende da taxa de câmbio devolvida pela API remota, pelo que não poderíamos testar de forma fiável o conteúdo do registo se este estivesse sempre a mudar. Há maneiras de contornar isso, é claro, mas elas não são muito elegantes.

Testar a ramificação incorrecta

Mais uma coisa a testar: o ramo "falha".

contexto "com resposta falhada" do
antes do do
stubstatus %r{http://poloniex.com/public(.*)}, 404
ir para pageurl
Watir::Wait.until { browser.execute_script('return window.logs.length === 1') }
end

it 'falha nos registos' do
expect(browser.execute_script('return window.logs')).to(
eq(['[EXEMPLO] Falha na pesquisa remota'])
)
end
fim

É muito semelhante à anterior, com a diferença de que a resposta é um código de estado HTTP 404 e esperamos um registo diferente.

Vamos agora analisar as nossas especificações.

% bundle exec rspec
Randomizado com a semente 63792
I, [2017-12-21T14:26:08.680953 #7303] INFO -- : Comando :: npm run build

Chamada remota
com resposta correta
regista os dados corretos
com resposta falhada
regista a falha

Terminou em 23,56 segundos (os ficheiros demoraram 0,86547 segundos a carregar)
2 exemplos, 0 falhas

Uau!

Conclusão

Nós discutimos brevemente como o JavaScript pode ser testado em integração com Ruby. Enquanto originalmente era considerado mais um paliativo, estamos bem felizes com nosso pequeno protótipo agora. Ainda estamos considerando uma solução pura de JavaScript, claro, mas enquanto isso temos uma maneira simples e prática de reproduzir e testar algumas situações bem complexas que encontramos na natureza.

Se estiver a pensar em construir algo semelhante, deve ter em atenção que não está isento de limitações. Por exemplo, se o que está a testar for muito pesado em termos de AJAX, o Puffing Billy vai demorar muito tempo a responder. Além disso, se você tiver que fazer o stub de algumas fontes SSL, será necessário um pouco mais de trabalho - dê uma olhada na documentação do watir se isso for um requisito que você tem. Vamos continuar a explorar e a procurar as melhores formas de lidar com o nosso caso de utilização único - e vamos certificar-nos de que vos dizemos o que descobrimos.

Artigos relacionados

Ilustração de uma aplicação de cuidados de saúde para smartphone com um ícone de coração e um gráfico de saúde em ascensão, com o logótipo The Codest, representando soluções digitais de saúde e HealthTech.
Desenvolvimento de software

Softwares para o setor de saúde: Tipos, casos de uso

As ferramentas em que as organizações de cuidados de saúde confiam atualmente não se assemelham em nada às fichas de papel de há décadas atrás. O software de cuidados de saúde apoia agora os sistemas de saúde, os cuidados aos doentes e a prestação de cuidados de saúde modernos em...

OCODEST
Ilustração abstrata de um gráfico de barras em declínio com uma seta ascendente e uma moeda de ouro que simboliza a eficiência ou a poupança de custos. O logótipo The Codest aparece no canto superior esquerdo com o slogan "In Code We Trust" sobre um fundo cinzento claro
Desenvolvimento de software

Como dimensionar a sua equipa de desenvolvimento sem perder a qualidade do produto

Aumentar a sua equipa de desenvolvimento? Saiba como crescer sem sacrificar a qualidade do produto. Este guia cobre sinais de que é hora de escalar, estrutura da equipe, contratação, liderança e ferramentas - além de como o The Codest pode...

OCODEST
Desenvolvimento de software

Construir aplicações Web preparadas para o futuro: ideias da equipa de especialistas do The Codest

Descubra como o The Codest se destaca na criação de aplicações web escaláveis e interactivas com tecnologias de ponta, proporcionando experiências de utilizador perfeitas em todas as plataformas. Saiba como a nossa experiência impulsiona a transformação digital e o negócio...

OCODEST
Desenvolvimento de software

As 10 principais empresas de desenvolvimento de software sediadas na Letónia

Saiba mais sobre as principais empresas de desenvolvimento de software da Letónia e as suas soluções inovadoras no nosso último artigo. Descubra como estes líderes tecnológicos podem ajudar a elevar o seu negócio.

thecodest
Soluções para empresas e escalas

Fundamentos do desenvolvimento de software Java: Um Guia para Terceirizar com Sucesso

Explore este guia essencial sobre o desenvolvimento de software Java outsourcing com sucesso para aumentar a eficiência, aceder a conhecimentos especializados e impulsionar o sucesso do projeto com The Codest.

thecodest

Subscreva a nossa base de conhecimentos e mantenha-se atualizado sobre os conhecimentos do sector das TI.

    Sobre nós

    The Codest - Empresa internacional de desenvolvimento de software com centros tecnológicos na Polónia.

    Reino Unido - Sede

    • Office 303B, 182-184 High Street North E6 2JA
      Londres, Inglaterra

    Polónia - Pólos tecnológicos locais

    • Parque de escritórios Fabryczna, Aleja
      Pokoju 18, 31-564 Cracóvia
    • Embaixada do Cérebro, Konstruktorska
      11, 02-673 Varsóvia, Polónia

      The Codest

    • Início
    • Sobre nós
    • Serviços
    • Case Studies
    • Saber como
    • Carreiras
    • Dicionário

      Serviços

    • Aconselhamento
    • Desenvolvimento de software
    • Desenvolvimento backend
    • Desenvolvimento de front-end
    • Staff Augmentation
    • Programadores de back-end
    • Engenheiros de nuvem
    • Engenheiros de dados
    • Outros
    • Engenheiros de GQ

      Recursos

    • Factos e mitos sobre a cooperação com um parceiro externo de desenvolvimento de software
    • Dos EUA para a Europa: Porque é que as empresas americanas decidem mudar-se para a Europa?
    • Comparação dos centros de desenvolvimento da Tech Offshore: Tech Offshore Europa (Polónia), ASEAN (Filipinas), Eurásia (Turquia)
    • Quais são os principais desafios dos CTOs e dos CIOs?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Direitos de autor © 2026 por The Codest. Todos os direitos reservados.

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