TomoProgの技術書

底辺プログラマーが達人プログラマーになるまで

ソフトウェアテスト技法を学んでみた Part1 【そもそもソフトウェアテストって何だろう?】

皆さん
こんにちは、こんばんは
TomoProgです。

こちらをメインに書くとか言っておきながら、ほぼ書いていませんでした。

というのも、こちらをメインに書くと言ったすぐにQiitaの会社アカウントが出来たので、結局書くときはQiitaに書いてたという言い訳をかましておきます。

それはさておき、会社でudemyのソフトウェアテスト技法についての講座を購入したので、今日からソフトウェアテスト技法について学んだことをこのブログに書いていこうかと思います。
講座のリンクは下に貼っておきます。

Part1は「そもそもソフトウェアテストって何だろう?」ということで書いていこうと思います。

それでは頑張っていきましょう。

ソフトウェアテストって何だろう?

テスト工程は結構経験しているのですが、改めて言われると難しいですね。

私はひねり出してこれくらいの言葉しか出てきませんでした。
「ソフトウェアをテストして、正しく動いているのかどうかを確認すること。」
すごく抽象的な気がします・・・。

今回受講した講座ではソフトウェアテストを次のように定義しています。
ある特定の条件下においてソフトウェアの振る舞いを記録し、その記録を期待される結果と比較・検証するプロセスである

中々難しいですね。もう少し簡単に次のようにも言ってくれています。
開発者の意図したとおりにプログラムが動くのかをいくつかの具体的なテストケースを用いて検証し、期待通りに動かない箇所(バグ)をへらすプロセスである

こう聞くと、私達プログラマーが日頃やっているテスト工程だと思えてきました。

ソフトウェアテストはなぜ行うのだろうか

これもまた難しい質問です。

何年も経験してくるとそもそも何なのかとかなぜやっているのかという最も重要なことを考えなくなってきて、機械的にやってしまいがちですが、一度考えて見ようと思います。

  • ソフトウェアが正しく動いていることを保証したい。
  • リリースされたソフトウェアにバグがあれば、ユーザーが減ってしまう。
  • バグがあると顧客から会社への信頼がなくなる。

とりあえず思いついたことを上げてみたらこんなくらいしか出てきませんでした・・・。

今回受けた講座では、
ソフトウェアテストはソフトウェアの品質を上げるために行うもの
だと言っています。

どうやって品質を上げていくのか

基本的にはテストケースが多ければ多いほど安心できるため、複数のテストケースを用意することで品質を上げていきます。
しかし、考えられるすべての組み合わせをテストすることは現実的に不可能なので、少しでも多くのバグを見つけられるようにテストケースを用意していく必要があります。

まとめ

ソフトウェアテストについて講義資料を見ながら改めて考えてみました。
当たり前にやっていることをそもそも何なの?と考えると意外と答えられないものです。

次は具体的な技法について学んで書ければと思います。

それではまた。

TomoProg

TomoProg (TomoProg) · GitHub

TomoProg (@tomoprog_xxx) | Twitter

  • はじめてのソフトウェアテスト技法【全てのエンジニアが知るべき最重要テスト技法を、丁寧な解説と演習問題で身につけよう】

https://www.udemy.com/course/software-test-design/

【C#】呼び出し元のメソッド名を渡したいときはCallerMemberNameAttributeが便利

皆さん
こんにちは、こんばんは
TomoProgです。

久しぶりのブログ投稿です。
最近はQiitaに書いていましたが、このブログも少しですが毎日の閲覧があるので、
このまま何も書かないはもったいない!
ということで、これからはこちらをメインに更新していこうかと考えています。

今回はC#CallerMemberNameAttributeを使うことで、
呼び出し元のメソッド名を簡単に渡せることを知ったため、
その方法について書こうと思います。

それでは頑張っていきましょう。

題材

以下のようなログメッセージと呼び出し元のメソッド名を出力するロガーを題材に CallerMemberNameAttributeを試していきます。

class Logger
{
    /// <summary>
    /// ファイルパス
    /// </summary>
    public string FilePath { get; private set; }
    public Logger(string filePath)
    {
        FilePath = filePath;
    }

    /// <summary>
    /// ログ出力
    /// </summary>
    /// <param name="msg">メッセージ</param>
    /// <param name="methodName">呼び出し元のメソッド名</param>
    public void Write(string msg, string methodName)
    {
        using (var sw = new StreamWriter(FilePath, true, Encoding.UTF8))
        {
            sw.WriteLine($"{msg} called by [{methodName}]");
        }
    }
}

CallerMemberNameAttributeを使わない場合

まずはCallerMemberNameAttributeを使わない場合を考えてみます。
メソッド名はSystem.Reflection.MethodBaseから取得できるため、そちらを使ってログを書いてみます。

class Program
{
    static Logger logger = new Logger("sample.log");

    static void Main(string[] args)
    {
        Method1();
        Method2();
    }

    static void Method1()
    {
        logger.Write("ログメッセージ1", MethodBase.GetCurrentMethod().Name);
    }

    static void Method2()
    {
        logger.Write("ログメッセージ2", MethodBase.GetCurrentMethod().Name);
    }
}

GetCurrentMethodメソッドは現在実行中のメソッドを表すMethodBaseオブジェクトを返します。
そのオブジェクトのNameプロパティにメソッド名が入っているため、それを利用します。

実行後のsample.logを確認すると

ログメッセージ1 called by [Method1]
ログメッセージ2 called by [Method2]

うまくログを書くことができました。

CallerMemberNameAttributeを使った場合

それでは次にCallerMemberNameAttributeを使って同じようにログを書いてみます。

Loggerクラス

class Logger
{
    // -- 省略 --

    /// <summary>
    /// ログ出力
    /// </summary>
    /// <param name="msg">メッセージ</param>
    /// <param name="methodName">呼び出し元のメソッド名</param>
    public void Write(string msg, [CallerMemberName]string methodName = null)
    {
        using (var sw = new StreamWriter(FilePath, true, Encoding.UTF8))
        {
            sw.WriteLine($"{msg} called by [{methodName}]");
        }
    }
}

Main

class Program
{
    // -- 省略 --

    static void Method1()
    {
        logger.Write("ログメッセージ1");
    }

    static void Method2()
    {
        logger.Write("ログメッセージ2");
    }
}

LoggerクラスのWriteメソッドのmethodName引数にCallerMemberNameAttributeを追加しました。
また、呼び出し側の第2引数を削除しました。

実行後のsample.logを確認すると

ログメッセージ1 called by [Method1]
ログメッセージ2 called by [Method2]

CallerMemberNameAttributeを使わないときと同じログを書くことができました。

このように、CallerMemberNameAttributeを使うと逐一メソッド名を渡さずとも、メソッド名を受け取る事ができるため便利です。
ちなみに第2引数はデフォルト引数のため、メソッド名を渡してあげることもできます。

まとめ

CallerMemberNameAttributeを使うとメソッド名を渡さずともメソッド名を受け取る事ができる

今回のようなログ出力を自作するときなんかに使えそうかなと思います。

それではまた。

TomoProg

お名前.comで独自ドメインを購入してみて気づいた勘違い

皆さん
こんにちは、こんばんは
TomoProgです。

最近はこのブログよりもQiitaの更新がメインになっていましたが、
久しぶりにこちらのブログ更新したいと思います。

前から開発用に独自ドメインが欲しいと思っていて、
ついに昨日、お名前.comさんで独自ドメインを購入しました。

そのときに気づいた僕の勘違いについて書こうと思います。

一つのドメイン名に対してホスト名は複数登録できる

僕はホスト名一つごとにドメイン名を取得するものだと勘違いしており、
結構お金かかるなと思いこんでいました。

しかし、お金がかかるのはあくまでドメイン名に対してであり、
ホスト名は複数件登録できます。

例えばwww.tomoprog-sample.workというアドレスが欲しいと思ったとします。

この場合、お名前.comなどのドメイン登録業者で取得するのは
tomoprog-sample.workを取得します。

ドメイン名より前にある文字列はお名前.comでは最大196件登録できるようです。

help.onamae.com

そのため、.tomoprog-sample.workを取得すれば

  • website1.tomoprog-sample.work
  • website2.tomoprog-sample.work
  • website3.tomoprog-sample.work

といった複数のアドレスを使用することができます。

まとめ

ホスト名が複数登録できるということに気づけました。

.workであれば今ならお名前.comで1円(更新料990円)/ 年で使えます。

個人開発などに手軽に使えるので、独自ドメインを取得してみようという方は
是非チャレンジしてみてください。

それではまた。

TomoProg

GitHub
TomoProg (TomoProg) · GitHub

Twitter
TomoProg (@tomoprog_xxx) | Twitter

アクセス修飾子(public, private, protected)について考えてみる

皆さん
こんにちは、こんばんは
TomoProgです。

ブログを更新すると書いてから、すでに半月以上経過していました。
更新頻度はあまり気にせず、ゆるりと書いていこうと思います。

今回はアクセス修飾子がなんで必要なのかを考えてみました。

それでは頑張っていきましょう。

なぜアクセス修飾子が必要なの?

自分なりの見解ですが、アクセス修飾子を適切に設定することにより、

  1. 外部からアクセスされてはまずい変数、メソッドへのアクセスを遮断できる
  2. 保守を容易にする

以上の2つ、特に2つめが重要だと考えています。

例えば、以下のようなプログラムで考えてみます。

using System;
using System.Collections.Generic;

public class Book
{
    public string Title { get; private set; }
    
    public Book(string title)
    {
        Title = title;
    }
}

public class BookStore
{
    private List<Book> _stock;    // 在庫
    
    public BookStore()
    {
        _stock = new List<Book>();
    }
    
    // 在庫補充
    public void Restock(Book book)
    {
        _stock.Add(book);
    }
    
    // 在庫表示
    public void PrintStock()
    {
        foreach(Book b in _stock)
        {
            Console.WriteLine(b.Title);
        }
    }
}

public class Hello
{
    public static void Main()
    {
        var store = new BookStore();

        var rubyBook = new Book("Hello Ruby World");
        var csBook = new Book("Hello c# World");
        var pythonBook = new Book("Hello Python World");
        
        store.Restock(rubyBook);
        store.Restock(csBook);
        store.Restock(pythonBook);
        
        store.PrintStock();
    }
}

本の在庫を補充して在庫を表示するというプログラムです。

BookStoreクラスの_stock変数がもしpublicだった場合、以下のようなことが可能になります。

public class BookStore
{
    public List<Book> _stock;    // 在庫(publicに変えた)
    ・・・
}

public class Hello
{
    public static void Main()
    {
        var store = new BookStore();
        var rubyBook = new Book("Hello Ruby World");
        var csBook = new Book("Hello c# World");
        var pythonBook = new Book("Hello Python World");
        
        store.Restock(rubyBook);
        store.Restock(csBook);
        store.Restock(pythonBook);
        store._stock.Add(new Book("Hello C++ World"));  // 強引に在庫を増やす
        
        store.PrintStock();
    }
}

Restockメソッドを使わずに在庫に直接アクセスして商品を追加できてしまいました。
追加に限らず、削除や編集も容易にできてしまいます。

また、在庫を変更している箇所を探そうと思った場合、メソッドだけでなく、_stock変数も考慮して探さなければいけません。

アクセス修飾子を適切に設定することで、影響範囲を限定し、保守しやすいコードを心がけていきましょう。

それではまた。

TomoProg

GitHub
TomoProg (TomoProg) · GitHub

Twitter
TomoProg (@tomoprog_xxx) | Twitter

そろそろブログ再開のフラグを立てておく

皆さん
こんにちは、こんばんは
TomoProgです。

気がつけば、前回の記事から1年以上が経過しているではないですか。

正直なところ書かないとなと思いつつも全然やる気が起きず、
放ったらかしになっていました。

これだけ放っておいたら、もはや誰もこのブログ見ないだろと
思っていたのですが、PVは1年前より伸びているという奇跡。

Pythonが日本でも流行りだしたことが原因なのかなと考えています。

このブログが少しでも皆さんの役に立っているならありがたいことです。

せっかく見て頂いてる人もいますし、
自分の勉強も兼ねてそろそろブログ再開していこうと思います。

前まではPythonの記事が多かったですが、
1年前に会社が変わってRubyを使うようになったので、
Rubyに関する記事が多くなりそうです。

それではまた。

TomoProg

GitHub
TomoProg (TomoProg) · GitHub

Twitter
TomoProg (@tomoprog_xxx) | Twitter

Rubyでオセロを作ってみた

皆さん
こんにちは、こんばんは
TomoProgです。

今回はRubyを使ってオセロを作ってみたので、
その工程を書いておきます。

それでは頑張っていきましょう。

オセロの手順を整理する

まずはオセロを作る前に簡単にオセロの手順を整理しておきます。

  1. 盤面を表示
  2. 石を置く
  3. 裏返す
  4. 相手のターンに移る

大体こんな手順でしょう。
この手順をお互いが石が置けなくなるまで延々と繰り返すようにすれば、オセロを作れそうです。

盤面の表示

手順も頭に入れたので、早速作っていきます。

まずは、盤面の表示ですが、盤面がないと表示も出来ないので盤面を作ります。

オセロの盤面である8×8のマスを作成する関数です。

BLANK = "" # 置き石無し
BLACK = "" # 黒
WHITE = "" # 白
WALL  = "" # 終端
MAX_ROW = 10 # 行
MAX_COL = 10 # 列

def make_field
  field = []

  # 置き石無しの状態に初期化
  MAX_ROW.times do
    row = []
    MAX_COL.times do
      row << BLANK
    end
    field << row
  end

  # 壁を作る
  0.upto(MAX_COL - 1) do |i|
    field[0][i] = WALL
    field[MAX_ROW - 1][i] = WALL
  end
  0.upto(MAX_ROW - 1) do |i|
    field[i][0] = WALL
    field[i][MAX_COL - 1] = WALL
  end

  # 中央に石を置く
  field[4][4] = WHITE
  field[5][5] = WHITE
  field[4][5] = BLACK
  field[5][4] = BLACK
end

あとは作った盤面(field変数)を表示するだけです。
field変数を一行ずつ表示することで、盤面を表示します。

def print_field
  for i in 0..MAX_ROW - 1
    print i.to_s
    row = field[i]
    row.each do |stone|
      print stone
    end
    print "\n"
  end
end

これで盤面の表示は完了です。

石を置く

次に石を置く処理を書いていきます。

rubyではgetsを使えばキーボードからの入力を取得できます。
行と列をカンマ区切りで入力するということにしましょう。

# キーボードからの入力を取得
put_pos = gets

# 改行を取り除き、カンマで区切る
put_pos = put_pos.chomp.split(",")

# 行、列を取得
row = put_pos[0]
col = put_pos[1]

# 指定された行、列から盤面に石を置く
field[row][col] = my_stone

裏返す

さて、オセロの一番重要な部分である裏返す処理を作ります。

手順としては、

  1. 置いた石の周りの相手の石を探す
  2. 相手の石を見つけた方向に自分の石を探す
  3. 自分の石の間にある相手の石をすべて自分の石にする

この手順で石を裏返していきます。

# 時計回りに方向を定義
directions = [[-1,0], [-1,1], [0,1], [1,1], [1,0], [1,-1], [0,-1], [-1,-1]]

# 置いた石の周りに相手の石があるか確認
directions.each do |direction|
  reverse_pos = []
  reverse_row = put_row + direction[0]
  reverse_col = put_col + direction[1]

  # 相手の石でない場合は次の方向を確認
  if field[reverse_row][reverse_col] != enemy
    next
  end

  reverse_flag = false
  reverse_pos << [reverse_row, reverse_col]

  # 見つけた方向を捜査していく
  while true
    reverse_row += direction[0]
    reverse_col += direction[1]
    if field[reverse_row][reverse_col] == enemy
      reverse_pos << [reverse_row, reverse_col]
    elsif field[reverse_row][reverse_col] == my_stone
      reverse_flag = true
      break
    else
      break
    end
  end

  # 間にあった相手の石を裏返す
  if reverse_flag
    reverse_pos.each do |pos|
      field[pos[0]][pos[1]] = my_stone
    end
  end
end

これで裏返しの処理は完了です。

あとは交互における場所がなくなるまで繰り返すだけです。

完成!

上記の処理をまとめて、オセロ完成です!
github.com

今回はRubyでオセロを作ってみました。

もしよければ遊んでみてください。

それではまた。

TomoProg

GitHub
TomoProg (TomoProg) · GitHub

Twitter
TomoProg (@tomoprog_xxx) | Twitter

Happy Birthdayプログラムで無事に誕生日を迎えました。

皆さん
こんにちは、こんばんは
TomoProgです。

今年の誕生日はコンピュータに一番最初に祝ってもらったので、
その道程を書いておきます。

それでは頑張っていきましょう。

40分前

あ、明日誕生日だ。
完全に忘れていた誕生日をふと思い出す。

しかし、誕生日になる瞬間を祝ってくれる人はいない。

「コンピュータに祝ってもらおう」

寂しい40分が開幕した。

35分前

どうやって祝ってもらおう。

とりあえず、カウントダウンして欲しい。
あと、曲も歌って欲しい。

ということで、

  1. 誕生日までのカウントダウン
  2. 誕生日曲の再生

をしてくれるプログラムを作る。

30分前

コーディング開始。

まずはカウントダウンの部分を実装する。

C#ではDateTime型を使用すれば
簡単に日時を取得することができる。

// 自分の誕生日を指定
DateTime birthday = new DateTime(2017, 3, 6);
// 現在時刻から誕生日までの残りの日時を計算
TimeSpan ts = birthday - DateTime.Now;

計算結果はTimeSpan型として保持する。
TimeSpan型のプロパティを参照すれば残りの日時を確認できる。

// 出力例:1Days 10:20:30.400
Console.WriteLine("{0}Days {1}:{2}:{3}.{4}"
    , ts.Days
    , ts.Hours
    , ts.Minutes
    , ts.Seconds
    , ts.Milliseconds);

これでカウントダウンは完了。

20分前

誕生日曲の再生部分を実装する。

まずは、音声ファイルを手に入れるため、
以下のサイトを利用し、YouTubeから音声ファイルを抽出。
www.onlinevideoconverter.com

ちなみに誕生日曲はコブクロの「Happy Birthday」を選曲。
www.youtube.com

C#にはwavファイルであれば簡単に再生できる
SoundPlayerというクラスが用意されている。

// 再生するwaveファイルを指定
SoundPlayer player = new SoundPlayer("happybirthday.wav");
// 再生する
player.PlaySync();

これでどちらの機能も実装が完了。
コードは全部で40行ほど。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Media;

namespace HappyBirthday
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime birthday = new DateTime(2017, 3, 6);
            SoundPlayer player = new SoundPlayer("../../happybirthday.wav");

            while (true)
            {
                Console.WriteLine("■■ Happy Birthday Countdown ■■");
                TimeSpan ts = birthday - DateTime.Now;
                if (ts.TotalMilliseconds < 0)
                {
                    break;
                }

                Console.WriteLine("{0}Days {1}:{2}:{3}.{4}"
                    , ts.Days
                    , ts.Hours.ToString().PadLeft(2, '0')
                    , ts.Minutes.ToString().PadLeft(2, '0')
                    , ts.Seconds.ToString().PadLeft(2, '0')
                    , ts.Milliseconds);

                Thread.Sleep(30);
                Console.Clear();
            }

            Console.WriteLine("Happy Birthday!!");
            player.PlaySync();
        }
    }
}

5分前

カウントダウンプログラムを起動させる。

とりあえず、カウントダウンはうまくいっている様子。

今年あった出来事を心の中で振り返る。

1分前

テストしていないため、音声鳴らなかったらどうしようと不安になる。

5秒前

ラスト5秒!
f:id:TomoProg:20170306010351p:plain

Happy BirthDay!!

誕生日!
f:id:TomoProg:20170306010248p:plain
この時点でコブクロの「Happy Birthday」も流れる。

まとめ

  • 1歳老けた。
  • 寂しさが込み上げてきた。

こんな感じで40分でHappy Birthdayプログラムを作ってみました。

うん。
誰か祝ってください。

TomoProg

GitHub
TomoProg (TomoProg) · GitHub

Twitter
TomoProg (@tomoprog_xxx) | Twitter