メインコンテンツまでスキップ

初級者向け!手書きMoqを使ってユニットテストをさらに進めよう(前編)

鶴田
ディアシステム(株)開発一部第2課

こんにちは。開発2課の鶴田です。

この記事は、前回の「ユニットテストを使ってカバレッジを計測、テストの品質を向上させよう」の続編です。前回は、主に「プログラムのロジックが十分にテストされているか(=カバレッジ)」を確認することで、テストの質を高める方法を紹介しました。

今回はそこから一歩進んで、実際のアプリでよく登場する「データベースアクセス」や「外部データとのやりとり」が入った場面でも、ユニットテストを効果的に行う方法を紹介します。

一般的にはMoqというオープンソースソフトウェア(以下、OSSと呼びます)を使って、データベースの代わりとなる代用オブジェクトを作成、テストします。しかし、実際の業務では開発ルール上、OSSを利用できないケースもあります。

そこで今回は、OSSを使わずに実装する方法を紹介します。 OSSを使わずに手でMoq的なモノを実装することで、Moqの基本的な概念に対する理解が深まる効果もあります♪

いきなり実装に入ると混乱しやすいため、この記事(前編)では今回のゴールと実装方針、用語の解説を先に紹介します。次回の後編で、実際にコードを書いてみましょう。

※後編の実装編ではわたしが書いた記事を流用します。記事はこちらです。

今回のゴールと実装方針

実際のアプリでは、商品マスター(前回の記事ではProduct)や在庫マスター(前回の記事ではZaiko)の情報は「データベース」や「CSVファイル」などの外部データソースから取得されます。しかしそれをそのままユニットテスト化しようとすると、データベースの準備や接続が必要となり、ユニットテストを実装・実行するためのコストが上がります。

そこで今回は、データ取得の仕組みをインターフェイスをつかって抽象化し、本番環境とテスト環境で差し替えられるようにする方針を取ります。

たとえば、商品マスター(前回の記事ではProduct)の場合は以下の通りとなります。

public interface IProductRepository
{
// Find は習慣的に決まっている名前
Product? Find(string productCode);
}

// テスト用の取得・更新用クラスはsealedにすることが一般的
public sealed class InMemoryProductRepository : IProductRepository
{
// テスト用 Product 向けリポジトリクラス
Product? Find(string productCode)
{
// テスト用の値を返すロジック
return new Product



}
}

このように「データの取り出し方」だけを切り離すことで、DBやテスト用CSVがなくてもユニットテストを実行することができ、結果として保守しやすいコードになります。

用語の解説

Moq(モック)

Moqは .NET向けに広く使われているOSSです。インターフェイスや仮想メソッドに対して、簡単に「スタブ」や「スパイ」として振る舞うオブジェクトを生成できます。

var mock = new Mock<IProductRepository>();
mock.Setup(x => x.Find("001")).Returns(new Product("001", "商品A", 100));

このように記述することで、Find("001") の戻り値をテストごとに制御できます。また、呼び出し回数の確認(Verify)なども可能です。とても便利なライブラリですが、本記事では使用しない方針で進めます。

リポジトリ (Repository)

リポジトリとは「データの取得や保存のしくみを隠すためのクラス」です。 実装用とテスト用、2パターンを用意し、共通のインターフェイス経由で実装することで差し替えることができるようになります。

テストダブル (Test Double)

「テストのときに、本物の代わりに使うオブジェクト」の総称です。今回は以下の2つの機能をつかいます。

スタブ:決まった値を返すだけ。今回の例ではInMemoryProductRepositoryがスタブに該当します スパイ:呼び出されたかどうかを記録します。今回は呼び出された回数を内部で記録、検証します。

依存関係逆転の原則(DIP)と依存性の注入(DI)

テスト用のリポジトリに差し替えるためには、依存関係逆転の原則(DIP)と依存性の注入(DI)という考え方が重要になります。

依存関係逆転の原則(DIP) は以下の2つのルールからなりたっています。

  1. 上位のモジュールは下位のモジュールに依存してはならない。

  2. 抽象詳細に依存してはならない。

    • 上位 ・・・・ビジネスロジックやアプリ全体を制御するロジック
    • 下位 ・・・・DBやCSV、JSONに対する入出力ロジック
    • 依存する ・・ここでは実装を変更したときに影響を受けることを意味する。具体的な例でいうと、下位モジュールのコードを変更したときに、上位モジュールがエラーになったりすることを言う
    • 抽象 ・・・・インターフェィスや抽象クラスが該当する
    • 詳細 ・・・・具体的な実装

依存性の注入(DI) はオブジェクトが必要とする依存先(他のオブジェクトやサービス)を、外部から渡してもらうデザインパターンです。

ちなみにですが、私は「依存性の注入」がデザインパターンの名前のことであることに気づきませんでした。(名前って重要や・・・) 当社の土井がアップしたこちらの記事でわかりやすく解説しています。5分程度でサラッと読めるよい記事です。ご一読されることをおススメします。 依存関係逆転の原則とDI(Dependency Injection)

後編の予告

後編では、実際にInMemoryProductRepositoryやProductServiceを使ってユニットテストを書きます。 前回同様、Fine Code Coverageを使って、テストがしっかり書けているかも確認します。

環境ごとのapp.configの切り替え

土井
ディアシステム(株)開発一部第2課

こんにちは。開発一部の土井です。
普段、業務で C#を使っているのですが、テストから本番に移すときなど、アプリケーション構成ファイル内の設定値を手動で変更しておりましたが、手間だったり、ミスも起こりやすいと思い調べていたところ良さそうな方法を見つけたので、ここに書きたいと思います。

動作環境

  • Visual Studio Community 2022
  • .NET6.0

今回行いたいことは、ソリューション構成(Debug,Release)の切り替えにより、アプリケーション構成ファイル(app.config)の内容が変更されるようにすることです。

画像1

画像2

結論から方法を伝えると、プロジェクトの csproj ファイルへ以下の設定情報を追加することで可能です。

<!-- Debug構成時 -->
<Target Name="CopyConfigDebug" Condition="'$(Configuration)' == 'Debug'" BeforeTargets="BeforeBuild">
<Copy SourceFiles="App.$(Configuration).config" DestinationFiles="app.config" />
</Target>

<!-- Release構成時 -->
<Target Name="CopyConfigRelease" Condition="'$(Configuration)' == 'Release'" BeforeTargets="BeforeBuild">
<Copy SourceFiles="App.$(Configuration).config" DestinationFiles="app.config" />
</Target>

各要素について、ご説明します。

<Target Name="CopyConfigDebug" Condition="'$(Configuration)' == 'Debug'" BeforeTargets="BeforeBuild">

<Target> はビルド中に実行するタスクを定義します。
Name はこの要素の名前です。
Condition は要素を有効にするかどうかを決めます。 ’$(Configuration)’ がソリューション構成の値です。つまり、今回の場合だとソリューション構成が’Debug’となっている場合に <Target> 内のタスクを有効にするということになります。
BeforeTargets はこの <Target> をどのタイミングで実行するかというものです。今回の場合は ”BeforeBuild” となっており、ビルド前に実行するということになります。

<Copy SourceFiles="App.$(Configuration).config" DestinationFiles="app.config" />

<Copy>はその名前の通りコピーを行う要素です。 SourceFiles がコピー元で、DestinationFiles が貼り付け先となります。

つまり、ソリューション構成が Debug のときは App.Debug.config を、Release のときは App.Release.config を、それぞれ ビルドの前に app.config に上書きコピーします。

以下、設定情報追加後の csproj ファイル

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<!-- Debug構成時 -->
<Target Name="CopyConfigDebug" Condition="'$(Configuration)' == 'Debug'" BeforeTargets="BeforeBuild">
<Copy SourceFiles="App.$(Configuration).config" DestinationFiles="app.config" />
</Target>

<!-- Release構成時 -->
<Target Name="CopyConfigRelease" Condition="'$(Configuration)' == 'Release'" BeforeTargets="BeforeBuild">
<Copy SourceFiles="App.$(Configuration).config" DestinationFiles="app.config" />
</Target>

</Project>

これで、ソリューション構成を切り替えることで App.Config の中身を切り替えることができます。
少し注意点ですが、ビルドを実行することで App.config の中身が切り替わるので、必ずソリューション構成を切り替えた後にビルドを行ってください。

ここからはおまけになりますが、csproj の他の要素についてご説明します。
<Project Sdk="Microsoft.NET.Sdk">
プロジェクト内でどの SDK を使うか定義しています。

<PropertyGroup>
プロジェクトのビルドや振る舞いの基本設定をまとめています。

<OutputType>
出力の種類を決めています。今回だと実行ファイル(.exe)を出力します。

<TargetFramework>
対応する.NET のバージョンを指定します。

<ImplicitUsings>
Using を自動で補完してくれる機能の使用可否を決めます。例えば、using System;と明示しなくても良くなります。

<Nullable>
Null に関しての安全なコードを書くためのチェックが有効になります。

他にも様々な要素が存在しますが、全ては書ききれないので一番初めからある基本的なものだけ説明しました。

ここまで csproj について説明しました。
今回は app.config の内容を丸々上書きするという内容でしたが、一部要素だけ書き換えるという方法もあるようでした。(なぜか上手くできませんでした。)
今後はこの方法で設定値を環境ごとに切り替えようと思います。

応用情報技術者試験の合格体験記

鈴木
ディアシステム(株)開発一部第2課

こんにちは。開発1部の鈴木です。

2024年の秋期試験(10月)を受験し、無事に合格することができました。これから応用情報技術者資格の取得を目指している方に向けて、私の勉強方法や、勉強時間などをお話ししたいと思います。


■応用情報技術者試験の概要

応用情報技術者試験のWebページ https://www.ipa.go.jp/shiken/kubun/ap.html

応用情報技術者試験は、独立行政法人情報処理推進機構が実施している試験です。春期(4月)、秋期(10月)の年2回実施しており、各地域の会場に直接赴いての筆記試験となります。

試験は午前、午後と分かれており、午前試験は80問の四肢択一問題、午後試験は必答問題が1問と、10問中4問を選択して回答する記述問題です。

■受験前のスキルレベル

同じく情報処理推進機構の基本情報技術者試験に合格していました。 エンジニアとしての経験があまりない方は、基本情報技術者をとってから応用に進んだ方が良いと思います。

■勉強方法

応用情報技術者試験ドットコム https://www.ap-siken.com/

上記のサイトでひたすら問題を解き続けました。それだけです。 前述の基本情報技術者試験受験の際にお世話になり、そのままステップアップしていった形です。

15年以上前から、精力的に更新され続けているサイトで、無料で利用できます。応用情報技術者試験の過去問が網羅されており、解説も充実しております。

●午前試験の勉強

基本情報技術者試験の問題が一回り難しくなったくらいの難易度なので、基本情報技術者試験に合格していれば、上記サイトの過去問道場の問題を一通り解くのは難しくないと思います。

分からなかった問題は解説を読み、自分で他の人に説明できる、というくらいまで理解度を上げるように心がけました。分野を絞って出題する機能もありますので、苦手な分野はそちらの機能を使って何度も確認しました。

私は一日10~30分くらい取り組んで、2週間程で9割以上正答できるようになったので、午後試験対策に移りました。

こちらに時間をかけるよりは、後述する午後試験の対策にしっかり時間をとった方が良いと感じました。

●午後試験の勉強

午前試験が楽々解けるようになり、意気揚々と午後試験道場に乗り込んだのですが、一瞬で打ちのめされました。

四肢択一の午前試験と異なり、自分で記述して回答しなければならない午後試験は、情報技術へのより深い理解が必要とされました。最初の内は選択問題だけ当てずっぽうで回答して、記述問題は白紙で解説を読む、なんてこともザラでした。

しかし、応用情報技術者試験ドットコムでは解説が非常に丁寧にされているので、理解できるまで読み込んで、気になるワードで検索をかけて知識を仕入れて、他の年度の過去問に挑戦を繰り返す内に、記述問題も正答できるようになっていきました。

午後試験は必答の問題が1問と、10問の内、4問の選択式ですので、選択する分野を絞った方が、必要な勉強時間は短くなります。私の場合は、選択する分野を完全に4分野に絞って勉強をしました。午前試験の過去問を解く中で、自分で得意だと感じた分野から挑戦するのをオススメします。

ただ、各年でそれぞれの分野の難易度にバラツキがあるので、念のため5、6分野の勉強をしておくのも手だと思います。問題文に目を通して、難易度に当たりをつけられるようになる必要がありますが。。。

午後試験は、各分野を10年度分、ほぼ正答できるようになるまで解いて受験に臨みました。合計で40時間くらいはかけたのではないかと思います。ここは自信がつくまで、じっくり時間をかけて勉強しました。

■応用情報技術者試験の振り返り

数ある情報系資格試験の中でも、それなりに深く、幅広い知識が求められ、難易度が高めだと言われる応用情報技術者試験ですが、試験勉強を通じて得られるものは大きかったです。特にエンジニアから、チームリーダーやマネージャーへのキャリアアップを目指す人にとっては、一つの指針となってくれるのではと感じました。

私は、今後、応用情報技術者試験の更に上のレベルと目される試験にも挑戦したいと思っています。現在は情報処理安全確保支援士試験の勉強を続けております。


最後になりましたが、ここまでお読みくださり、ありがとうございました。この記事が、これから応用情報技術者試験の受験を目指す方のお役に立てることをお祈りしております。

Scoop を使ったアプリケーション管理

河田
ディアシステム(株)開発一部第5課

Scoop を使ったアプリケーション管理を紹介します。

Scoop とは

Scoopとは、有志により開発されている Windows 用のコマンドラインインストーラーです。
PowerShell 上でコマンドを実行することでアプリケーションのインストール、更新、アンインストールなどが行なえるようになります。
パッケージ管理ツールと呼ばれることもあり、同様のツールに Microsoft の WinGetや Chocolatey Software の Chocolateyがあります。

Scoop のインストール

さっそく Scoop をインストールします。PowerShell ターミナルを起動し、PS C:\> プロンプトが表示されたら次のコマンド実行します。

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

Scoop was installed successfully! といった表示が出ればインストールは完了です。

アプリケーションのインストール

Scoop でアプリケーションをインストールするにはアプリケーション名を指定して install コマンドを実行します。

例として、Windows に初期インストールされている Windows PowerShell ではない、PowerShell Core をインストールしてみます。

scoop install pwsh

これだけでインストールは完了です。アプリケーションの設定によってスタートメニューに追加され、環境変数 PATH に設定されているため、 スタートメニューから実行したり、Windows キー + R の「ファイル名を指定して実行」で pwsh と入力し実行すると PowerShell Core が起動するようになります。

インストールしたアプリケーションは ~\scoop ディレクトリ(例: C:\User\user\scoop)に配置されるため、環境を綺麗に保つことができます。

インストールしたアプリケーションのアンインストールは uninstall コマンドで、更新は update コマンドで行ないます。

scoop uninstall pwsh
scoop update pwsh

他にどのようなアプリケーションがあるかは search コマンドで一覧表示し、info コマンドで詳細が確認できます。

scoop search
scoop info pwsh

バケットの追加

アプリケーションの設定はマニュフェストという JSON 形式のファイルに記述されています。いくつかのマニュフェストを纏めて管理しているものがバケットです。

初期状態では main バケットしか有効になっていません。main バケットに GUI ツールは含まれないため、インストールできるアプリケーションも限られたコマンドラインツールのみになります。

そこでバケット extras を追加して、VSCode をインストールしてみます。

scoop bucket add extras
scoop install vscode

extras 以外にも games nerd-fonts nirsoft sysinternals java nonportable php versions バケットが利用できます。

エクスポートとインストール

Scoop で管理しているアプリケーションの状態を JSON 形式でエクスポートすることができます。 エクスポートした JSON をインポートすることで管理していた状態を再現することができます。

エクスポートすると次のような内容の scoop.json ファイルができあがります。

scoop export > ~/Desktop/scoop.json
scoop.json
{
"buckets": [
{
"Name": "main",
"Source": "https://github.com/ScoopInstaller/Main",
"Updated": "2025-04-19T17:30:20+09:00",
"Manifests": 1388
},
{
"Name": "extras",
"Source": "https://github.com/ScoopInstaller/Extras",
"Updated": "2025-04-19T21:30:52+09:00",
"Manifests": 2152
}
],
"apps": [
{
"Info": "",
"Name": "pwsh",
"Source": "main",
"Version": "7.5.0",
"Updated": "2025-04-19T22:49:29.0042927+09:00"
},
{
"Info": "",
"Name": "vscode",
"Source": "extras",
"Version": "1.99.3",
"Updated": "2025-04-19T22:56:28.5872264+09:00"
},
]
}

インポートするには import コマンドに JSON ファイルのパスを指定します。

scoop import ~/Desktop/scoop.json

これで PowerShell Core と VSCode がインストールされた状態が再現されます。

まとめ

標準で提供されているもの以外に有志により提供されるバケットも利用すると ある程度のアプリケーションは Scoop で管理できるようになります。

コマンドラインでアプリケーションの管理ができる Scoop を活用してみてはいかがでしょうか。

DartでSwiftのif letぽく書きたい!!

井上
ディアシステム(株)開発二部第1課

こんにちは。モバイル開発歴 12 年目の井上です。

業務で Flutter / Dart を扱う事があり、その中で Swift の if let のように書きたい!!

    var fruit: String? = "apple"

if let apple = fruit {
print("リンゴ")
}

と思ったのですが・・・Dart では書けない事が判明

なんとか書けなものかとさらに調べると、switch で Null チェックができることを発見!!

  String? fruit = "apple";

switch (fruit) {
case var apple?:
print("リンゴ");
}

これだ!! if let では無いものの理想に近い物に出会えました。

Dart 3.0 から使用することができます。ご注意ください

余談になりますが

  String? fruit = "apple";

switch (fruit) {
case var apple?:
print("リンゴ");
default:
return;
}

上記のように default に return を記述すると、switch を終えた後の fruit は Null 非許容として扱うことができます。

DIコンテナの使い方

土井
ディアシステム(株)開発一部第2課

別記事にて依存関係逆転の原則と DI(Dependency Injection)の手法について記載いたしました。今回はこの手法を使うときに便利な DI コンテナの使い方を紹介します。

前提条件

以下のツールを事前にインストールしておいてください

  • Visual Studio また以下のライブラリを Nuget パッケージよりインストールしてください
  • Microsoft.Extensions.DependencyInjection
動作環境
  • Visual Studio Community 2022
  • .NET6.0

早速 DI コンテナを使ってみる

まず使用するインターフェースとクラスを用意します。

public interface ILogger
{
void Info(string message);
}

public class Logger : ILogger
{
public void Info(string message)
{
Console.WriteLine(message)
}
}

次に DI コンテナを作成します。
DI コンテナは「ServiceCollection」クラスを使用します。
この「ServiceCollection」クラスは DI の情報を格納するクラスで、ここにインターフェースとクラスを登録していきます。
※TestFuncClass はこの後、作成します。

using Microsoft.Extensions.DependencyInjection;

・・・
public class Program
{

static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddSingleton<ILogger, Logger>();

TestFuncClass inst = new TestFuncClass();
inst.Execute(services);
}
}

インターフェースとクラスの登録には3種類のメソッドが用意されており、
状況によって使い分けます。各メソッドの使い方等については後述にて記載いたします。

  • AddSingleton アプリケーション全体で一度だけインスタンスが生成され、全てのリクエストで同じインスタンスが再利用されます。

  • AddScoped 各スコープごとに新しいインスタンスが生成されます。

  • AddTransient 各リクエストごとに新しいインスタンスが生成されます。

次にこの DI コンテナを使ったクラスを作成します。
BuildServiceProvider()メソッドは ServiceCollection に登録されたサービスのインスタンスを解決し、提供する役割を持ちます。
Execute メソッド内では BuildServiceProvider()から GetService メソッドを使って、ILogger を継承した Logger のインスタンスを受け取ります。これで TestFuncClass は ILogger を継承した Logger クラスのメソッドを使用することができます。

public class TestFuncClass
{
public void Execute(ServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
var logger = serviceProvider.GetService<ILogger>();
logger.Info(“test”);
}
}

また Logger 内に string 型の引数を持つコンストラクタがある場合、インスタンスとインターフェースの登録は以下のようになります。

public class Logger : ILogger
{
private string _subMessage;
public Logger_Part2(string message)
{
_subMessage = message;
}
public void Info(string message)
{
Console.WriteLine(message + _subMessage);
}
}

public class Program
{
static void Main(string[] args)
{
TestFuncClass inst = new TestFuncClass();
var services = new ServiceCollection();

services.AddSingleton<ILogger>(provider => new Logger(“追加メッセージ”));
inst.Execute(services);
}
  }

DI コンテナへの登録の3種類の確認
前述にて DI コンテナへ登録する際に 3 つのメソッドがあることを記載しました。
ここではその使い方や動作の違いを確認していきたいと思います。

ILogger と Logger クラスを以下のように修正します。

public interface ILogger
{
stirng SubMessage{ get; set;}
void Info(string message);
}

public class Logger : ILogger
{
public string SubMessage { get; set; }
public void Info(string message)
{
Console.WriteLine(message)
}
}
  • AddSingleton
    アプリケーション全体で一度だけインスタンスが生成されるので、インスタンスの受け取りを再度行っても、同じインスタンスが取得され logger.SubMessage に入れた値がそのままコンソールへ表示されています。
public class Program
{
Static void Main(string[] args)
{
var services = new ServiceCollection();
// DIコンテナへの登録
services.AddSingleton<ILogger,Logger>();

var serviceProvider = services.BuildServiceProvider();
// インスタンス受け取り
var logger = serviceProvider.GetService<ILogger>();
logger.SubMessage = “追加メッセージ”;
logger.Info(“test ++ logger.SubMessage);
// もう一度インスタンスを受け取る
logger = serviceProvider.GetService<ILogger>();
logger.Info(“test ++ logger.SubMessage);
}
}

出力結果

test +追加メッセージ
test +追加メッセージ
  • AddScoped
    AddScoped では各スコープごとに新しいインスタンスが生成されます。
    使い方としては CreateScope()メソッドによって、スコープを作成し、Dispose()メソッドを使うことで、スコープを破棄します。
    このスコープを破棄しない限りは GetService()メソッドを実行しても、同じインスタンスが返されます。 スコープを再作成すると、新しいインスタンスが生成されます。
public class Program
{
Static void Main(string[] args)
{
var services = new ServiceCollection();
// DIコンテナへの登録
services. AddScoped<ILogger,Logger>();
var serviceProvider = services.BuildServiceProvider();
// スコープの作成
var scope = serviceProvider.CreateScope();
        // インスタンス受け取り
var logger = scope.ServiceProvider.GetService<ILogger>();
logger.SubMessage = “追加メッセージ”;
logger.Info(“test ++ logger.SubMessage);
// もう一度インスタンスを受け取る
logger = scope.ServiceProvider.GetService<ILogger>();
logger.Info(“test ++ logger.SubMessage);
// スコープの破棄
Scope.Dispose();

// スコープの再作成
scope = serviceProvider.CreateScope();
logger = scope.ServiceProvider.GetService<ILogger>();
logger.Info(“test ++ logger.SubMessage);
// スコープの破棄
Scope.Dispose();
}
}

出力結果

test +追加メッセージ
test +追加メッセージ
test +
  • AddTransient
    リクエストのたびに新しいインスタンスが生成されるため、GetService()の実行のたびに新しいインスタンスが生成されます。そのため2つ目の出力の際、logger.SubMessage に入れた値は空の状態となっています。
public class Program
{
Static void Main(string[] args)
{
var services = new ServiceCollection();
// DIコンテナへの登録
services. AddTransient<ILogger,Logger>();
var serviceProvider = services.BuildServiceProvider();
// インスタンス受け取り
var logger = serviceProvider.GetService<ILogger>();
logger.SubMessage = “追加メッセージ”;
logger.Info(“test ++ logger.SubMessage);
// もう一度インスタンスを受け取る
logger = serviceProvider.GetService<ILogger>();
logger.Info(“test ++ logger.SubMessage);
}
}

出力結果

test +追加メッセージ
test +

以上が DI コンテナ(Microsoft.Extensions.DependencyInjection)の使い方です。
今後もこの機能を使って、依存関係逆転の原則に沿った実装を行っていきたいと思います。

依存関係逆転の原則とDI(Dependency Injection)

土井
ディアシステム(株)開発一部第2課

業務の中で「依存関係逆転の原則」という原則とそれに関わるデザインパターンの「DI(Dependency Injection)」について、学ぶ機会があったので、ここに書き記します。

動作環境 C#

依存関係逆転の原則とは?

ソフトウェア設計においての概念です。
この原則には以下二つのルールがあります。

  • 高レベルのモジュールは低レベルのモジュールに依存してはならない。両者は抽象に依存すべきである
  • 抽象は詳細に依存してはならない。詳細は抽象に依存すべきである。

ここで言う、抽象は「インターフェース」、詳細は「具体的な実装」のことだと思ってください。
つまり、他クラスのメソッド等を使いたいときはそのクラスに依存するようなコードでは無く、インターフェースに依存するようなコードにしましょうということです。
そのようなプログラムを作りたいときに使われるのが、DI(Dependency Injection)です。

DIとは?

デザインパターンの一つです。プログラムの必要な部分を外部から入れてあげるような手法です。この手法を使用することによりテストが簡単になったり、コードを再利用しやすくなります。またコードの柔軟性も向上します

この二つをふわっと理解しながら、実際にプログラムで考えてみましょう。
例えばロボット作りで考えてみます。
ロボットには燃料が必要ですよね。その燃料を入れる部分をDIを使わずに作ると以下のようになります。

    public class Program
{
static void Main(string[] args)
{
Robot robot = new Robot();
Electricity electricity = new Electricity();
// ロボットに電気を渡す
robot.Start(electricity);
}
}

public class Robot
{

public void Start(Electricity electricity)
{
electricity = new Electricity();
// ロボットに燃料を注入する
electricity.SupplyElectricityPower();
}
}

public class Electricity
{
public void SupplyElectricityPower()
{
Console.WriteLine("電気を注入します");
}
}

この例では、ロボットの燃料には電気(Electricity)が使われています。
メイン部分でRobotクラスにインスタンス化したElectricityクラスを渡しています。またRobotクラスではElectricityクラスのSupplyElectricityPowerメソッドを使っています。
この状態だとRobotクラスはElectricityクラスを必ず使わないと動かないため、RobotクラスはElectricityクラスに依存している状態となっています。

次に今回のテーマであるDI(Dependency Injection)の手法を使うと次のようになります。

        public class Program
{
static void Main(string[] args)
{
IEnergy electricity = new Electricity();
// ロボットに電気を渡す
Robot robot = new Robot(electricity);
robot.Start();
}
}

public class Robot
{
private IEnergy _energy;
public Robot(IEnergy energy)
{
_energy = energy;
}
public void Start()
{
// ロボットに燃料を注入する
_energy.SupplyPower();
}
}
// 新しくインターフェースを作成
public interface IEnergy
{
public void SupplyPower();
}

// インタフェースを継承してクラスを作成
public class Electricity : IEnergy
{
public void SupplyPower()
{
Console.WriteLine("電気を注入します");
}
}

IEnergyというインターフェースを作成し、Electricityクラスはそれを継承したものとなっています。またRobotクラスはIEnergyインターフェースを継承したクラスを入れることで動作するようになっています。
このようにするとRobotクラスはElectricityクラスに依存することなく、動作させることができます。

何が良いのか?

  • コードの再利用性
    例えば、ロボットの燃料が電気ではなく、ガソリンに変わったとしましょう。
    その場合、IEnergyを継承した新しくガソリンを取り扱うクラスを用意し、その中にガソリンを注入するメソッドを用意することになります。
    public class Gasoline : IEnergy
{
public void SupplyPower()
{
Console.WriteLine("ガソリンを注入します");
}
}

DIを使っているとRobotクラスはの中身は変えずにGasolineクラスを使うことができます。

    public class Program
{
static void Main(string[] args)
{
// 電機ではなくガソリンに変更
//IEnergy electricity = new Electricity();
IEnergy gasoline = new Gasoline();

// ロボットにガソリンを渡す
Robot robot = new Robot(gasoline);
robot.Start();
}
}

// Robotクラスの中身は変えなくていい
public class Robot
{
private IEnergy _energy;
public Robot(IEnergy energy)
{
_energy = energy;
}
public void Start()
{
// ロボットに燃料を注入する
_energy.SupplyPower();
}
}
  • テストのしやすさ
    Robotクラスのテストをする際に、Gasolineクラスを必ず用意しなくても、Energyインターフェースを経由したテスト用のクラスを作成しテストが行えます。

実際にあるような状況で考えてみましょう。
例えば、500人分の従業員の給料を計算し、明細にして印刷するようなプログラムがあるとします。
必要なクラスは「従業員のデータを取得するクラス」、データをもとに「計算するクラス」、給料明細を「印刷するクラス」の3つです。この一連の動作のテストをする際に、 「印刷するクラス」に依存している状態だと、テストをするたびに給料明細を印刷することになってしまい、紙を大量に使用し、社内は深刻な紙不足に陥ります。そんなときにDIの手法を使った実装にしていれば、テスト用の出力クラスを作ることができ、印刷ではなく、PDFをローカルのフォルダに出力することで印刷したことにしてしまえば、テスト時に印刷をする必要は無くなります。(もちろん、本番に移行するときは一度印刷して確かめてくださいね。)

画像1

  • コードの柔軟性
    DIの手法を使ったプログラムの場合、インターフェースに依存している状態になるので、それを継承したプログラムであれば中身がどう変わっても、使う側に変更の必要がなく、将来的な変更や拡張がしやすくなります。
public class Program
{
static void Main(string[] args)
{
IEnergy gasoline = new Gasoline();

// ロボットにガソリンを渡す
Robot robot = new Robot(gasoline);
robot.Start();
}
}


public class Robot
{
private IEnergy _energy;
public Robot(IEnergy energy)
{
_energy = energy;
}
public void Start()
{
// SupplyPowerの処理の中身は変わっているが変更する必要がない
_energy.SupplyPower();
}
}
public class Gasoline : IEnergy
{
// 処理を変更
public void SupplyPower()
{
Console.WriteLine(“必要な燃料の量を計算します”)
Console.WriteLine("ガソリンを注入します");
  Console.WriteLine(“注入完了をお知らせします);
}
}

以上が依存関係逆転の原則とDIについてとなります。
具体的な実装に依存するのではなく、インターフェースに依存したつくりを行って、管理、テストしやすいプログラムの実装を目指していきたいと思います。

次は別記事にて、DIの手法を使う際に便利なDIコンテナというツールの使い方について紹介します。

情報セキュリティマネジメント試験(SG)合格体験記

酒井
ディアシステム(株)開発二部第1課兼管理部

こんにちは。開発二部第1課兼管理部の酒井です。
昨年の8月、無事に情報セキュリティマネジメント試験(SG)に合格することができましたので、 簡単ではありますが合格までの道のりや感想などを綴らせていただきます。

まず、情報セキュリティマネジメント試験(SG)について簡単に説明させていただきます。

資格の位置づけ

情報セキュリティマネジメントの計画・運用・評価・改善を通して組織の情報セキュリティ確保に貢献し、脅威から継続的に組織を守るための基本的なスキルを認定する試験です。
〈共通キャリア・スキルフレームワーク(CCSF)レベル2相当〉
※基本情報技術者試験と同レベル

試験内容

試験時間: 120分
問題数: 60問(科目A:多肢選択式(四肢択一)、科目B:多肢選択式)
合格点: 1000点満点中600点以上
出題分野:
●科目Aの出題内容

  • 情報セキュリティ全般
  • 情報セキュリティ管理
  • 情報セキュリティ対策
  • 情報セキュリティ関連法規
  • テクノロジ
  • マネジメント
  • ストラテジ

●科目Bの出題内容

業務の現場における情報セキュリティ管理の具体的な取組みである情報資産管理、リスクアセスメント、IT利用における情報セキュリティ確保、委託先管理、情報セキュリティ教育・訓練などのケーススタディによる出題を通して、情報セキュリティ管理の実践力を問う。

受験の動機

私が本資格を受験しようと思った動機は

  • 社内でISMSの管理者や情シス的な役割をしておりセキュリティに常に関わっているため、より一層の知識を身に付ける必要性があると感じたこと
  • 今まで学んできた自分の知識や経験を客観的に証明したいと思ったこと
    あとは、長年IT業界にはいますが今まであまり資格取得には挑戦していなかったためキャリアアップや自信をつけるために資格を取得したいという気持ちで受験することを決心しました。

勉強法

まずは、教材として書籍を購入し通勤時間などを使用して一通り書籍を読むことに集中しました。私が使用したのは「情報セキュリティマネジメント 合格教本」です。
文字ばかりの書籍は個人的に苦手なのでこの書籍は図解などが多様されていて読みやすい、問題集なども含まれており実践的な対策ができました。
その後は無料の過去問サイトを利用して、とにかく繰り返し過去問に挑戦し問題形式に慣れること、時間配分や解答の仕方を実践的に練習しました。

試験当日の流れ

私は自宅から近い試験会場で設置されたコンピュータを使用して実施するCBT方式で受験しました。
試験当日は緊張しましたが、60問で120分のため1問1問慎重に解答していっても試験時間の全ての時間を使い切らないくらいの時間で終えることができたので時間については割と余裕を持てるかと思います。
ただ、自信をもって回答できた問題と不安な問題、体感では五分五分くらいでした。
テスト全問解答直後に画面で結果を確認できるので、最後の結果確認のボタンを押す際は緊張しましたが結果は無事に合格でした。
点数は思ったよりは取れておらず、合格に対して余裕の点数とはいえませんでしたが…。

最後に

個人的にはISMSの資格取得から関わり維持を通じ学んできたこと、その他今まで日々の業務で積み重ねてきた知識や経験のおかげで全体の4割くらいは勉強前から既に理解できていたことは勉強を進める中で合格への自信になりました。
また、本資格は昔と違ってほぼ毎日のように受験ができるため、年に二度しかチャンスがないというようなプレッシャーが少ないこと、受験日や勉強のスケジュール調整などもしやすいため日々の仕事をしながらでも挑戦しやすい資格だと思います。
あとは、最新の時事問題などからも出題されることがあるため、書籍から得られる知識だけでなく試験前にネットなどで最新のセキュリティ知識や話題を事前に確認しておくことをオススメします。
情報セキュリティマネジメント試験の合格は勉強を通じてさらなる知識を得ることができたこと、また、自信にもなりこれからまた別の資格にも挑戦していきたいと思いました。

初級者向け!ユニットテストを使ってカバレッジを計測、テストの品質を向上させよう

鶴田
ディアシステム(株)開発一部第2課

今回の記事では、ユニットテストを使ってカバレッジを計測し、さらに向上させる方法を解説します。

ユニットテストとは、プログラムの一部分を独立してテストする手法で、主に早期のバグ発見が目的です。 ユニットテストを導入すると、テストが十分に行き届いているかを確認できるようになり、テスト品質の 向上にも大いに役立ちます。さらに、リファクタリングや仕様変更の際に安心してコードを修正できるようになり、 開発効率の向上にも貢献するなど、開発全体において非常に重要な役割を果たします。

カバレッジとは、テスト対象のプログラムのどの部分が実際にテストされているかを示す指標です。 カバレッジを計測することで、テストが漏れている部分が明確になり、より高品質なテストへと つなげられます。

私自身、「ユニットテストでカバレッジを計測して品質を上げる」方法は、経験の浅いプログラマーにも わかりやすく効果的だと考えています。高品質な成果物はチーム全体の意識を高めるだけでなく、 モチベーションの向上にもつながります。 経験が浅い方でも、ユニットテストを活用すればチームに十分貢献できるでしょう。

「何から手を付ければいいかわからない」という方こそ、ぜひ参考にしてみてください。

動作環境

  • 開発環境: Visual Studio 2022 Community
  • 言語: C#

今回作成するプログラムの仕様

  • 製品クラス
    品番ごとに製品の名前と価格、廃番日付を保持する

  • 在庫クラス
    品番ごとに在庫数を保持する

  • 上記2クラスのユニットテストクラス

作成手順

  1. プロジェクトを2つ追加する

    • 1つ目のプロジェクト

      • プロジェクトの種類: クラスライブラリ
      • プロジェクトの名前: ClassLibrary1
    • 2つ目のプロジェクト

      • プロジェクトの種類: MSTestテストプロジェクト
      • プロジェクトの名前: TestProject1

    もともとある Class1.cs は削除してください。

  2. ClassLibrary1 に製品クラスと在庫クラスを追加する

製品マスタは「品番、製品名、価格、廃番日付」のプロパティを、在庫マスタは「品番、在庫数」のプロパティを持つようにします。下記のコードを追加してください。

製品クラス(Product)と在庫クラス(Zaiko)

public class Product
{
/// <summary>
/// 製品CD
/// </summary>
public string ProductCode { get; }

/// <summary>
/// 製品名称
/// </summary>
public string Name { get; }

/// <summary>
/// 価格
/// </summary>
public decimal Price { get; }

/// <summary>
/// 廃番日付
/// </summary>
public string? HaibanDate { get; }

public Product(string productCode, string name, decimal price)
{
if (string.IsNullOrWhiteSpace(productCode)) throw new ArgumentException("製品CDは必須です");
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("名称は必須です");
if (price < 0) throw new ArgumentException("製品価格は必須です");
if (haibanDate != null)
{
if (!DateTime.TryParseExact(haibanDate, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out _))
throw new ArgumentException("廃番日付が有効な日付ではない");
}

ProductCode = productCode;
Name = name;
Price = price;
HaibanDate = haibanDate;
}
}

public class Zaiko
{
public string ProductCode { get; }
public int ZaikoSu { get; private set; }

public Zaiko(string productCode, int zaikoSu)
{
if (string.IsNullOrWhiteSpace(productCode)) throw new ArgumentException("製品CDは必須");
if (zaikoSu < 0) throw new ArgumentException("在庫数はゼロ以上のみ有効");

ProductCode = productCode;
ZaikoSu = zaikoSu;
}

public void AddZaikoSu(int amount)
{
if (amount <= 0) throw new ArgumentException("追加在庫数はゼロ以上のみ有効");
ZaikoSu += amount;
}
}

  1. TestProject1 にテストコードを追加する
[TestClass]
public class ProductTests
{
[TestMethod]
[Priority(0001)] // テストの優先度
public void コンストラクタ01()
{
var target = new Product("001", "商品A", 100, "20250101");
Assert.AreEqual("001", target.ProductCode);
Assert.AreEqual("商品A", target.Name);
Assert.AreEqual(100, target.Price);
Assert.AreEqual(target.HaibanDate, "20250101");
}
}
  1. 在庫クラスのテストコードを追加する
[TestClass]
public class ZaikoTests
{
[TestMethod]
[Priority(0001)]
public void コンストラクタ()
{
var target = new Zaiko("001", 10);
Assert.AreEqual("001", target.ProductCode);
Assert.AreEqual(10, target.ZaikoSu);
}
}

Visual Studio でテストを実行すると、テストが実行され、テスト結果が表示されます。

テストエクスプローラー実行結果

これで完成・・・でしょうか?

それを確認するために、カバレッジを計測してみましょう。

VisualStudio 2022 へ Fine Code Coverage を導入する

  1. カバレッジを計測するために、以下のツールを使用します。

coverlet.console NuGet パッケージマネージャーより、coverlet.console をインストールします。

Fine Code Coverage https://marketplace.visualstudio.com/items?itemName=FortuneNgwenya.FineCodeCoverage

拡張機能より、Fine Code Coverage を検索してインストールしてください。

Visual Studio を再起動すると、ツールバーに Fine Code Coverage が表示されます。

拡張機能→Fine Code Coverage→設定 より色の設定を確認してください。以下の設定がおすすめです。

Fine Code Coverageおすすめ

Fine Code Coverage についての詳しい解説は、以下のサイトも参考になります。

カバレッジ100%へ

先ほどまで作成したソリューションを再度開いて、テストを実行します。

在庫クラスを確認してみましょう。

カバレッジ結果

オレンジや赤色の背景色がコード内に表示されています。 これは、赤はテストが実行されていない行(C0カバレッジ未達成)、オレンジは分岐の一部がテストされていない行(C1カバレッジ未達成)を示しています。

この行が「テストの漏れ」です。放置してはいけません。テストコードを追加します。

テストコードを以下の通り追加してください。

[TestClass]
public class ProductTests
{
[TestMethod]
[Priority(0001)] // テストの優先度を設定
public void コンストラクタ()
{
var target = new Product("001", "商品A", 100, "20250101");
Assert.AreEqual("001", target.ProductCode);
Assert.AreEqual("商品A", target.Name);
Assert.AreEqual(100, target.Price);
Assert.AreEqual(target.HaibanDate, "20250101");
}

[TestMethod]
[Priority(0002)]
public void コンストラクタ_Exception_ProductCode()
{
Assert.ThrowsException<ArgumentException>(() => new Product("", "商品A", 100));
}
/*
* 以下の通りにも記述可能です。
* ただ、カバレッジツールの制約上最後の } が未テスト行として認識されます。
*
[Priority(0002)]
[ExpectedException(typeof(ArgumentException))] // 例外が発生することを確認するテストコード
public void コンストラクタ_Exception_ProductCode()
{
var target = new Product("", "商品A", 100);
}
*/

[TestMethod]
[Priority(0003)]
public void コンストラクタ_Exception_Name()
{
Assert.ThrowsException<ArgumentException>(() => new Product("001", "", 100));
}

[TestMethod]
[Priority(0004)]
public void コンストラクタ_Exception_Price()
{
Assert.ThrowsException<ArgumentException>(() => new Product("001", "商品A", -1M));
}

[TestMethod]
[Priority(0005)]
public void コンストラクタ_Exception_HaibanDate()
{
Assert.ThrowsException<ArgumentException>(() => new Product("001", "商品A", 100, "100"));
}
}

public class ZaikoTests
{
[TestMethod]
[Priority(0001)]
public void コンストラクタ()
{
var target = new Zaiko("001", 10);
Assert.AreEqual("001", target.ProductCode);
Assert.AreEqual(10, target.ZaikoSu);
}

[TestMethod]
[Priority(0002)]
public void コンストラクタ_Exception_ProductCode()
{
Assert.ThrowsException<ArgumentException>(() => new Zaiko("", 10));
}

[TestMethod]
[Priority(0003)]
public void コンストラクタ_Exception_ZaikoSu()
{
Assert.ThrowsException<ArgumentException>(() => new Zaiko("001", -1));
}

[TestMethod]
[Priority(0101)]
public void AddZaikoSu_Exception()
{
var target = new Zaiko("001", 10);
Assert.ThrowsException<ArgumentException>(() => target.AddZaikoSu(-1));
}

[TestMethod]
[Priority(0102)]
public void AddZaikoSu()
{
var target = new Zaiko("001", 10);
target.AddZaikoSu(1);
Assert.AreEqual(11, target.ZaikoSu);
}
}

これですべての行がテストされました。 Fine Code Coverage で確認してみましょう。

表示→その他のウィンドウ→Fine Code Coverage を選択します。

カバレッジ結果

確かにカバレッジの欄には燦然と輝く100%の文字がっ!

これで、テストの漏れがなくなりました。

もちろん、カバレッジを100%にしたからといって、バグが完全になくなるわけではありません。しかし、ユニットテストを書く過程でコードを深く理解できるようになり、結果として品質向上につながります。

ぜひ、ユニットテストを書いてカバレッジを活用してみてください。そうすることで、チーム全体の品質意識が高まり、開発効率も向上するはずです。

ISO20022とは何ぞや?(外国送金編)

笹木
ディアシステム(株)開発一部第4課

開発1部第4課の笹木です。
これまで金融分野(銀行、信用金庫、損保会社系)を中心に様々なプロジェクトに携わらせて頂いておりますが、現在最も注目されているISO20022(外国送金)について触れてみたいと思います。

ISO20022とは?

ISO20022は金融通信メッセージの標準化を目指すために、ISOが定める国際規格を指します。
具体的には、これまで連携対象となった関連項目を固定長で定義し、「MT方式」で金融通信メッセージをSWIFT(※)を経由して世界各国間でやり取りしていましたが、これに代わって「XML形式」で金融通信メッセージのやり取りを行う形に変更されます。
(※)SWIFT:国際銀行間通信協会(Society for Worldwide Interbank Financial Telecommunicationの略でベルギーに本部を置く非営利組織です。)

画像1

現行システムの問題点は?

外国送金には仕向/被仕向送金があります。
世界各国毎にフォーマットが異なるため、送金のやり取りをする際にフォーマット変換が行われ、またフリーフォーマットでかつ古いため、名前、住所などの情報のチェックも機能せず、増加する送金情報への対応が複雑化し、業務効率化の妨げになっていました。

画像2

ISO20022の特徴およびメリット

可変長で柔軟に金融通信メッセージの送付が可能になることにより、各金融機関がコストを削減し、リスクにさらされる可能性を抑えながらトランザクションの効率化を向上させることが可能といわれており、以下のメリットが想定されます。

  • ビジネスプロセス通信用の共通言語が出来ます。
  • フォーマットが統一されることで大量の送金データを迅速・正確にシステム処理が可能となり、コンプライアンス対応の向上が見込まれます。
  • 他のプロトコルとの運用性が相互間で向上します。
     ➡ 海外送金だけではなく、国内取引(各金融機関 - 日銀間のやり取り等)についてもISO20022移行が行われています。
  • アルファベット以外の非ラテン文字(日本語ETC)についてもサポートが行われるようになります。
  • 送金に関する項目や文字数も拡充されます。
     ➡AML(アンチ・マネーロンダリング)の規制、反社会的勢力の強化や被仕向の入金送金失敗(口座不明等)などの減少が見込まれます。

画像3

最後に

先述の通り、外国送金について触れてみました。ISO20022についてデメリットがないかと言えば、それはNoであると考えます。少なからず、項目の構造化といった対処の検討も必要になると推測しますが、絶対やらないという選択肢はないと思いますので、デメリットは深く考えないようにした方がいいですね。最近のニュースでも円高/円安、金利など為替に関する様々な動きの背景や理由が語られています。為替相場を動かす要素は政治・経済や国際関係まで様々なのでこの辺の情報もしっかり注視しないといけませんね。

未経験から始める
システムエンジニア

一生モノのITスキルを身につけよう

あなたの経験とスキルを
ディアシステムで発揮してください!