apeescape2.com
  • メイン
  • プロジェクト管理
  • データサイエンスとデータベース
  • デザイナーライフ
  • リモートの台頭
バックエンド

TypeScript、依存性注入、Discordボットの操作

タイプとテスト可能なコード これは、特にコードが時間の経過とともに変化するときに、バグを回避するための最も効果的な2つの方法です。 TypeScriptと依存性注入(DI)デザインパターンをそれぞれ活用することで、これら2つの手法をJavaScript開発に適用できます。

このTypeScriptチュートリアルでは、コンパイルを除いて、TypeScriptの基本については直接説明しません。代わりに、Discordボットを最初から作成し、テストとDIを接続し、サンプルサービスを作成する方法を説明しながら、TypeScriptのベストプラクティスを簡単に示します。使用します:

  • Node.js
  • TypeScript
  • Discord.js、DiscordAPIのラッパー
  • InversifyJS、依存性注入フレームワーク
  • テストライブラリ:Mocha、Chai、およびts-mockito
  • ボーナス:統合テストを作成するためのMongooseとMongoDB

Node.jsプロジェクトの設定

まず、typescript-botという名前の新しいディレクトリを作成しましょう。次に、それを入力し、次を実行して新しいNode.jsプロジェクトを作成します。



npm init

注:yarnを使用することもできますそのためですが、npmに固執しましょう簡潔にするため。

これにより、インタラクティブなウィザードが開き、package.jsonが設定されます。ファイル。押すだけで安全に 入る すべての質問に対応します(または、必要に応じて情報を提供します)。次に、依存関係と開発依存関係(テストにのみ必要なもの)をインストールしましょう。

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

次に、生成された'scripts'を置き換えますpackage.jsonのセクションと:

'scripts': { 'start': 'node src/index.js', 'watch': 'tsc -p tsconfig.json -w', 'test': 'mocha -r ts-node/register 'tests/**/*.spec.ts'' },

tests/**/*.spec.tsを囲む二重引用符ファイルを再帰的に見つけるために必要です。 (注:構文は、Windowsを使用している開発者によって異なる場合があります。)

startスクリプトはボット、watchを起動するために使用されますTypeScriptコードをコンパイルするためのスクリプト、およびtestテストを実行します。

さて、私たちのpackage.jsonファイルは次のようになります。

テレグラムボットとは
{ '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' }

DiscordAppsダッシュボードでの新しいアプリケーションの作成

Discord APIと対話するには、トークンが必要です。このようなトークンを生成するには、Discord DeveloperDashboardにアプリを登録する必要があります。これを行うには、Discordアカウントを作成し、に移動する必要があります https://discordapp.com/developers/applications/ 。次に、をクリックします 新しいアプリ ボタン:

不和

名前を選択してクリック 作成する 。次に、をクリックします ボット → ボットを追加 、これで完了です。ボットをサーバーに追加しましょう。ただし、まだこのページを閉じないでください。すぐにトークンをコピーする必要があります。

Discordボットをサーバーに追加する

ボットをテストするには、Discordサーバーが必要です。既存のサーバーを使用することも、新しいサーバーを作成することもできます。これを行うには、[一般情報]タブにあるボットのCLIENT_IDをコピーして、この一部として使用します 特別承認 URL:

https://discordapp.com/oauth2/authorize?client_id=&scope=bot

ブラウザでこのURLを押すと、ボットを追加するサーバーを選択できるフォームが表示されます。

ボットがサーバーに参加したことに対する標準のDiscordウェルカムメッセージ。

ボットをサーバーに追加すると、上記のようなメッセージが表示されます。

.envの作成ファイル

アプリにトークンを保存する方法が必要です。そのために、dotenvを使用しますパッケージ。まず、Discordアプリケーションダッシュボードからトークンを取得します( ボット → クリックしてトークンを公開 ):

ザ・

次に、.envを作成しますファイルを作成し、トークンをコピーしてここに貼り付けます。

TOKEN=paste.the.token.here

Gitを使用する場合は、トークンが危険にさらされないように、このファイルを.gitignoreに配置する必要があります。また、.env.exampleを作成しますファイル、そのためTOKEN定義する必要があります:

TOKEN=

TypeScriptのコンパイル

TypeScriptをコンパイルするには、npm run watchを使用できます。コマンド。または、PHPStorm(または別のIDE)を使用している場合は、TypeScriptプラグインのファイルウォッチャーを使用して、IDEにコンパイルを処理させます。 src/index.tsを作成してセットアップをテストしましょう内容のファイル:

console.log('Hello')

また、tsconfig.jsonを作成しましょう以下のようなファイル。 InversifyJSには、experimentalDecorators、emitDecoratorMetadata、es6、および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' ] }

ファイルウォッチャーが正しく機能する場合は、src/index.jsを生成する必要があります。ファイル、および実行中npm start結果は次のようになります。

> node src/index.js Hello

ボットクラスの作成

それでは、ついにTypeScriptの最も便利な機能であるタイプの使用を開始しましょう。先に進み、次を作成しますsrc/bot.tsファイル:

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'); } }

これで、ここで必要なもの、つまりトークンを確認できます。ここにコピーして貼り付けるだけですか、それとも環境から直接値をロードしますか?

どちらでもない。代わりに、選択した依存性注入フレームワークであるInversifyJSを使用してトークンを注入することにより、より保守可能、拡張可能、およびテスト可能なコードを記述しましょう。

また、Clientがわかります。依存関係はハードコーディングされています。これも注入します。

依存性注入コンテナの構成

に 依存性注入コンテナ 他のオブジェクトをインスタンス化する方法を知っているオブジェクトです。通常、各クラスの依存関係を定義し、DIコンテナがそれらの解決を処理します。

llc s corp c corp

InversifyJSは、依存関係をinversify.config.tsに配置することをお勧めしますファイルがあるので、先に進んで、そこにDIコンテナを追加しましょう。

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;

また、 InversifyJSドキュメントは推奨します types.tsを作成するファイル、および関連するSymbolとともに使用する各タイプのリスト。これは非常に不便ですが、アプリの成長に伴って名前の衝突が発生しないようにします。各Symbol説明パラメーターが同じであっても、は一意の識別子です(パラメーターはデバッグのみを目的としています)。

export const TYPES = { Bot: Symbol('Bot'), Client: Symbol('Client'), Token: Symbol('Token'), };

Symbol sを使用しない場合、名前の衝突が発生したときの外観は次のとおりです。

Error: Ambiguous match found for serviceIdentifier: MessageResponder Registered bindings: MessageResponder MessageResponder

この時点で、それは もっと どのMessageResponderを分類するのは不便です特にDIコンテナが大きくなる場合は、使用する必要があります。 Symbol sを使用するとそれが処理され、同じ名前の2つのクラスがある場合に奇妙な文字列リテラルが発生することはありません。

DiscordBotアプリでのコンテナーの使用

それでは、Botを変更しましょうコンテナを使用するクラス。 @injectableを追加する必要がありますおよび@inject()それを行うための注釈。これが新しいBotですクラス:

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); } }

最後に、index.tsでボットをインスタンス化しましょうファイル:

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) });

次に、ボットを起動してサーバーに追加します。次に、サーバーチャネルにメッセージを入力すると、次のようにコマンドラインのログに表示されます。

> node src/index.js Logged in! Message received! Contents: Test

最後に、ボット内のTypeScriptタイプと依存性注入コンテナという基盤を設定しました。

ビジネスロジックの実装

この記事の内容の核心である、テスト可能なコードベースの作成に直接進みましょう。つまり、コードはベストプラクティスを実装する必要があります( 固体 )、依存関係を非表示にしない、静的メソッドを使用しない。

また、 実行時に副作用が発生しないようにし、簡単にモックできるようにする必要があります 。

簡単にするために、ボットは1つのことだけを実行します。着信メッセージを検索し、「ping」という単語が含まれている場合は、使用可能なDiscordボットコマンドの1つを使用して、ボットに「pong! 」そのユーザーに。

カスタムオブジェクトをBotに挿入する方法を示すためオブジェクトとユニットテストを行い、2つのクラスを作成します:PingFinderおよびMessageResponder。注入しますMessageResponder Botにクラス、およびPingFinder MessageResponderに。

コンテンツの空間階層を確立するために使用できるデザインツールに名前を付けます

これがsrc/services/ping-finder.tsですファイル:

import {injectable} from 'inversify'; @injectable() export class PingFinder { private regexp = 'ping'; public isPing(stringToSearch: string): boolean { return stringToSearch.search(this.regexp) >= 0; } }

次に、そのクラスをsrc/services/message-responder.tsに挿入します。ファイル:

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(); } }

最後に、変更されたBotです。 MessageResponderを使用するクラスクラス:

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); } }

その状態では、MessageResponderの定義がないため、アプリの実行に失敗します。およびPingFinderクラス。以下をinversify.config.tsに追加しましょうファイル:

container.bind(TYPES.MessageResponder).to(MessageResponder).inSingletonScope(); container.bind(TYPES.PingFinder).to(PingFinder).inSingletonScope();

また、タイプシンボルをtypes.tsに追加します。

MessageResponder: Symbol('MessageResponder'), PingFinder: Symbol('PingFinder'),

これで、アプリを再起動した後、ボットは「ping」を含むすべてのメッセージに応答する必要があります。

単語を含むメッセージに応答するボット

そして、これがログでどのように見えるかです:

> 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!

ユニットテストの作成

依存関係が適切に挿入されたので、単体テストの作成は簡単です。そのためにChaiとts-mockitoを使用します。ただし、使用できるテストランナーやモックライブラリは他にもたくさんあります。

ts-mockitoのモック構文は非常に冗長ですが、理解しやすいものです。 MessageResponderを設定する方法は次のとおりですサービスと注入PingFinderそれにモック:

let mockedPingFinderClass = mock(PingFinder); let mockedPingFinderInstance = instance(mockedPingFinderClass); let service = new MessageResponder(mockedPingFinderInstance);

モックを設定したので、isPing()の結果を定義できます。呼び出しは、確認する必要がありますreply()呼び出します。重要なのは、単体テストでは、isPing()の結果を定義するということです。電話:trueまたはfalse。メッセージの内容は関係ないため、テストでは'Non-empty string'を使用します。

when(mockedPingFinderClass.isPing('Non-empty string')).thenReturn(true); await service.handle(mockedMessageInstance) verify(mockedMessageClass.reply('pong!')).once();

テストスイート全体は次のようになります。

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); } });

PingFinderのテスト嘲笑される依存関係がないため、非常に簡単です。テストケースの例を次に示します。

describe('PingFinder', () => { let service: PingFinder; beforeEach(() => { service = new PingFinder(); }) it('should find 'ping' in the string', () => { expect(service.isPing('ping')).to.be.true }) });

統合テストの作成

単体テストとは別に、統合テストを作成することもできます。主な違いは、これらのテストの依存関係がモックされていないことです。ただし、外部API接続など、テストしてはならない依存関係がいくつかあります。その場合、モックとrebindを作成できます。それらをコンテナに追加して、代わりにモックを注入します。これを行う方法の例を次に示します。

Pythonにログインする方法
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 });

これで、Discordボットのチュートリアルは終了です。おめでとうございます。TypeScriptとDIを最初から配置して、きれいに構築しました。このTypeScript依存性注入の例は、任意のプロジェクトで使用するためにレパートリーに追加できるパターンです。

TypeScriptと依存性注入:Discordボット開発だけではありません

オブジェクト指向の世界をもたらす TypeScript JavaScriptへの変換は、フロントエンドコードとバックエンドコードのどちらで作業している場合でも、優れた拡張機能です。型だけを使用するだけで、多くのバグを回避できます。 TypeScriptに依存性注入を行うことで、JavaScriptベースの開発にさらにオブジェクト指向のベストプラクティスがプッシュされます。

もちろん、言語の制限のため、静的に型付けされた言語ほど簡単で自然なことはありません。ただし、確かなことが1つあります。それは、TypeScript、単体テスト、依存性注入により、開発しているアプリの種類に関係なく、より読みやすく、疎結合で、保守しやすいコードを記述できることです。

関連: アプリではなく、WhatsAppチャットボットを作成する

基本を理解する

なぜ依存性注入を使用する必要があるのですか?

ユニットテストが可能で、保守が容易で、疎結合であるという意味で、よりクリーンなコードを記述したい場合は、依存性注入のデザインパターンを使用する必要があります。依存性注入を使用することで、車輪の再発明をせずに、よりクリーンなコードのレシピを得ることができます。

依存性注入の利点は何ですか?

依存性注入を実装することにより、保守が容易なユニットテスト可能なコードを作成する必要があります。依存関係はコンストラクターを介して注入され、単体テストで簡単にモックすることができます。また、このパターンは、疎結合コードを書くことを奨励します。

TypeScriptの目的は何ですか?

TypeScriptの主な目的は、型を追加することで、よりクリーンで読みやすいJavaScriptコードを作成できるようにすることです。これは開発者への支援であり、主にIDEで役立ちます。内部的には、TypeScriptは引き続きプレーンJavaScriptに変換されます。

Discordボットとは何ですか?

Discordボットは、通信にDiscordAPIを使用するWebアプリです。

Discordボットは何ができますか?

Discordボットは、メッセージへの応答、役割の割り当て、反応での応答などを行うことができます。通常のユーザーと管理者が実行できるDiscordアクション用のAPIメソッドがあります。

TypeScriptの利点は何ですか?

TypeScriptの主な利点は、開発者が型を定義して使用できることです。型ヒントを使用することにより、トランスパイラー(または「ソースからソースへのコンパイラ」)は、特定のメソッドに渡されるオブジェクトの種類を認識します。エラーや無効な呼び出しはコンパイル時に検出されるため、ライブサーバーのバグが少なくなります。

JS開発者として、これが私を夜更かしするものです

バックエンド

JS開発者として、これが私を夜更かしするものです
残り火データ:残り火データライブラリの包括的なチュートリアル

残り火データ:残り火データライブラリの包括的なチュートリアル

Webフロントエンド

人気の投稿
電子メール感情分析ボットを作成する方法:NLPチュートリアル。
電子メール感情分析ボットを作成する方法:NLPチュートリアル。
Redux、RxJS、およびReduxを使用したリアクティブアプリの構築-ReactNativeで観察可能
Redux、RxJS、およびReduxを使用したリアクティブアプリの構築-ReactNativeで観察可能
プレゼンテーションデザインとビジュアルストーリーテリングの芸術
プレゼンテーションデザインとビジュアルストーリーテリングの芸術
ヘルムには誰がいますか? –デザインリーダーシップの質の分析
ヘルムには誰がいますか? –デザインリーダーシップの質の分析
遺伝的アルゴリズム:自然淘汰による検索と最適化
遺伝的アルゴリズム:自然淘汰による検索と最適化
 
リモート再発明:フリーランスの仕事を見つける方法
リモート再発明:フリーランスの仕事を見つける方法
バーチャルリアリティ:仕事の未来を触媒する
バーチャルリアリティ:仕事の未来を触媒する
予測ソーシャルネットワーク分析のためのデータマイニング
予測ソーシャルネットワーク分析のためのデータマイニング
Ruby onRailsでのStripeとPayPalの支払い方法の統合
Ruby onRailsでのStripeとPayPalの支払い方法の統合
製品管理会議の包括的なリスト
製品管理会議の包括的なリスト
人気の投稿
  • 情報アーキテクトは何をしますか
  • 不和ボットはどの言語で書かれていますか
  • 出会い系サイトはどのようにお金を稼ぐのですか
  • プライベートエクイティへのファミリーオフィス投資
  • Web上のすべてのアニメーションフラッシュアニメーションです
  • クレジットカード情報をバイパスする方法
カテゴリー
人とチーム プロセスとツール バックエンド 計画と予測 収益と成長 Uxデザイン Uiデザイン デザイナーライフ ツールとチュートリアル モバイル

© 2021 | 全著作権所有

apeescape2.com