booklista tech blog

booklista のエンジニアリングに関する情報を公開しています。

Docker + Capybara + appium_capybara + RSpec + クロスブラウザー(Stable + Beta版) を使用したリグレッションテストの自動化

アイキャッチ

はじめまして。プロダクト開発部QAエンジニアの岡です。
普段は弊社が総合的な運営をサポートしている電子書籍ストアのQA業務を行っています。
今回はQA業務として行っているリリース前のリグレッションテストを自動化したことについてお伝えします。

リグレッションテスト自動化に至る背景

弊社が開発を外部委託から内製へ切り替えるにあたり、品質と開発リードタイム短縮のためテストの自動化を進めるという目標がありました。
しかし、単体テスト自動化には課題があり直ぐに実現できないことから、開発工程で不具合が混入してしまう状況でした。

一例)

  • HTMLテンプレートにJavaScriptが直書きされているなどLinterによる機械的なチェックができずブラウザーの互換性担保が難しい
  • jQueryで実装しているためセレクターの書き方によって、予期せぬところで予期せぬ動作をしてしまうことがある

このように、E2Eによるリグレッションテストが効果を発揮しやすいプロジェクト背景もあり、まず、既存のコードから独立して進めることのできるリグレッションテストの自動化をQA主体で進めることになりました。

自動化で実施したこと

  • テストシナリオの選定
  • クライアント環境の選定
  • 環境構築

テストシナリオの選定

サイトのリグレッションテストを作成するにあたり、どの部分のシナリオを作成するか考える必要があります。
サイト内をすべて網羅できていれば安心ですが、シナリオ作成やメンテナンスのコストを考えると、主要部分にケースを絞ることが必要です。
対象は電子書籍サイトのため、以下の観点でテストケースを絞りました。

  1. ユーザーがサインイン、本を購入する、本を読むなどの読書体験に関わる箇所は最重要
  2. ボタンやリンクをタップしたときにアクションが起きる箇所は重要
  3. レイアウトの崩れはSeleniumのみで検知することは難しいため、1と2が問題無ければ自動化ではOKとする

クライアント環境の選定

次に、クライアント環境の選定です。ブラウザー依存の不具合を検出するため、対応デバイスを網羅するようにしました。

[対応デバイス]

  • デバイス
    • Windows
    • Mac
    • iPhone
    • iPad
    • Android
  • ブラウザー
    • Chrome
    • Safari
    • Firefox
    • Edge

さらにブラウザーのバージョンアップでの不具合も検出するため、ブラウザーのBeta版にも対応することにしました。

環境構築

以下が作成した環境のご紹介です。

使用技術

  • Docker
    • selenium/hub
      • Grid Hubイメージ(ブラウザー構成を一元管理するものです)
    • selenium/node-base
      • Nodeのベースイメージ(ここに各ブラウザーとドライバーをインストールしています。Chromeがすでにインストールされているイメージのselenium/node-chromeもありますが、Beta版に対応させたいためこちらを使用しています)
  • Capybara
    • ブラウザーの操作を自動化するためのgem
    • Seleniumのラッパー
    • RSpecを内包している
  • appium_capybara
    • CapybaraでAppium(Seleniumの一種、モバイル用)が使えるようになる
  • RSpec
    • Rubyのテスティングフレームワーク。Webアプリの総合テスト(FeatureSpec)を記述する際に使用
  • AWS CodeBuild
    • クラウドでの定期実行に使用している

構成

構成図

こちらは、Windows側のdocker-compose.ymlの一部抜粋です。
RSpecコンテナに設置したテストスクリプト(RSpec)でテストを実行しており、全クライアント環境を共通のテストスクリプトで実施しています。

version: '3.8'
services:
  rspec:
    volumes:
      - ./rspec/test_code:/test_code
    build: ./rspec
    tty: true
    shm_size: 2gb
    env_file: .env
    ports:
      - 2222:2222
    depends_on:
      - selenium-hub
      - chrome-beta
      (略)
    environment:
      - TZ=Asia/Tokyo
  selenium-hub:
    image: selenium/hub:latest
    ports:
      - 4442:4442
      - 4443:4443
      - 4444:4444
  chrome-beta:
    build: ./chrome-beta
    environment:
      - TZ=Asia/Tokyo
      - HUB_HOST=selenium-hub
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
    depends_on:
      - selenium-hub
    shm_size: 1gb
    ports:
      - 5900:5900
  (略)

ChromeBeta版コンテナのDockerfileでは、selenium/node-base:latestをベースに、 Beta版のChromeブラウザーとドライバーをインストールしています。
参考URL: docker-selenium/NodeChrome/Dockerfile

ここまで設定したら、 docker-compose -f docker-compose.yml up --build -d でdockerを起動します。
次に、テストを実行します。
こちらはテスト実行時設定(Chrome)の一部抜粋です。

require 'selenium-webdriver'
require 'capybara/rspec'

Capybara.register_driver :chrome do |app|
  capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
    'goog:chromeOptions': {
      args: ['headless', 'window-size=1200,970', 'no-sandbox', 'disable-gpu', 'disable-dev-shm-usage']
    }
  )

  Capybara::Selenium::Driver.new(
    app,
    browser: :chrome,
    url: 'http://selenium-hub:4444/wd/hub',
    capabilities: capabilities
  )
end


こちらは、テスト実行時設定(モバイルブラウザー)の一部抜粋です。
共通のテストスプリプトで実行させるため、appium_capybaraを使用しています。
※実施時にはホストマシンでAppiumが起動中である必要があります。

# Android Chrome
require 'appium_capybara'

desired_caps_android = {
  deviceName: 'emulator-5554',
  platformName: 'Android',
  browserName: 'Chrome',
  automationName: 'UiAutomator1'
}

url = 'http://host.docker.internal:4723/wd/hub'

Capybara.register_driver(:appium) do |app|
  appium_lib_options = {
    server_url: url
  }
  all_options = {
    appium_lib: appium_lib_options,
    caps: desired_caps_android
  }
  Appium::Capybara::Driver.new app, all_options
end
Capybara.default_driver = :appium


# iPhone Safari
require 'appium_capybara'

desired_caps_ios = {
  platformName: 'iOS',
  deviceName: 'iPhone Simulator',
  browserName: 'Safari',
  platformVersion: '15.5',
  automationName: 'XCUITest',
  locate: 'ja_JP',
  languge: 'Japanese'
}

url = 'http://host.docker.internal:4723/wd/hub'

Capybara.register_driver(:appium) do |app|
  appium_lib_options = {
    server_url: url,
    wait: 30
  }
  all_options = {
    appium_lib: appium_lib_options,
    caps: desired_caps_ios
  }
  Appium::Capybara::Driver.new app, all_options
end

Capybara.default_driver = :appium


特定のクライアント環境は、AWS CodeBuildを利用して定期実行をしています。結果はSlackで通知するようになっています。

環境構築で苦労した点

  • Chromeのバージョンアップでテストコードメンテナンスの発生する場合がある
    例えば、Chrome103ではテスト実行ができない問題が発生していました。 こちらはドライバーが対応されるまでの暫定対応として、headlessモードにすることで動作しています。ブラウザーバージョンアップに対応し、常にテストを実行可能にすることが課題となっています。

  • Safariブラウザーでの要素の操作に難航した
    通常のclick()ではタップがシミュレートできないため、JavaScriptを使用して要素をタップすることで回避しています。

リグレッションテスト自動化運用後の改善点

  • リグレッションテストの実行時間が、3日 → 1日になった
  • リグレッションテスト実行時間短縮により、開発のリードタイムの短縮に貢献した
  • AWS CodeBuildを使用した定期実行(Stable + Beta版)により、ブラウザーのバージョンアップによる不具合も検知しているため、サイトが安定して稼働している

さいごに

リグレッションテストを自動化することにより、開発~運用が改善されました。
弊社では、リグレッションテストだけではなく、単体テストの自動化の促進、ノーコードでテストを自動化するツールの開発などが進められています。
お客様が快適にサイトをご利用いただけるよう、テストを通して品質の向上を目指します。

Amazon Connectを使った障害発生時の自動オンコール実現について

アイキャッチ

こんにちは。プロダクト開発部でクラウドインフラエンジニアとして業務を行っている高澤です。

インフラ構築以外の日常的な業務としては、以下のようなタスクを行っています。

  • 改善内容の発見・提案・実装
  • 異常の確認・対応
  • クラウドサービスが随時発表する機能の情報収集・検証

今回は、導入検討を行った Amazon Connect というサービスでの一斉架電方法についてお伝えします。

この記事で伝えたいこと

  • AWSが提供しているAmazon Connectサービスの概要
  • Amazon Connectを利用し、自動で一斉架電をする方法、コードについて

架電の自動化に至る背景

なんらかの障害が発生した場合、担当者に障害の発生を知らせ、対応の開始を促すことになります。

業務中であればメール・SMS・Slackなどで気づく確率が高いですが、 業務時間外や取り込み中には1回携帯電話が鳴ったり、振動したくらいでは気づかないこともあります。 電話をかけることにより、通知よりも長時間の呼び出しができるため有効な手段となります。

また、電話を架けて障害の発生を気づかせる場合にも、 1人だけがその電話で対応を開始しても対応できる内容に限界があるので、 「自動で」「一斉に電話をかけ」「障害に気づかせたい」という要望が生まれました。

ポイントをまとめると以下となります。

  • 障害が発生した場合に、電話で障害の発生を通知し対応を促したい
  • 複数人に一斉架電をしたい

Amazon Connectの技術紹介

AWSにて提供されているサービスのうち、電話を架ける、ということが可能なサービスであるAmazon Connectについて軽く紹介をします。

Amazon Connectの利用用途として主に想定されているのは、コンタクトセンターとして電話を受けたり、電話をかけたりすることによりお客様対応を効率的に実現することです。 Amazon Connectを使うことでウェブブラウザー上で電話を受けたり、別の人に電話を転送したり、など様々なことができます。 音声、SMS、メールなどを通してお客様とのコミュニケーションを取ることができるサービスです。

このAmazon Connectで、今回は電話を架ける、という機能を主に使っていきます。

作成した環境の紹介

構成図

使用する要素は以下の4つです。

  1. Datadog(Monitorを使用)
  2. AWS SNS
  3. AWS Lambda
  4. Amazon Connect

要素は以下のような流れで連携します。

Datadogから一斉架電までの流れ

初期設定について

Amazon Connectの導入・初期設定については、 以下の記事を参考とさせていただき、構築しました。

参考URL: DatadogとAmazon Connectを用いた電話通知実施してみた【監視】

現在は追加で以下の2点の申請が必要でした。

  1. 電話番号を取得するために申請が必要1

  2. 携帯電話に架電したい場合は、電話番号を取得後に別途緩和申請が必要

一斉架電の実現について

ここから、複数人に同時に架電をする+容易に架電対象とするかどうかの設定変更を可能にしていきます。

同時に架けられる数の調査

一斉に電話を架ける場合、同時に何人にかけられるのかを確認してみます。

参考URL: Amazon Connect サービスクォータ

この中の、「インスタンスあたりのアクティブな同時呼び出しの数」が「同時に何人にかけられるのか」に該当します。

デフォルトは10ですので、10人に同時に架けられるようです。

こちらは気になったので AWS Support Centerにて問い合わせをし、確認が取れています。

発信と着信の両方を合算した数 であり、インスタンス内の電話番号すべての音声通話数 とのことです。

今回は発信しかしませんので、同時であれば10人までの一斉架電が可能です。

10人以上に同時架電したい場合は、緩和申請にて依頼してみることになると思われます。

一斉架電の実現パターンについて

一斉架電の実現パターンとしては、有効な選択肢として以下の2つが考えられました。

  • パターン1

    • 1つのSNSから1つのLambdaを呼び、その中で複数人に架電
      • メリット:SNSを1つ指定するだけでグループに架電可能
      • デメリット:変更があった場合にグループ情報の編集が必要
  • パターン2

    • 複数のSNSから複数のLambdaを呼び、1つのLambdaでは1人に架電
      • メリット:変更があった場合にトリガからSNSを外すだけでOK
      • デメリット:トリガに複数のSNSの設定が必要

架電の対象とする人数がそこまで多くならないのではないか、という見込み、および 対象者のつけ外しのしやすさを選択し、今回はパターン2を選択しました。 この場合、1つのSNS=1人への架電、となります。 人数がとても多い場合はパターン1のほうが良いと思われます。

架電用のAmazon Connectの問い合わせフロー(SNS, Datadog設定, Lambda)について

Amazon Connectの問い合わせフロー

参考とさせていただいた記事と同じです。

こちらは1回作れば変更はない想定のため、コードではなく手動で作成しました。

SNSについて

コードで管理したい、人数分作らなければならない、ということもあり、 AWS CDKにてSNS,Lambda部分を作成しました。 SNSとLambdaが一対一となるように作成します。

SNS-Phone-Call-for-A → Lambda-for-A
SNS-Phone-Call-for-B → Lambda-for-B

といった形で作っています。

以下、Amazon Connect設定用Stackと、interface,settingの該当部分のコード例です。

/* eslint-disable no-new */
import { Duration, Stack, StackProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as sns from "aws-cdk-lib/aws-sns";
import * as subs from "aws-cdk-lib/aws-sns-subscriptions";
import * as environment from "../environment"; //設定用に作成しているファイルです

/**
 * Amazon Connect設定用Stack
 */
export class AmazonConnectSettingStack extends Stack {
  constructor(
    scope: Construct,
    id: string,
    target: string, // dev,staging,prod,などのデプロイターゲットのstringです
    setting: environment.EnvironmentSetting, // 設定です
    props?: StackProps
  ) {
    super(scope, id, props);

    /**
     * Amazon Connect呼び出し用のLambdaの作成
     */
    // Lambda関数に付与するIAMロール
    const connectRole = new iam.Role(
      this,
      `${target}-amazon-connect-lambda-role`,
      {
        assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
        path: "/service-role/",
        inlinePolicies: {
          ConnectPolicy: new iam.PolicyDocument({
            statements: [
              new iam.PolicyStatement({
                actions: [
                  "logs:CreateLogGroup",
                  "logs:CreateLogStream",
                  "logs:PutLogEvents",
                ],
                resources: ["*"],
              }),
              // Amazon Connectの権限
              new iam.PolicyStatement({
                actions: [
                  "connect:Start*",
                  "connect:Describe*",
                  "connect:List*",
                ],
                resources: ["*"],
              }),
            ],
          }),
        },
      }
    );

    // ユーザーごとに回してLambda関数を作成
    setting.AMAZON_CONNECT_DEFINITION!.ConnectUserLists.forEach(
      (ConnectUserList) => {
        // Lambda関数を作成
        const callFunction = new lambda.Function(
          this,
          `${ConnectUserList.UserName}-call`,
          {
            runtime: lambda.Runtime.PYTHON_3_9,
            handler: "lambda_function.lambda_handler",
            architecture: lambda.Architecture.ARM_64,
            code: lambda.Code.fromAsset("lib/resources/lambda/connect-call/"),// Lambdaのファイルを置いている場所を指定します
            timeout: Duration.seconds(10),
            role: connectRole,
            environment: {
              DESTINATION_NAME: ConnectUserList.UserName,
              DESTINATION_PHONE_NUMBER: ConnectUserList.PhoneNumber,
              CONTACT_FLOW_ID: setting.AMAZON_CONNECT_DEFINITION!.ContactFlowId,
              INSTANCE_ID: setting.AMAZON_CONNECT_DEFINITION!.InstanceId,
              SOURCE_PHONE_NUMBER:
                setting.AMAZON_CONNECT_DEFINITION!.SourcePhoneNumber,
            },
          }
        );

        // SNS,サブスクリプション作成
        const snsTopic = new sns.Topic(
          this,
          `${ConnectUserList.UserName}-call-sns`,
          {
            displayName: `Phone-Call-for-${ConnectUserList.UserName}`,
            topicName: `Phone-Call-for-${ConnectUserList.UserName}`,
          }
        );
        snsTopic.addSubscription(new subs.LambdaSubscription(callFunction));
      }
    );
  }
}


//Amazon CONNECT 該当interfaceの抜粋
//(略)

/**
 * Amazon CONNECT用の宛先名・電話番号
 */
export interface ConnectUserList {
  UserName: string; // 受話する人の名前(主に識別のため)なるべく英数字
  PhoneNumber: string; // 電話番号 (日本の 080-xxxx-yyyyの場合は +8180xxxxyyyy 形式)
}

/**
 * Amazon Connectの定義
 */
export interface AmazonConnectDefinition {
  InstanceId: string; // AmazonConnectのインスタンスID
  ContactFlowId: string; // AmazonConnectのコンタクトフローID
  SourcePhoneNumber: string; // 発信元電話番号
  ConnectUserLists: ConnectUserList[]; // 受話するユーザーリスト
}

//(略)


//setting の該当設定例の抜粋
//(略)

  AMAZON_CONNECT_DEFINITION: {
    InstanceId: "ABCDE",
    ContactFlowId: "ABCDE",
    SourcePhoneNumber: "+8100000000", //発信元電話番号
    // ConnectUserListsはAmazon Connectの同時発信制限により、10までとしてください(緩和可能)
    ConnectUserLists: [
      { UserName: "A-user", PhoneNumber: "+818000000000" },
      { UserName: "B-user", PhoneNumber: "+818000000000" },
    ],
  },

//(略)

上記のようにCDKで作らない場合も、SNSとLambdaを作成していけばOKです。

Lambda functionについて

こちらも参考とさせていただいた記事とほぼ同じです。

違いは、1つのLambdaにて1人に電話を掛けるようにしているため、電話番号の取得場所を直接環境変数から取っていることです。

冗長とはなりますが、CDKで管理されているため、あまり気にする必要はなくなります。

# -*- coding: utf-8 -*-
# Amazon Connectを利用し、電話をかける
# Datadogでの指定を細かく可能とするため、1ユーザーごととする
import logging
import os
import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
    logger.info("Event: " + str(event))

    # Datadogからのイベントからアラート名を取得し、読み上げに使う
    datadogSubject = event["Records"][0]["Sns"]["Subject"]
    message = datadogSubject + "が発生しました。確認・対応をしてください。"

    # ログ用
    logger.info("DESTINATION_NAME: " + os.environ["DESTINATION_NAME"])  # 電話宛先名
    logger.info("Message: " + str(message))

    # Amazon Connectで架電
    connect = boto3.client("connect")
    response = connect.start_outbound_voice_contact(
        DestinationPhoneNumber=os.environ["DESTINATION_PHONE_NUMBER"],
        ContactFlowId=os.environ["CONTACT_FLOW_ID"],
        InstanceId=os.environ["INSTANCE_ID"],
        SourcePhoneNumber=os.environ["SOURCE_PHONE_NUMBER"],
        Attributes={"alarm": message * 2},  # 2回メッセージを繰り返した文言を読み上げる
    )
    logger.info(str(response))

Datadog設定について

Datadogのモニタが変化した場合、電話をしたい場合は、DatadogのモニタのNotify your teamにて、 以下の例のように設定をします。 一人ずつ別れているため、ここでつけ外しが容易です。

Datadogモニタ設定

テスト

Datadogでテストを行います。 複数のSNSをつけてテストし、ほぼ同時に複数の電話に着信があれば完成です。

では、楽しいAmazon Connect架電ライフをお送りください。


  1. AWS Support Center にて、Amazon Connectで使用する電話番号を取得したい旨を伝えると、必要な書類や手続きについて回答が貰えます。内容は変更になる可能性がありますので、詳細はAWS Support Center にてお問い合わせください。

App Storeで3位になったアプリをFlutterでつくっている話

自己紹介

はじめまして
株式会社ブックリスタでスマートフォンアプリエンジニアをしている藤井と申します。

まだまだスマートフォンアプリに関しては始めたばかりの弊社ですが、界隈では有名になりつつあるOshibanaというアプリを開発しています。
Oshibanaってどんなアプリなのか簡単に説明すると「推しを常に身近に感じて推し活と人生を豊かにしてくアプリ」です。
ご存知でない方はぜひとも、一度手に取ってもらえるとありがたいです。

Oshibana

※今はiOS版だけになります。

Flutter について

さて、冒頭でも紹介したOshibanaですがFlutterを採用して開発しております。

FlutterはGoogleが提供するDart言語を使用したアプリケーションフレームワークです。
 ※アプリケーションフレームワークとは、アプリの開発を支援するために作られたライブラリのこと。

現在はモバイルアプリケーションのフレームワークとして有名ですが、Webアプリ、デスクトップアプリ(Windows,Mac,Linux)などのアプリも作ることが可能です。
今後はFlutterができればどんなアプリも作れる時代が来ることでしょう。

ただ実際にプロジェクトで採用する際に問題となるのが、言語がDartという点。
普通に使ってると学ぶことはなく、過去にはワーストランキングで1位になるという不名誉1も持っています。
私も最初は難しいのかなと思っていたのですが、実際に携わってみると言語としてはそんなに難しいものではないと感じました。
また人気も、今はFlutterの人気に伴い急上昇2をしています。

難しいと思わなかった理由

  • 関数や処理の書き方もJavaやCを学んだ人ならすぐに理解できる
  • 公式サイトに色々なパッケージが用意されており、一般的なiOS、Andoridの機能はDartのみで実現できる
  • 画面を作成する上でホットリロードがかなり便利で、ビルドし直しの手間を減らすことができる
  • 画面を作成するための基礎的なWidgetについては日本語の資料がいっぱいWebに存在している
  • Flutterで実現できない場合でも、KotlinやSwiftを使って処理ができる

でもやっぱり、デメリットもあります

  • 画面を生成するためのWidgetの組み合わせは慣れないと配置が難しい
  • 状態管理がいっぱいありすぎて何がベストプラクティスなのか迷ってしまう
  • iOSアプリっぽく作ろうとすると標準のマテリアルデザインを変える必要がある
  • ちょっと複雑なことをしようとすると、英語などの資料を読む必要がある

パッケージ紹介

Flutterはいろいろなパッケージがあり、公式サイトで管理されてます。
色々なパッケージがあり大変便利なのですが、前述したデメリットで挙げた通り日本語説明を書いてくれているWebサイトがないパッケージも多数あります。 せっかくなのでそういったパッケージを紹介します。

https://pub.dev/packages/background_fetch

background_fetchというパッケージです。
どんなパッケージかというと、アプリがバックグランドにいっても処理を行う機能を追加するためのものになります。

Oshibanaではこのバックグランド処理を使って、通知用のデータ作成やクリアを行っています。

Webで検索するとworkmanagerがよく引っかかるのですが、私の場合、ひとまず手順書どおり実装してもXcodeを使ったBackgroundFetchのテストで動作が確認できませんでした。
その後、実機で確認してみてもやはり動作していませんでした。
そのため、色々検討した結果background_fetchの導入をしました。
また、background_fetchの方が導入時に必要な設定が少なめになっており、個人的にはおすすめです。

導入方法

パッケージのpubspec.yamlに次のように記入を追加します。

  dependencies:
   background_fetch: ^1.0.2

現時点でOshibanaアプリはiOSだけの対応ですので、記載もiOSだけに絞って記載します。

 まず、Runner配下のinfo.plistに次の記述を追加します。

 <key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>XXX.YYYYYYYYY.fetch</string>
    </array>
    <key>UIBackgroundModes</key>
    <array>
        <string>fetch</string>
        <string>processing</string>
    </array>

以上で準備は完了です。

あとは次のコードをmain.dartのinitStateあたりで呼び出せば完了です。

    int status = await BackgroundFetch.configure(BackgroundFetchConfig(
        minimumFetchInterval: 30,
    ), (String taskId) async {  

   //ここにバックグラウンドで実行したい処理を追加する

      BackgroundFetch.finish(taskId);
    }, (String taskId) async {  
      BackgroundFetch.finish(taskId);
    });

公式ですと、BackgroundFetchConfigに対していろいろな引数を設定していますが、minimumFetchInterval以外はAndorid用の設定になります。
そのため、iOSでの運用であればその他は設定する必要がありません。
また、minimumFetchIntervalの設定値はiOSの場合15分以上に設定する必要があります。

さいごに

ここまで読んでいただき、ありがとうございました。
ブログ作成ということ自体が初めてでしたので拙い内容となっておりますが、皆様に読んでためになることを今後も記載していけるよう日々精進いたします。