Tüübid ja testitav kood on kaks kõige tõhusamat viisi vigade vältimiseks, eriti kui kood aja jooksul muutub. Neid kahte tehnikat saame JavaScripti arendamisel rakendada, kasutades selleks vastavalt TypeScripti ja sõltuvuse süstimise (DI) kujundusmustrit.
Selles TypeScripti õpetuses ei käsitleta otseselt TypeScripti põhitõdesid, välja arvatud kompileerimine. Selle asemel demonstreerime lihtsalt TypeScripti parimaid tavasid, kui käime läbi, kuidas Discordi bot nullist teha, teste ja DI siduda ning prooviteenust luua. Kasutame:
Kõigepealt loome uue kataloogi nimega typescript-bot
Seejärel sisestage see ja looge uus Node.js projekt, käivitades:
npm init
Märkus. Võite kasutada ka yarn
selleks, kuid jäägem npm
juurde lühiduse pärast.
See avab interaktiivse viisardi, mis seadistab package.json
faili. Võite lihtsalt vajutada Sisenema kõigi küsimuste jaoks (või esitage soovi korral teavet). Seejärel installime oma sõltuvused ja dev-sõltuvused (need, mida on vaja ainult testide jaoks).
npm i --save typescript discord.js inversify dotenv @types/node reflect-metadata npm i --save-dev chai mocha ts-mockito ts-node @types/chai @types/mocha
Seejärel asendage loodud 'scripts'
jaotis package.json
koos:
'scripts': { 'start': 'node src/index.js', 'watch': 'tsc -p tsconfig.json -w', 'test': 'mocha -r ts-node/register 'tests/**/*.spec.ts'' },
Topeltpakkumised tests/**/*.spec.ts
ümber on vaja failide rekursiivseks leidmiseks. (Märkus: Windowsi kasutavate arendajate puhul võib süntaks erineda.)
start
roboti käivitamiseks kasutatakse skripti watch
skript TypeScripti koodi kompileerimiseks ja test
testide läbiviimiseks.
Nüüd on meie package.json
fail peaks välja nägema selline:
{ 'name': 'typescript-bot', 'version': '1.0.0', 'description': '', 'main': 'index.js', 'dependencies': { '@types/node': '^11.9.4', 'discord.js': '^11.4.2', 'dotenv': '^6.2.0', 'inversify': '^5.0.1', 'reflect-metadata': '^0.1.13', 'typescript': '^3.3.3' }, 'devDependencies': { '@types/chai': '^4.1.7', '@types/mocha': '^5.2.6', 'chai': '^4.2.0', 'mocha': '^5.2.0', 'ts-mockito': '^2.3.1', 'ts-node': '^8.0.3' }, 'scripts': { 'start': 'node src/index.js', 'watch': 'tsc -p tsconfig.json -w', 'test': 'mocha -r ts-node/register 'tests/**/*.spec.ts'' }, 'author': '', 'license': 'ISC' }
Discordi API-ga suhtlemiseks vajame märki. Sellise märgi genereerimiseks peame rakenduse registreerima Discordi arendaja juhtpaneelil. Selleks peate looma ebakõla konto ja minema https://discordapp.com/developers/applications/ . Seejärel klõpsake nuppu Uus rakendus nupp:
Valige nimi ja klõpsake nuppu Loo . Seejärel klõpsake nuppu Bot → Lisage Bot ja olete valmis. Lisame roboti serverisse. Kuid ärge seda lehte veel sulgege, peame varsti kopeerima märgise.
Meie roboti testimiseks vajame Discordi serverit. Võite kasutada olemasolevat serverit või luua uue. Selleks kopeerige roboti CLIENT_ID
- leitud vahekaardilt Üldteave - ja kasutage seda selle osana eriluba URL:
https://discordapp.com/oauth2/authorize?client_id=&scope=bot
Kui klõpsate seda URL-i brauseris, kuvatakse vorm, kus saate valida serveri, kuhu robot tuleks lisada.
Pärast roboti lisamist oma serverisse peaksite nägema ülaltoodud sõnumit.
.env
Loomine FailVajame mingit viisi, kuidas märk oma rakendusse salvestada. Selleks kasutame dotenv
pakend. Esmalt hankige märgis Discord Application Dashboardilt ( Bot → Klõpsake märgi paljastamiseks ):
Nüüd looge .env
fail, seejärel kopeerige ja kleepige märk siia:
TOKEN=paste.the.token.here
Kui kasutate Giti, peaks see fail olema paigutatud kausta .gitignore
, et luba ei kahjustataks. Looge ka .env.example
faili, nii et on teada, et TOKEN
vajab määratlust:
TOKEN=
TypeScripti kompileerimiseks võite kasutada npm run watch
käsk. Teise võimalusena, kui kasutate PHPStormi (või mõnda muud IDE-d), kasutage lihtsalt oma TypeScripti pistikprogrammi failivaaturit ja laske oma IDE-l kompileerida. Testime oma seadistust, luues src/index.ts
fail sisuga:
console.log('Hello')
Looge ka tsconfig.json
fail nagu allpool. InversifyJS nõuab experimentalDecorators
, emitDecoratorMetadata
, es6
ja reflect-metadata
{ 'compilerOptions': { 'module': 'commonjs', 'moduleResolution': 'node', 'target': 'es2016', 'lib': [ 'es6', 'dom' ], 'sourceMap': true, 'types': [ // add node as an option 'node', 'reflect-metadata' ], 'typeRoots': [ // add path to @types 'node_modules/@types' ], 'experimentalDecorators': true, 'emitDecoratorMetadata': true, 'resolveJsonModule': true }, 'exclude': [ 'node_modules' ] }
Kui failivaatleja töötab korralikult, peaks see genereerima src/index.js
fail ja töötab npm start
tulemuseks peaks olema:
> node src/index.js Hello
Nüüd hakkame lõpuks kasutama TypeScripti kõige kasulikumat funktsiooni: tüübid. Jätkake ja looge järgmine src/bot.ts
fail:
import {Client, Message} from 'discord.js'; export class Bot { public listen(): Promise { let client = new Client(); client.on('message', (message: Message) => {}); return client.login('token should be here'); } }
Nüüd näeme siin vajalikku: märki! Kas me kopeerime selle lihtsalt siia või laadime väärtuse otse keskkonnast?
Kumbagi. Selle asemel kirjutagem hooldatavam, laiendatavam ja testitavam kood, sisestades märgi meie valitud sõltuvuse süstimise raamistiku InversifyJS abil.
Samuti näeme, et Client
sõltuvus on kodeeritud. Süstime ka seda.
TO sõltuvuse süstemahuti on objekt, mis teab, kuidas teisi objekte eksponeerida. Tavaliselt määratleme sõltuvused iga klassi jaoks ja DI konteiner hoolitseb nende lahendamise eest.
InversifyJS soovitab panna sõltuvused tähele inversify.config.ts
faili, nii et jätkame ja lisame sinna oma DI-konteineri:
import 'reflect-metadata'; import {Container} from 'inversify'; import {TYPES} from './types'; import {Bot} from './bot'; import {Client} from 'discord.js'; let container = new Container(); container.bind(TYPES.Bot).to(Bot).inSingletonScope(); container.bind(TYPES.Client).toConstantValue(new Client()); container.bind(TYPES.Token).toConstantValue(process.env.TOKEN); export default container;
Samuti soovitavad dokumendid InversifyJS luues types.ts
fail ja loetlege kõik tüübid, mida kavatseme kasutada, koos seotud Symbol
See on üsna ebamugav, kuid see tagab, et meie rakenduse kasvades ei esine kokkupõrkeid. Iga Symbol
on kordumatu identifikaator, isegi kui selle kirjelduse parameeter on sama (parameeter on mõeldud ainult silumiseks).
export const TYPES = { Bot: Symbol('Bot'), Client: Symbol('Client'), Token: Symbol('Token'), };
Kasutamata Symbol
s, näeb see välja, kui juhtub nimede kokkupõrge:
Error: Ambiguous match found for serviceIdentifier: MessageResponder Registered bindings: MessageResponder MessageResponder
Sel hetkel on see isegi rohkem ebamugav välja selgitada, millised MessageResponder
tuleks kasutada, eriti kui meie DI konteiner kasvab suureks. Symbol
S kasutamine hoolitseb selle eest ja me ei ole välja mõelnud kummalisi stringiliitaleid, kui meil on kaks samanimelist klassi.
Muutkem nüüd oma Bot
klassi konteineri kasutamiseks. Peame lisama @injectable
ja @inject()
märkused selleks. Siin on uus Bot
klass:
import {Client, Message} from 'discord.js'; import {inject, injectable} from 'inversify'; import {TYPES} from './types'; import {MessageResponder} from './services/message-responder'; @injectable() export class Bot { private client: Client; private readonly token: string; constructor( @inject(TYPES.Client) client: Client, @inject(TYPES.Token) token: string ) { this.client = client; this.token = token; } public listen(): Promise { this.client.on('message', (message: Message) => { console.log('Message received! Contents: ', message.content); }); return this.client.login(this.token); } }
Lõpuks kiirendame oma roboti index.ts
-s fail:
require('dotenv').config(); // Recommended way of loading dotenv import container from './inversify.config'; import {TYPES} from './types'; import {Bot} from './bot'; let bot = container.get(TYPES.Bot); bot.listen().then(() => { console.log('Logged in!') }).catch((error) => { console.log('Oh no! ', error) });
Nüüd käivitage robot ja laske see oma serverisse lisada. Siis, kui sisestate sõnumi serverikanalisse, peaks see ilmuma käsurea logides järgmiselt:
> node src/index.js Logged in! Message received! Contents: Test
Lõpuks on meil loodud alused: TypeScripti tüübid ja sõltuvuse süstimiskonteiner meie roboti sees.
Läheme otse selle artikli tuumani: testitava koodibaasi loomine. Lühidalt öeldes peaks meie koodeks rakendama parimaid tavasid (nt TAHKE ), mitte varjata sõltuvusi, mitte kasutada staatilisi meetodeid.
Samuti see ei tohiks joostes põhjustada kõrvaltoimeid ja olla kergesti mõnitatav .
Lihtsuse huvides teeb meie bot ainult ühte asja: see otsib sissetulevaid sõnumeid ja kui see sisaldab sõna 'ping', siis kasutame ühte saadaolevatest Discordi botikäskudest, et bot reageeriks sõnaga 'pong! ” sellele kasutajale.
Selleks, et näidata, kuidas kohandatud objekte Bot
-i sisestada objekt ja testige neid ühikutega, loome kaks klassi: PingFinder
ja MessageResponder
. Süstime MessageResponder
sisse Bot
klassi ja PingFinder
sisse MessageResponder
.
Siin on src/services/ping-finder.ts
fail:
import {injectable} from 'inversify'; @injectable() export class PingFinder { private regexp = 'ping'; public isPing(stringToSearch: string): boolean { return stringToSearch.search(this.regexp) >= 0; } }
Seejärel süstime selle klassi src/services/message-responder.ts
fail:
import {Message} from 'discord.js'; import {PingFinder} from './ping-finder'; import {inject, injectable} from 'inversify'; import {TYPES} from '../types'; @injectable() export class MessageResponder { private pingFinder: PingFinder; constructor( @inject(TYPES.PingFinder) pingFinder: PingFinder ) { this.pingFinder = pingFinder; } handle(message: Message): Promise { if (this.pingFinder.isPing(message.content)) { return message.reply('pong!'); } return Promise.reject(); } }
Lõpuks on siin muudetud Bot
klass, mis kasutab MessageResponder
klass:
import {Client, Message} from 'discord.js'; import {inject, injectable} from 'inversify'; import {TYPES} from './types'; import {MessageResponder} from './services/message-responder'; @injectable() export class Bot { private client: Client; private readonly token: string; private messageResponder: MessageResponder; constructor( @inject(TYPES.Client) client: Client, @inject(TYPES.Token) token: string, @inject(TYPES.MessageResponder) messageResponder: MessageResponder) { this.client = client; this.token = token; this.messageResponder = messageResponder; } public listen(): Promise { this.client.on('message', (message: Message) => { if (message.author.bot) { console.log('Ignoring bot message!') return; } console.log('Message received! Contents: ', message.content); this.messageResponder.handle(message).then(() => { console.log('Response sent!'); }).catch(() => { console.log('Response not sent.') }) }); return this.client.login(this.token); } }
Selles olekus ei õnnestu rakendust käivitada, kuna MessageResponder
jaoks pole definitsioone ja PingFinder
klassides. Lisame inversify.config.ts
-i juurde järgmise fail:
container.bind(TYPES.MessageResponder).to(MessageResponder).inSingletonScope(); container.bind(TYPES.PingFinder).to(PingFinder).inSingletonScope();
Lisame ka tüübile sümbolid types.ts
:
MessageResponder: Symbol('MessageResponder'), PingFinder: Symbol('PingFinder'),
Nüüd, pärast meie rakenduse taaskäivitamist, peaks bot vastama igale sõnumile, mis sisaldab pingi:
Ja kuidas see logides välja näeb:
> node src/index.js Logged in! Message received! Contents: some message Response not sent. Message received! Contents: message with ping Ignoring bot message! Response sent!
Nüüd, kui sõltuvused on meile õigesti süstitud, on ühikutestide kirjutamine lihtne. Selleks kasutame Chai ja ts-mockitot; siiski on palju teisi testijooksjaid ja pilkavaid raamatukogusid, mida saaksite kasutada.
Ts-mockito pilkav süntaks on üsna sõnakas, kuid samas ka hõlpsasti mõistetav. Siit saate teada, kuidas seadistada MessageResponder
teenust ja süstige PingFinder
mõnitama seda:
let mockedPingFinderClass = mock(PingFinder); let mockedPingFinderInstance = instance(mockedPingFinderClass); let service = new MessageResponder(mockedPingFinderInstance);
Nüüd, kui meil on mõnitused paika pandud, saame määratleda, millise tulemuse isPing()
kõned peaksid olema ja kontrollige reply()
kõned. Asi on selles, et ühikutestides määratleme isPing()
tulemuse helista: true
või false
. Pole tähtis, mis on sõnumi sisu, nii et testides kasutame lihtsalt 'Non-empty string'
when(mockedPingFinderClass.isPing('Non-empty string')).thenReturn(true); await service.handle(mockedMessageInstance) verify(mockedMessageClass.reply('pong!')).once();
Kogu testikomplekt võiks välja näha nii:
import 'reflect-metadata'; import 'mocha'; import {expect} from 'chai'; import {PingFinder} from '../../../src/services/ping-finder'; import {MessageResponder} from '../../../src/services/message-responder'; import {instance, mock, verify, when} from 'ts-mockito'; import {Message} from 'discord.js'; describe('MessageResponder', () => { let mockedPingFinderClass: PingFinder; let mockedPingFinderInstance: PingFinder; let mockedMessageClass: Message; let mockedMessageInstance: Message; let service: MessageResponder; beforeEach(() => { mockedPingFinderClass = mock(PingFinder); mockedPingFinderInstance = instance(mockedPingFinderClass); mockedMessageClass = mock(Message); mockedMessageInstance = instance(mockedMessageClass); setMessageContents(); service = new MessageResponder(mockedPingFinderInstance); }) it('should reply', async () => { whenIsPingThenReturn(true); await service.handle(mockedMessageInstance); verify(mockedMessageClass.reply('pong!')).once(); }) it('should not reply', async () => { whenIsPingThenReturn(false); await service.handle(mockedMessageInstance).then(() => { // Successful promise is unexpected, so we fail the test expect.fail('Unexpected promise'); }).catch(() => { // Rejected promise is expected, so nothing happens here }); verify(mockedMessageClass.reply('pong!')).never(); }) function setMessageContents() { mockedMessageInstance.content = 'Non-empty string'; } function whenIsPingThenReturn(result: boolean) { when(mockedPingFinderClass.isPing('Non-empty string')).thenReturn(result); } });
Katsed PingFinder
on üsna tühised, kuna pole sõltuvusi, mida mõnitada. Siin on näide testjuhtumist:
describe('PingFinder', () => { let service: PingFinder; beforeEach(() => { service = new PingFinder(); }) it('should find 'ping' in the string', () => { expect(service.isPing('ping')).to.be.true }) });
Lisaks ühikutestidele saame kirjutada ka integreerumisteste. Peamine erinevus on see, et nende testide sõltuvusi ei mõnitata. Siiski on mõned sõltuvused, mida ei tohiks testida, näiteks välised API-ühendused. Sel juhul saame luua mõnitusi ja rebind
konteinerisse, nii et selle asemel süstitakse pilk. Siin on näide, kuidas seda teha:
import container from '../../inversify.config'; import {TYPES} from '../../src/types'; // ... describe('Bot', () => { let discordMock: Client; let discordInstance: Client; let bot: Bot; beforeEach(() => { discordMock = mock(Client); discordInstance = instance(discordMock); container.rebind(TYPES.Client) .toConstantValue(discordInstance); bot = container.get(TYPES.Bot); }); // Test cases here });
See viib meid Discordi bot õpetuse lõppu. Palju õnne, sa ehitasid selle puhtalt, TypeScript ja DI olid algusest peale paigas! See TypeScripti sõltuvuse süstimise näide on muster, mille saate oma repertuaari lisada mis tahes projektiga kasutamiseks.
Objekti orienteeritud maailma toomine TypeScript JavaScripti on suurepärane täiustus, hoolimata sellest, kas töötame esi- või tagakoodi koodiga. Ainuüksi tüüpide kasutamine võimaldab meil vältida paljusid vigu. Sõltuvuse süstimine TypeScripti abil sunnib JavaScripti põhisele arendusele veelgi rohkem objektorienteeritud parimaid tavasid.
Muidugi ei saa keele piirangute tõttu see kunagi olema nii lihtne ja loomulik kui staatiliselt kirjutatud keeltes. Kuid üks on kindel: TypeScript, ühikutestid ja sõltuvuse süstimine võimaldavad meil kirjutada loetavamat, vabamalt ühendatud ja hooldatavat koodi - olenemata sellest, millist rakendust me arendame.
Seotud: Looge rakendus WhatsApp Chatbot, mitte rakendusKui soovite kirjutada puhtama koodi selles mõttes, et see on ühtselt testitav, hooldatavam ja vabalt ühendatud, peaksite kasutama sõltuvuse süstimise kujundusmustrit. Kasutades sõltuvussüstimist, on teil puhtama koodi retsept ilma ratast leiutamata.
Rakendades sõltuvuse süstimist, oleme sunnitud kirjutama ühikutestitava koodi, mida on lihtne hooldada. Sõltuvused süstitakse konstruktorite kaudu ja neid saab ühikutestide abil hõlpsasti mõnitada. Samuti julgustab see muster kirjutama lõdvalt seotud koodi.
TypeScripti põhieesmärk on lubada puhtamat ja loetavamat JavaScripti koodi, lisades tüüpe. See on abi arendajatele, enamasti on see kasutusel IDE-des. Kapoti all teisendatakse TypeScript endiselt tavaliseks JavaScripti.
Discord bot on veebirakendus, mis kasutab suhtlemiseks Discord API-d.
Discordi bot saab vastata sõnumitele, määrata rolle, vastata reaktsioonidega ja palju muud. Igale ebakõla toimingule, mida tavakasutajad ja administraatorid saavad teha, on API-meetodid.
TypeScripti peamine eelis on lubada arendajal tüüpe määratleda ja kasutada. Tüübivihjete abil teab transpiler (või „allikast lähte juurde kompilaator“), milline objekt tuleks antud meetodile edastada. Võimalikud vead või kehtetud kõned tuvastatakse kompileerimise ajal, mis viib live-serveris vähem vigu.