20201011のC#に関する記事は6件です。

Azure Functionsでアクティビティ関数を直接デバッグする

Azure Functionsで大規模なコードを開発していると、デバッグがめんどくさい・・・
HTTPトリガーをコールしてから目的の関数にたどり着くまで待ち時間が長いことも多い。
なので、直接アクティビティ関数をデバッグできないか調査。
MSの以下のドキュメントをを参考にした。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-manually-run-non-http

「HTTP によってトリガーされない関数に HTTP 要求を介してアクセスするには、フォルダー admin/functions から要求を送信する必要があります。」とあるので、デバッグ時もそうすればいいのでは?という単純な思考でデバッグしてみる。

1. デバッグする関数

ソースは以下。(クリックするとソースが表示されます)
    public static class Function2
    {
        [FunctionName("Function2")]
        public static async Task<List<string>> RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var outputs = new List<string>();

            // Replace "hello" with the name of your Durable Activity Function.
            outputs.Add(await context.CallActivityAsync<string>("Function2_Hello", "Tokyo"));
            outputs.Add(await context.CallActivityAsync<string>("Function2_Hello", "Seattle"));
            outputs.Add(await context.CallActivityAsync<string>("Function2_Hello", "London"));

            // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
            return outputs;
        }

        [FunctionName("Function2_Hello")]
        public static string SayHello([ActivityTrigger] IDurableOrchestrationContext inputs, ILogger log)
        //public static string SayHello([ActivityTrigger] string name, ILogger log)
        {
            var name = inputs.GetInput<string>();
            log.LogInformation($"Saying hello to {name}.");
            return $"Hello {name}!";
        }

        [FunctionName("Function3")]
        public static string SayHello3([ActivityTrigger] string inputString, ILogger log)
        {
            var input = JsonConvert.DeserializeObject<Function3Input>(inputString);

            var name = input.Name;
            log.LogInformation($"Saying hello to {name}.");
            return $"Hello {name}!";
        }

        [FunctionName("Function2_HttpStart")]
        public static async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req,
            [DurableClient] IDurableOrchestrationClient starter,
            ILogger log)
        {
            // Function input comes from the request content.
            string instanceId = await starter.StartNewAsync("Function2", null);

            log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

            return starter.CreateCheckStatusResponse(req, instanceId);
        }

        public class Function3Input
        {
            public string Name { get; set; }
            public string Age { get; set; }
        }
    }

オーケストレーター関数を最初に作成したときのサンプルに毛をはやした(Functions3とFunction3Inputを足した)もの。
Functions2_HelloとFunctions3を直接呼び出せるか確認する。

2. 結果

Functions2_Helloは呼び出せなかった。Function3は呼び出せた。
以下のようにリクエストするとFunction3が呼びだる。

POST /admin/functions/Function3 HTTP/1.1
HOST: localhost:7071
content-type: application/json
content-length: 56

{
  "input": "{\"name\": \"aaa\", \"age\": \"20\" }"
}

アクティビティ関数の引数にわたる文字列は「input」という固定のプロパティにJSON形式の文字列を渡せば、それがそのまま渡るみたい。
上記の場合、Function3の引数には「{"name":"aaa","age":"20"}」という文字列が渡される。

ちなみに、Function2_Helloのほうを同じように呼び出すと、リクエストは成功(202 Accepted)するが、デバッグコンソール上でエラーが表示される。

POST /admin/functions/Function2_Hello HTTP/1.1
HOST: localhost:7071
content-type: application/json
content-length: 54

{
  "input": "{\"inputs\" : {\"name\": \"aaa\"} }"
}
System.Private.CoreLib: Exception while executing function: Function2_Hello. 
Microsoft.Azure.WebJobs.Host: Exception binding parameter 'inputs'. 
Newtonsoft.Json: Could not create an instance of type Microsoft.Azure.WebJobs.Extensions.DurableTask.IDurableOrchestrationContext. 
Type is an interface or abstract class and cannot be instantiated. 
Path 'inputs', line 1, position 10.

エラー見ると「インタフェースクラスなのでインスタンス化できません」って言われてるのでなんか無理そうだし調べるのめんどくさそうだしいいやってことで調べるのやめた()

もし呼び出せた方いたらやり方教えてください。

今日は以上~。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ASP.NET Core 3.1 MVC による Microsoft アカウント認証のロール ベース アクセス制御

はじめに

前回、以下の記事で、ASP.NET Core 3.1 MVC による Microsoft アカウント認証の実装手順を説明しました。

認証ができたら、次は、ロール ベース アクセス コントロール、つまり、コントローラーやアクションに、[Authorize(Roles="admin")] みたいな属性をつけて、特定のグループに所属している場合のみにアクセスを許可するといったことをやりたいのですが、Microsoft アカウント認証のみを対象としたときは、やり方が見つかりませんでした。
Azure Active Directory アカウントでは、以下の方法で、roles や groups をクレームに含めて、アカウントのロールとして管理、制御できます。

これらの方法の様に所属しているロールをクレームに含めることができれば、ロールの管理が容易となるのですが、今回は、アプリ側で、ロールを定義して ASP.NET Core のポリシーで認可を制御する方法を説明します。

ロールの定義

今回は、appSettings.json にロールと所属するアカウントを定義していきます。
ここでは、Admins を配列として定義して、その値に、所属するアカウントとして、対象アカウントのオブジェクト ID "http://schemas.microsoft.com/identity/claims/objectidentifier" の値を定義しています。

appSettings.json

{
  "Admins": [
    "00000000-0000-0000-0000-000000000000",
    "00000000-0000-0000-0000-000000000000",
    "00000000-0000-0000-0000-000000000000"
  ]
}

"http://schemas.microsoft.com/identity/claims/objectidentifier" の値は、実際に Microsoft アカウントを認証させて、確認しました。
以下の記事の手順で、Microsoft アカウント認証を行い、対象アカウントのオブジェクト ID を取得して、設定を行ってください。

ASP.NET Core 3.1 MVC による Microsoft アカウント認証#動作確認
http://schemas.microsoft.com/identity/claims/objectidentifier

ポリシー ベースでの認可

Startup.ConfigureServices でポリシーを定義、追加します。
以下のコードでは、先ほど定義した appSettings.json の Admins 配列にオブジェクト ID が含まれているかを評価するポリシー "AdminOnly" を定義、追加しています。
AuthorizationPolicyBuilder.RequireAssertion を使うとクロージャーで簡単にポリシーを定義できます。

Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd");
    services.AddControllersWithViews().AddMicrosoftIdentityUI();

    services.Configure<AuthorizationOptions>(options =>
    {
        options.AddPolicy("AdminOnly", policy =>
            policy.RequireAssertion(context =>
            {
                if (!context.User.Identity.IsAuthenticated)
                {
                    return false;
                }
                // オブジェクト ID の取得
                var oid = context.User.Claims.FirstOrDefault(claim => claim.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier");
                // appSettings.json の Admins にオブジェクト ID が含まれているか評価
                return this.Configuration.GetSection("Admins").Get<string[]>().Contains(oid?.Value);
            }));
    });
}


コントローラーへのポリシーの設定

あとは、コントローラーやアクションに [Authorize] 属性とともに、ポリシーを指定します。
[Authorize(Policy = "ポリシー名")] でポリシー名を指定します。
AuthorizeAttribute のコンストラクターで、ポリシー名を指定できるので、[Authorize("ポリシー名")] とすることもできます。

HomeController.cs
[Authorize(Policy = "AdminOnly")]
public IActionResult Edit()
{
    return View();
}

[Authorize("AdminOnly")]
public IActionResult Delete()
{
    return View();
}

まとめ

ASP.NET Core 3.1 MVC による Microsoft アカウント認証 の手順で、Azure Active Directory にアプリを登録する際に、[サポートされているアカウントの種類] を "個人用 Microsoft アカウントのみ" とした時、つまり、Microsoft アカウント認証を選択した場合は、ロールの定義や定義したロールをクレームに含めることができないようです。
よって、今回は、ロールの定義をアプリ側で行い、ユーザー アカウントがロールに含まれていることを検証するポリシーを定義し、ポリシー ベースの認可で、ロール ベース ライクなアクセス制御を実現してみました。
実は、ASP.NET Core のミドルウェアで、クレームを追加して [Authorize(Roles="ロール名")] といったこともできるのですが、セキュリティ面でどうなのかという点と、ポリシー ベースの方が、色々と応用が利くと考えたので、今回は、ポリシー ベースでの説明としました。

参考サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[.NET][C#]NUnitのCollectionAssertアサーションメソッド一覧

はじめに

前回、NUnitのStringAssertクラスのアサーションメソッドを一通り試したので
今回はCollectionAssertクラスのアサーションメソッドを一通り試してみる。

実施環境

.NET:3.1.401
C#:8.0
NUnit:3.12.0

CollectionAssertクラス

NUnitのClassicModelのアサーションクラスの1つ。
クラス名の通り、コレクション(IEnumerable実装クラス)を検査する。

1. AllItemsAreInstancesOfType

コレクションが保持する要素の型が全て同じかどうかを検査する。
第1引数のコレクションが保持する要素が、第2引数に指定した型か、指定した型のサブクラスのみであればテストOKと判定される。

C#のコレクションは、保持する型をジェネリクスで指定するICollection<T>の実装クラスを使用するのが一般的なので
このアサーションを使用する機会はあまり無いだろう。

public class Target {}
public class TargetSub : Target {}

public class CollectionAssertTest
{

    [TestCase]
    public void AllItemsAreInstancesOfTypeTest()
    {
        // Test OK.
        CollectionAssert.AllItemsAreInstancesOfType(
            new ArrayList() {"hoge", "fuga", "hage"}, Type.GetType("System.String"));

        // Test NG.
        CollectionAssert.AllItemsAreInstancesOfType(
            new ArrayList() {"hoge", "fuga", new Target()}, Type.GetType("System.String"));

        // Test OK.
        CollectionAssert.AllItemsAreInstancesOfType(
            new ArrayList() {new Target(), new Target(), new TargetSub()}, Type.GetType("AssetionsTest.Target"));
    }
}

2. AllItemsAreNotNull

コレクションが保持する要素の中にnullが含まれるかどうかを検査する。
コレクションが空の場合はテストOKとなる。

[TestCase]
public void AllItemsAreNotNullTest()
{
    // Test OK.
    CollectionAssert.AllItemsAreNotNull(
        new List<string>() {"hoge", "fuga", "hage"});

    // Test NG.
    CollectionAssert.AllItemsAreNotNull(
        new List<string>() {"hoge", null, "hage"});

    // Test OK.
    CollectionAssert.AllItemsAreNotNull(
        new List<string>() {});
}

3. AllItemsAreUnique

コレクションが保持する要素が全てユニークなものかを検査する。
ユニーク性の判定はEqualsメソッドで判定しているようで、
下記コードの1番下の例のように、参照が異なっていても値として同じものが含まれていればテストNGとなる。

[TestCase]
public void AllItemsAreUniqueTest()
{
    // Test OK.
    CollectionAssert.AllItemsAreUnique(
        new List<string>() {"hoge", "fuga", "hage"});

    // Test NG.
    CollectionAssert.AllItemsAreUnique(
        new List<string>() {"hoge", "hoge", "hage"});

    // Test OK.
    CollectionAssert.AllItemsAreUnique(
        new List<string>() {"hoge", null, "hage"});

    // Test NG.
    CollectionAssert.AllItemsAreUnique(
        new List<string>() {new String("hoge"), new String("hoge"), new String("hage")});

}

4. AreEqual、AreNotEqual

AreEqualメソッドは、2つのコレクションが保持する要素が格納順序込みで同じものがどうかを検査する。
AreNotEqualメソッドは、その逆の判定を行う。

下記コードの3つ目の例のように、保持する要素が同じでも、格納する順序が異なる場合、テストNGとなる。
順序を無視した検査をしたい場合は、AreEquivalentメソッドを使う。

[TestCase]
public void AreEqualTest()
{
    // Test OK.
    CollectionAssert.AreEqual(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {"hoge", "fuga", "hage"});

    // Test NG.
    CollectionAssert.AreEqual(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {"hoge", "hoge", "hage"});

    // Test NG.
    CollectionAssert.AreEqual(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {"fuga", "hoge", "hage"});

    // Test OK.
    CollectionAssert.AreEqual(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {new String("hoge"), new String("fuga"), new String("hage")});

}

5. AreEquivalent、AreNotEquivalent

AreEquivalentメソッドは、2つのコレクションが保持する要素が同じものがどうかを検査する。
AreNotEquivalentメソッドは、その逆の判定を行う。

AreEqualメソッドとは違い、格納順は異なっていてもテストOKとなる。

[TestCase]
public void AreEquivalentTest()
{
    // Test OK.
    CollectionAssert.AreEquivalent(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {"hoge", "fuga", "hage"});

    // Test NG.
    CollectionAssert.AreEquivalent(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {"hoge", "hoge", "hage"});

    // Test OK.
    CollectionAssert.AreEquivalent(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {"fuga", "hoge", "hage"});

    // Test OK.
    CollectionAssert.AreEquivalent(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {new String("hoge"), new String("fuga"), new String("hage")});

}

6. Contains、DoesNotContain

Containsメソッドは、コレクションに指定した要素が含まれるかどうかを検査する。
DoesNotContainメソッドは、その逆の判定を行う。

[TestCase]
public void ContainsTest()
{
    // Test OK.
    CollectionAssert.Contains(
        new List<string>() {"hoge", "fuga", "hage"},
        "hoge");

    // Test NG.
    CollectionAssert.Contains(
        new List<string>() {"hoge", "fuga", "hage"},
        "HOGE");

    // Test OK.
    CollectionAssert.Contains(
        new List<string>() {"hoge", "fuga", "hage"},
        new String("hoge"));

}

7. IsSubsetOf、IsNotSubsetOf

IsSubsetOfメソッドは、コレクションが部分的に一致するかどうかを検査する。
IsNotSubsetOfメソッドは、その逆の判定を行う。

第1引数に部分一致させるコレクションを指定、
第2引数に全体となるコレクションを指定する。

[TestCase]
public void IsSubsetOfTest()
{
    // Test OK.
    CollectionAssert.IsSubsetOf(
        new List<string>() {"hoge", "fuga"},
        new List<string>() {"hoge", "fuga", "hage"});

    // Test NG.
    CollectionAssert.IsSubsetOf(
        new List<string>() {"HOGE", "FUGA"},
        new List<string>() {"hoge", "fuga", "hage"});

    // Test NG.
    CollectionAssert.IsSubsetOf(
        new List<string>() {"hoge", "fuga", "hage"},
        new List<string>() {"hoge", "fuga"});

}

8. IsEmpty、IsNotEmpty

IsEmptyメソッドは、コレクションが空であることを検査する。
IsNotEmptyメソッドは、その逆の判定を行う。

nullが入っていてもコレクション自体は空ではないのでテスト結果NGとなる。

[TestCase]
public void IsEmptyTest()
{
    // Test OK.
    CollectionAssert.IsEmpty(
        new List<string>() {});

    // Test NG.
    CollectionAssert.IsEmpty(
        new List<string>() {"hoge"});

    // Test NG.
    CollectionAssert.IsEmpty(
        new List<string>() {null});
}

9. IsOrdered

コレクションの順序を検査する。
引数にコレクションオブジェクトのみ指定した場合、自然順で要素が格納されていることを検査する。
検査する順序を指定したい場合は、下記コードの一番下の例のように、第2引数にIComparerインターフェースを実装したクラスのインスタンスを渡す。

[TestCase]
public void IsOrderedTest()
{
    // Test OK.
    CollectionAssert.IsOrdered(
        new List<string>() {"A", "B", "C"});

    // Test NG.
    CollectionAssert.IsOrdered(
        new List<string>() {"C", "B", "A"});

    // Test OK.
    CollectionAssert.IsOrdered(
        new List<string>() {null, "A", "B"});

    // Test NG.
    CollectionAssert.IsOrdered(
        new List<string>() {"A", "B", null});

    // Test ArgumentException.
    CollectionAssert.IsOrdered(
        new ArrayList() {"A", "B", new Target()});

    // Test OK.
    CollectionAssert.IsOrdered(
        new List<string>() {"C", "B", "A"},
        new StringOrderDesc()
    );
}

public class StringOrderDesc : Comparer<string>
{
    public override int Compare(string x, string y) => y.CompareTo(x);
}

nullは自然順序的には最も小さい値という扱いらしい。

また、型が混在している場合、ArgumentExceptionが発生する。
(ジェネリクスを使用していれば発生しないだろうが)

ちなみにIComparer<T>インターフェースIComparerインターフェースを継承していないので(なぜだろうか・・・)
IComparer<T>インターフェースを実装したクラスを第2引数に渡すことができない。
Comparer<string>クラスはその両方をimplementsしているので、ジェネリクスを使用して無用なキャストを避けたい場合は、Comparerクラスのサブクラスを用意する。

参考

NUnit公式:CollectionAssert

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity CommandBufferを用いて選択したオブジェクトにだけアウトラインをつける

概要

以下のgifのように、クリックしたオブジェクトにのみアウトラインを設定するようなものを、Unityの機能であるCommandBufferを利用して作成しました。
selective-outline.gif

プロジェクトのソースコードはこちら
https://github.com/Arihide/unity-selective-outline

解説

CommandBufferを用いると、レンダリングパイプラインの任意の箇所に、別の描画処理を挟み込むことができます。
今回はこの機能を用いて、以下の図のように、一部メッシュに対してだけレンダリング・輪郭抽出を行った後に合成を行いました。
selective-outline.png

次からは具体的なスクリプト部分について説明します。

スクリプト部分

まずは、CommandBufferを用いたスクリプト部分を見てみましょう。

SelectiveOutline.cs
using UnityEngine;
using UnityEngine.Rendering;

[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class SelectiveOutline : MonoBehaviour
{
    public Material emissionMaterial;
    public Material outlineMaterial;

    private new Camera camera;
    private CommandBuffer commandBuffer;

    [SerializeField] private Renderer targetRenderer = null;

    void OnEnable()
    {
        camera = GetComponent<Camera>();

        commandBuffer = new CommandBuffer();
        commandBuffer.name = "Selective Outline";

        SetCommandBuffer();

        // ImageEffects前(OnRenderImageが呼ばれる前)に適用
        camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer);
    }

    void OnDisable()
    {
        camera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer);
    }

    void SetCommandBuffer()
    {
        commandBuffer.Clear();

        if (targetRenderer != null)
        {
            // レンダリング結果を格納するテクスチャ作成
            var id = Shader.PropertyToID("_OutlineTex");
            commandBuffer.GetTemporaryRT(id, -1, -1, 24, FilterMode.Bilinear);
            commandBuffer.SetRenderTarget(id);

            // アウトラインを表示させたいメッシュの描画
            commandBuffer.ClearRenderTarget(false, true, Color.clear);
            commandBuffer.DrawRenderer(targetRenderer, emissionMaterial);

            // アウトラインを抽出して合成
            commandBuffer.Blit(id, BuiltinRenderTextureType.CameraTarget, outlineMaterial);
        }
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = camera.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out RaycastHit hit))
            {
                targetRenderer = hit.transform.GetComponent<Renderer>();
                SetCommandBuffer();
            }
        }
    }
}

具体的な内容はソースコードのコメントを見ていただくとして、

大まかな処理の流れとしては、
OnEnable関数内でCommandBufferオブジェクトを作成し、さらにSetCommandBuffer関数内でどのような流れで描画するのかを設定しています。
その後、

camera.AddCommandBuffer(CameraEvent.BeforeImageEffects, commandBuffer);

とすることによって、設定したCommandBufferをImageEffectsの直前(一通りメッシュの描画が完了したあと)に挟み込むようにします。

またUpdate関数内でオブジェクトがクリックされたのを検出したとき、そのオブジェクトにアウトラインをつけるように、CommandBufferの再設定を行っています。

シェーダー部分

さて、処理の流れがわかったところで、次にアウトラインを抽出するシェーダーを見てみましょう。

Outline.shader
Shader "Custom/Outline"
{
    Properties
    {
        [HideInInspector]_MainTex ("Texture", 2D) = "white" {}

        _OutlineColor ("Outline Color", Color) = (1,1,1,1)
        _OutlineWidth ("Outline Width", Range(0, 10)) = 1
    }
    SubShader
    {
        Tags { "Queue" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            half2 _MainTex_TexelSize;

            half4 _OutlineColor;
            half _OutlineWidth;

            half4 frag (v2f_img i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                half2 destUV = _MainTex_TexelSize * _OutlineWidth;

                half left   = tex2D(_MainTex, i.uv + half2(destUV.x, 0)).a;
                half right  = tex2D(_MainTex, i.uv + half2(-destUV.x, 0)).a;
                half bottom = tex2D(_MainTex, i.uv + half2(0, destUV.y)).a;
                half top    = tex2D(_MainTex, i.uv + half2(0, - destUV.y)).a;
                half topLeft = tex2D(_MainTex, i.uv + half2(destUV.x, destUV.y)).a;
                half topRight = tex2D(_MainTex, i.uv + half2(-destUV.x, destUV.y)).a;
                half bottomLeft = tex2D(_MainTex, i.uv + half2(destUV.x, -destUV.y)).a;
                half bottomRight = tex2D(_MainTex, i.uv + half2(-destUV.x, -destUV.y)).a;

                // あるピクセルの近傍が不透明であれば 1
                half result = saturate(left + right + bottom + top + topLeft + topRight + bottomLeft + bottomRight);

                // 透過じゃないところはそのまま
                clip(0.99 - col.a);

                half4 outline = result * _OutlineColor;

                return outline;
            }
            ENDCG
        }
    }
}

よく見かけるアウトラインシェーダーは深度や法線による方法が多いですが、
今回はあるピクセルの近くが不透明だったら自分のピクセルも不透明とみなして輪郭を広げ、もともと不透明だったピクセル部分はくり抜く。という単純な方法で実装しました。
理由としては今回は描画対象が1つなので、重なりなどを考慮する必要がないからです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

タイマー作成①

初めに

新卒でIT系の企業に就職して半年がたち、10月から本格的に業務に参加させていただいています。もやしと申します。

現在配属している部署で、月に1度、自身が行っている勉強や、それによる成長度合い等の発表を行わなければならなくなったさせていただく事になりました。

せっかくなので、勉強の過程で得られた知識やプログラムを共有したく、今回Qiitaに登録しました!
読みにくい文章ですが、興味のある方は、ぜひ読んでいってください。

本題

会社の新卒研修で作成したタイマーソフトを、もう一度作り直そうと思います。
というのもこの作成したタイマーソフト、今見直すと酷過ぎるプログラムでして・・・。黒歴史になりかねないので、今一度誰もが納得する出来にしたいです!

要求仕様書

うろ覚えですが、以下に与えられた要求仕様書を載せておきます。
研修では、細かい仕様をサポーターの方と話し合いながら決めましたが、今回は自分で決めようと思います。

  • 20分50秒までカウントするタイマーを作成してください。
  • 分数を設定するボタンを1回押すと1分カウントアップし、秒数を設定するボタンを1回押すと10秒カウントアップするようにしてください。
  • クリアキーを押すと、設定した時間がリセットされるようにしてください。
  • タイマースタート・ストップは1つのキーで制御してください。
  • 時間の設定は、カウントダウンが停止している時のみ行えるようにしてください。
  • タイマーが0になったら、「タイマーが終了しました」と3秒間表示し、3秒たったら自動的に時間設定を行えるようにしてください。
  • ただし、「タイマーが終了しました」と表示中に何らかのキーが押された場合、強制的に時間設定が行えるようにしてください。

長くなりましたが、以上が仕様書となります。図にすると、こんな感じですかね。
状態遷移図.png

環境

IDE:Visual Studio Express 2017

言語:C#

フォームアプリケーションではなく、コンソールアプリケーションとして作成します。

コーディング規約

実際に業務を始めてから痛感していることですが、コーディング規約を守るのは非常に大切ですね。
今回はマイクロソフトドキュメントのコーディング規約に従ってコーディングしていきます。

メソッド名を作成者の名前にするのは犯罪です。絶対にやめましょう。(個人単位のデバッグ用なら良い)

前置きは以上となります。
次回からは実際にコーディングを行っていくので、よろしければ次回も読んでくださいね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[.NET][C#]NUnitのStringAssertアサーションメソッド一覧

はじめに

前回、NUnitのAssertクラスのClassicModelのアサーションメソッドを一通り試したので
今回はStringAssertクラスのアサーションメソッドを一通り試してみる。

実施環境

.NET:3.1.401
C#:8.0
NUnit:3.12.0

StringAssertクラス

NUnitのClassicModelのアサーションクラスの1つ。
クラス名の通り、文字列を検査する。
全てのメソッドは大文字小文字を区別して判定する。

1. Contains、DoesNotContain

Containsメソッドは、第1引数の文字列が第2引数の文字列内に含まれていることを検査する。
DoesNotContainメソッドは、その逆で第1引数の文字列が第2引数の文字列内に含まれていないことを検査する。

[TestCase("hoge", "aaahogeaaa")]// Test OK.
[TestCase("hoge", "aaahogaaa")]// Test NG.
[TestCase("hoge", "aaaHOGEaaa")]// Test NG.
public void ContainsTest(string expected, string actual)
{
    StringAssert.Contains(expected, actual);
}

[TestCase("hoge", "aaahogeaaa")]// Test NG.
[TestCase("hoge", "aaahogaaa")]// Test OK.
[TestCase("hoge", "aaaHOGEaaa")]// Test OK.
public void DoesNotContainTest(string expected, string actual)
{
    StringAssert.DoesNotContain(expected, actual);
}

2. StartsWith、DoesNotStartWith

StartsWithメソッドは、第1引数の文字列が第2引数の文字列の接頭辞になっていることを検査する。
DoesNotStartWithメソッドは、第1引数の文字列が第2引数の文字列の接頭辞になっていないことを検査する。

[TestCase("hoge", "hogeaaa")]// Test OK.
[TestCase("hoge", "hogaaa")]// Test NG.
[TestCase("hoge", "HOGEaaa")]// Test NG.
public void StartsWithTest(string expected, string actual)
{
    StringAssert.StartsWith(expected, actual);
}

[TestCase("hoge", "hogeaaa")]// Test NG.
[TestCase("hoge", "hogaaa")]// Test OK.
[TestCase("hoge", "HOGEaaa")]// Test OK.
public void DoesNotStartWithTest(string expected, string actual)
{
    StringAssert.DoesNotStartWith(expected, actual);
}

ちなみに、公式サイトのドキュメントでは、
DoesNotStartsWithと記載されているが、
DoesNotStartWithが正しい。
(StartsWithは、Withの前にsがあるので実装ミス?)

3. EndsWith、DoesNotEndWith

StartsWithの接尾辞版。

[TestCase("hoge", "aaahoge")]// Test OK.
[TestCase("hoge", "aaahog")]// Test NG.
[TestCase("hoge", "aaaHOGE")]// Test NG.
public void EndsWithTest(string expected, string actual)
{
    StringAssert.EndsWith(expected, actual);
}

[TestCase("hoge", "aaahoge")]// Test NG.
[TestCase("hoge", "aaahog")]// Test OK.
[TestCase("hoge", "aaaHOGE")]// Test OK.
public void DoesNotEndWithTest(string expected, string actual)
{
    StringAssert.DoesNotEndWith(expected, actual);
}

4. AreEqualIgnoringCase、AreNotEqualIgnoringCase

AreEqualIgnoringCaseメソッドは、第1引数と第2引数の文字列が大文字小文字無視で一致していることを検査する。
AreNotEqualIgnoringCaseメソッドは、第1引数と第2引数の文字列が大文字小文字無視で一致していないことを検査する。
当然だが、半角全角を区別しないというわけではない。

[TestCase("hoge", "HOGE")]// Test OK.
[TestCase("hoge", "Hoge")]// Test OK.
[TestCase("hoge", "HOG")]// Test NG.
[TestCase("hoge", "hoge")]// Test NG.
public void AreEqualIgnoringCaseTest(string expected, string actual)
{
    StringAssert.AreEqualIgnoringCase(expected, actual);
}

[TestCase("hoge", "HOGE")]// Test NG.
[TestCase("hoge", "Hoge")]// Test NG.
[TestCase("hoge", "HOG")]// Test OK.
[TestCase("hoge", "hoge")]// Test OK.
public void AreNotEqualIgnoringCaseTest(string expected, string actual)
{
    StringAssert.AreNotEqualIgnoringCase(expected, actual);
}

5. IsMatch、DoesNotMatch

IsMatchメソッドは、第1引数の正規表現パターンが第2引数の文字列にマッチするか検査する。
DoesNotMatchメソッドは、第1引数の正規表現パターンが第2引数の文字列にマッチしないか検査する。

[TestCase("^[0-9]{3}hoge[A-Z]{3}$", "123hogeABC")]// Test OK.
[TestCase("[0-9]*", "hoge123hoge")]// Test OK.
[TestCase("^[0-9]{3}hoge[A-Z]{3}$", "ABChoge123")]// Test NG.
public void IsMatchTest(string regexPattern, string actual)
{
    StringAssert.IsMatch(regexPattern, actual);
}

[TestCase("^[0-9]{3}hoge[A-Z]{3}$", "123hogeABC")]// Test NG.
[TestCase("[0-9]*", "hoge123hoge")]// Test NG.
[TestCase("^[0-9]{3}hoge[A-Z]{3}$", "ABChoge123")]// Test OK.
public void DoesNotMatchTest(string regexPattern, string actual)
{
    StringAssert.DoesNotMatch(regexPattern, actual);
}

参考

NUnit公式:StringAssert

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む