- 投稿日:2020-01-19T17:42:41+09:00
僕のC#基礎の基礎
- 投稿日:2020-01-19T16:04:37+09:00
ブラックジャックの勝率を上げたくて、ディーラーのバースト率を1300万回かけて計算してみた
本投稿の目的
トランプゲーム「ブラックジャック」における『ディーラー』の、初手ごとのバースト率を計算します。
はじめに
トランプゲーム「ブラックジャック」では、まずプレイヤーが、表になっている2枚のカードから、「次のカードを引くかどうか」ということを判断します。
その際、例えば「Q」「K」のような強い手札なら、言うまでもなく「スタンド(引かずに終わる)」という選択をするでしょう。
しかし、例えば「10」「5」のような微妙な手札の場合、バースト(21を超えること)を恐れて次を引かない、ということも多いと思います。一方で、プレイヤーの得点が17未満でスタンドした場合、プレイヤーが勝つためには、「ディーラーがバーストする」ということを祈るしかありません。
それは、ディーラーは「17以上まで引き続ける」という世界共通のルールのためです。
ディーラーが17~21になれば、プレイヤーの負け。
ディーラーが22以上になれば、プレイヤーの勝ち。ということになります。そのため、自分が勝利するために超重要になってくるのが、
ヒット(引く) or スタンド(引かない) の段階で、
「ディーラーはバーストするかどうか?」
の判断です。最初に場にカードを配る際、ディーラーの手札は1枚公開されています。
自分の手札が17未満の場合に、ヒットかスタンドかを決める場合には、
「今ディーラーが見えている手札1枚で、ディーラーがバーストする確率」
が、超重要な要素です。
そのままスタンドした場合には、ディーラーのバースト率 = プレイヤーの勝率になります。そこで今回は、ディーラーの初手ごと(1~13)に、ディーラーのバースト率を計算してみます。
検証方法
- 山札は、52枚のパターン、ならびに52枚4セット→208枚のパターンとする
- プレイヤーの要素は取り入れず、ディーラーのみでゲームを行う
- まずはディーラーの初手を、1に固定する
- そのままディーラーがブラックジャックを実施する。17以上になるまで手札を引き続ける
- 結果の点数と、バーストしたかどうかを記録する。これを100万回繰り返す
- 100万回完了したら、ディーラーの初手を2に変更し、同じ処理を繰り返す
- 以上の流れを、初手を1~13で、各100万回、合計1,300万回ブラックジャックを実施する
- それぞれの初手ごとに、バースト率と、最終点数の推移を計算する
もちろん、手動で1,300万回やるのは無理なので、プログラミングします。
以前書いた記事で作ったC#のプログラミングを、そのまま流用しています。結果
表にまとめました。
※各初手ごとに、100万回で検証しています。一覧
初手 バースト率 52枚 バースト率 52枚×4セット 1 11.2814% 11.0174% 2 35.55% 35.5126% 3 37.5917% 37.521% 4 43.1333% 43.1712% 5 46.3084% 46.1001% 6 41.5123% 41.6665% 7 24.3494% 24.2288% 8 21.9696% 21.8951% 9 23.9652% 23.8183% 10 21.3837% 21.3874% 11 21.4819% 21.3717% 12 21.4571% 21.2703% 13 21.5109% 21.3918% 詳細
※列17~26は、各初手ごとの、最終得点の件数です。
52枚
初手 バースト率 17 18 19 20 21 22 23 24 25 26 A 0.1128 118327 130965 128928 131281 377685 27759 28862 23119 18892 14182 2 0.3555 136031 126789 133007 126322 122351 158993 62243 43618 49438 41208 3 0.3759 122115 129504 125603 127316 119545 101760 150105 53564 43424 27064 4 0.4313 130115 83044 115658 117970 121880 105475 96274 144579 47597 37408 5 0.4630 120217 124727 116605 73387 101980 103473 94185 88732 137907 38787 6 0.4151 169107 112990 106267 100042 96471 42985 92277 80877 75864 123120 7 0.2434 387910 140236 75133 76664 76563 60562 51687 36122 51196 43927 8 0.2196 136672 377436 131418 66549 68229 55427 50680 49100 46455 18034 9 0.2396 131998 55908 381094 127766 63582 59758 51737 47223 39158 41776 10 0.2138 123360 121664 123747 292919 124473 53869 49652 43324 37265 29727 J 0.2148 122605 121415 124099 292787 124275 53900 49987 43708 37291 29933 Q 0.2145 123389 121473 123674 292626 124267 54344 49961 43491 37175 29600 K 0.2151 124197 121259 123143 292671 123621 54053 50374 43746 37287 29649 52枚×4セット=208枚
初手 バースト率 17 18 19 20 21 22 23 24 25 26 A 0.110174 120105 130257 129570 130221 379673 27081 27781 22752 18423 14137 2 0.355126 135301 128728 133202 126417 121226 161894 61121 43059 48204 40848 3 0.37521 124457 128685 126461 126035 119152 100839 152559 52222 43363 26227 4 0.431712 129024 86097 114850 118188 120129 104863 94922 147297 46704 37926 5 0.461001 123223 123505 117132 73773 101366 102014 94220 86644 140007 38116 6 0.416665 169207 112435 106113 100377 95203 44794 90000 81480 74317 126074 7 0.242288 389351 140190 76237 76151 75783 59491 52267 35539 51753 43238 8 0.218951 136861 378666 130951 66870 67701 56378 49974 49385 45156 18058 9 0.238183 132904 55997 381364 127525 64027 59298 52728 46442 38801 40914 10 0.213874 122816 122645 123031 294161 123473 54521 49898 43413 36568 29474 J 0.213717 123444 122771 123676 292932 123460 54842 49581 43055 36602 29637 Q 0.212703 123176 123132 123733 293887 123369 54292 49248 43153 36714 29296 K 0.213918 122684 122974 123013 294415 122996 54997 49534 43216 36651 29520 52枚でやった場合と、52枚×4セットでやった場合、どちらも結果はほぼ同じでした。
また、もっとも大事なのが、「バースト率」です。ディーラーの初手が1、つまりAの場合は、バースト率は11%しかありません。
つまり、ディーラーの初手がAで、17未満なのにプレイヤーがスタンドしてしまったら、その時点でプレイヤーは「勝率は11%しかない」ということになります。
なので、その時点でスタンドすることは、かなり分の悪い勝負になります。
多少頑張ってもヒットする、もしくは潔くサレンダー(降参)、という選択肢が生まれてくるわけです。一方でディーラーの初手が4~6の場合、バースト率は4割を超えています。
なのでプレイヤーは、たとえ手札が17未満でスタンドしたとしても、4割強の確率で勝てるということです。それはそこまで、分の悪い勝負ではないでしょう。こういったことが、上記の表から分かります。
※なお実際には、この確率は多少のブレがあります。
実際には、「すでに他のプレイヤーに配られているカードは二度と引かれない」ため、次に引くカードは、若干絞られます。
「場に小さな数がいっぱい出ているから、次引くカードは大きな数の可能性が高い」などです。
上記の表をベースに、確率の上下を計算しても良いかもしれないですね。
以上です。
この確率を暗記するだけで、ブラックジャックの勝率が大きく変わるかもしれないですね。
ブラックジャックをやる方は、是非覚えておいて欲しいです!また、プログラミングを覚えてしまえば、こんな検証もお手軽にできます。
これからプログラミングをやってみたい!と考えている方は、是非楽しみにしていてくださいね。リンク集
- 投稿日:2020-01-19T15:39:41+09:00
.NET Core 汎用ホスト(GenericHost)ログ出力の構成
このドキュメントの内容
汎用ホスト(GenericHost)におけるログ出力の構成についてまとめます。
このドキュメントの内容は .NET Core 3.1 で確認しています。
既定のロガー
Host
クラスの CreateDefaultBuilder メソッドの既定の動作では、Microsoft.Extensions.Logging.ILogger<T> 型のロガーが注入されます。T は注入先のサービスなどの型です。このロガーインスタンスには次の4つのログプロバイダが格納されており、ロガーに対して出力したログはこれらのプロバイダを経由してそれぞれの出力先に出力されることになります。
- Microsoft.Extensions.Logging.Console.ConsoleLogger
- コンソールに対してログを出力します。
- ログレベルは Information です。
- Microsoft.Extensions.Logging.Debug.DebugLogger
- デバッグ出力に対してログを出力します。
- ログレベルは Information です。
- Microsoft.Extensions.Logging.EventSource.EventSourceLogger
- ETW(Event Tracing for Windows)に対してログを出力します。
- ログレベルは Debug です。
- Microsoft.Extensions.Logging.EventLog.EventLogLogger
- イベントログに対してログを出力します。
- ログレベルは Warning です。
ログプロバイダの追加と削除
既定のログプロバイダを削除したり、任意のログプロバイダを追加するには、IHostBuilder.ConfigureLogging メソッドに実装します。
ホストの構成例IHostBuilder builder = Host.CreateDefaultBuilder(args) .ConfigureLogging((HostBuilderContext context, ILoggingBuilder builder) => { // ログプロバイダをクリア builder.ClearProviders(); // コンソールログを追加 builder.AddConsole(); // デバッグログを追加 builder.AddDebug(); // イベントログを追加 builder.AddEventLog(); // イベントソースを追加 builder.AddEventSourceLogger(); // 独自のログプロバイダを追加 builder.AddProvider(new SampleLoggerProvider()); } );コンソールログ
動作オプションを設定できるオーバーロードが定義されています。
ホストの構成例IHostBuilder builder = Host.CreateDefaultBuilder(args) .ConfigureLogging((HostBuilderContext context, ILoggingBuilder builder) => { builder.AddConsole((ConsoleLoggerOptions options) => { options.Format = ConsoleLoggerFormat.Default; options.DisableColors = true; } ); } );イベントログ
出力先とフィルタを設定できるオーバーロードが定義されています。
ホストの構成例IHostBuilder builder = Host.CreateDefaultBuilder(args) .ConfigureLogging((HostBuilderContext context, ILoggingBuilder builder) => { builder.AddEventLog((EventLogSettings settings) => { settings.LogName = "Application"; settings.MachineName = Environment.MachineName; settings.SourceName = "GenericHostSample"; settings.Filter = (string category, LogLevel level) => true; } ); } );イベントソース
AddEventSourceLogger メソッドにはオーバーロードは定義されていません。独自のイベントソースに出力するような場合はプロバイダを自作する必要があります。
独自のログプロバイダ
ILoggerProvider インターフェースを実装したプロバイダを定義し、AddProvider メソッドを使って登録します。
ホストの構成例IHostBuilder builder = Host.CreateDefaultBuilder(args) .ConfigureLogging((HostBuilderContext context, ILoggingBuilder builder) => { builder.AddProvider(new SampleLoggerProvider()); } );ILoggerProvider に定義されているメソッドは CreateLogger メソッドのみです。
IDisposable インターフェースから派生していますので、Dispose メソッドも実装する必要があります。
プロバイダクラスに対して ProviderAliasAttribute を定義しておくと、既定のプロバイダ同様、JSON構成ファイルでプロバイダに対するログレベルを設定できるようになります。SampleLoggerProvider[ProviderAlias("SampleLogger")] public class SampleLoggerProvider : ILoggerProvider { public SampleLoggerProvider() {} ILogger ILoggerProvider.CreateLogger(string categoryName) { return new SampleLogger(m_Name + "." + categoryName); } void IDisposable.Dispose() { } }ILogger インターフェースには BeginScope, IsEnabld, Log の3つのメソッドが定義されています。IsEnabld, Log はその名のとおりです。
BeginScope メソッドはある一連の処理のログを出力するときにその開始と終了にログを出力するための仕組みだと思うのですが、ヘッダー・フッターを出力する、インデントするなどの例しか思いつきませんでした。
参考:Using The ILogger BeginScope In ASP.NET CoreSampleLoggerpublic class SampleLogger : ILogger { public SampleLogger(string name) { m_Name = name; } private readonly string m_Name; // 指定されたログレベルが出力対象かどうかを取得します。 bool ILogger.IsEnabled(LogLevel logLevel) { return true; } // 指定されたログを出力します。 void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { string message = (メッセージをフォーマット。割愛します。) WriteLogLine(message); } // 指定されたログを出力します。 private void WriteLogLine(string message) { Console.WriteLine(new string('\t', m_ScopeLevel) + message)); } // スコープのレベル // スレッドセーフは考慮していません private int m_ScopeLevel = 0; // スコープが開始されたときの処理を行います。 IDisposable ILogger.BeginScope<TState>(TState state) { WriteLogLine($"Begin {state}"); ++m_ScopeLevel; return new Scope<TState>(state, () => { // スコープが終了されたときの処理 if (m_ScopeLevel > 0) { --m_ScopeLevel; } WriteLogLine("End"); }); } // BeginScope の戻り値として返すオブジェクト。 // dispose されたときにスコープの終了処理を行います。 private struct Scope<TState> : IDisposable { internal Scope(TState state, Action onDispose) { m_State = state; m_OnDispose = onDispose; } private readonly TState m_State; private readonly Action m_OnDispose; void IDisposable.Dispose() { m_OnDispose?.Invoke(); } } }上の SampleLogger のスコープを使ったログ出力の例です。スコープが閉じられるまでの間はインデントされます。
ログを出力するコードusing (var scope = logger.BeginScope("一連の処理")) { logger.LogTrace($"[TRACE] {message}"); logger.LogDebug($"[DEBUG] {message}"); logger.LogInformation($"[INFO] {message}"); logger.LogWarning($"[WARN] {message}"); logger.LogError($"[ERROR] {message}"); logger.LogCritical($"[CRITICAL] {message}"); }出力されたログ(LogLevel.Traceは無効になっています)Begin 一連の処理 [DEBUG] ログメッセージ [INFO] ログメッセージ [WARN] ログメッセージ [ERROR] ログメッセージ [CRITICAL] ログメッセージ Endなお、このような場合は ILoggingBuilder インターフェースに対する拡張メソッドを定義することが一般的です。こうすることによって本来外に見せる必要がないプロバイダクラスを見せないようにすることができます。
拡張メソッドpublic static class SampleLoggerProviderExtension { public static ILoggingBuilder AddSampleLogger(this ILoggingBuilder builder) { return builder.AddProvider(new SampleLoggerProvider()); } }ホストの構成例IHostBuilder builder = Host.CreateDefaultBuilder(args) .ConfigureLogging((HostBuilderContext context, ILoggingBuilder builder) => { // builder.AddProvider(new SampleLoggerProvider()); builder.AddSampleProvider(); } );ログレベル
Microsoft.Extensions.Logging.LogLevel 列挙体には次の7つの値が定義されています。一般的なロガーのログレベルとほぼ同じです。ロガーのログレベルは出力対象の最小レベルを意味し、例えば Information が設定されている場合は Information ~ Critical が出力対象になります。
列挙値 値 Trace 0 Debug 1 Information 2 Warning 3 Error 4 Critical 5 None 6 コードによるログレベルの設定
ILoggingBuilder.SetMinimumLevel メソッドを使って既定のログレベルを設定することができます。
ホストの構成例IHostBuilder builder = Host.CreateDefaultBuilder(args) .ConfigureLogging((HostBuilderContext context, ILoggingBuilder builder) => { // ログレベルを設定 builder.SetMinimumLevel(LogLevel.Debug); } );
- 既定で登録される4つのログプロバイダのうち、ConsoleLogger と DebugLogger にはログレベルが適用されました。EventSourceLogger と EventLogLogger には適用されませんでした。
JSON構成ファイルによるログレベルの設定
application.json や任意のJSON構成ファイルを使って、ログレベルを含むロガーの設定を行うことができます。
構成ファイルの例{ "Logging": { // 既定のログレベル "LogLevel": { "Default": "Information" }, // ConcoleLoggerのログレベル "Console": { "LogLevel": { "Default": "Warning" } }, // DebugLoggerのログレベル "Debug": { "LogLevel": { "Default": "None", "GenericHostSample": "Debug" } }, // EventSourceLoggerのログレベル 反映されない? "EventSource": { "LogLevel": { "Default": "None" } }, // EventLogLoggerのログレベル "EventLog": { "LogLevel": { "Default": "None" } } } }
既定のログレベルの他、Microsoft.Extensions.Logging で提供されている各ログプロバイダごとのログレベルを設定することができます。この例の "GenericHostSample" のようにカテゴリ(≒注入先の名前空間+型名)ごとのログレベルを設定することもできます。これは後述するフィルタに相当します。
既定で登録される4つのログプロバイダのうち、ConsoleLogger と DebugLogger と EventLoglogger にはログレベルが反映されました。EventSourceLogger には適用されませんでした。
ログフィルタ
コードによるログフィルタの設定
ILoggingBuilder.AddFilter メソッドを使ってログフィルタを設定することができます。
ホストの構成例IHostBuilder builder = Host.CreateDefaultBuilder(args) .ConfigureLogging((HostBuilderContext context, ILoggingBuilder builder) => { // カテゴリに対してフィルタを設定 builder.AddFilter("System", LogLevel.Warning); builder.AddFilter("Microsoft", LogLevel.Warning); builder.AddFilter("GenericHostSample", LogLevel.Debug); // フィルタメソッドで設定(Func<string, string, LogLevel, bool>) builder.AddFilter((provider, category, level) => { if (provider.EndsWith("ConsoleLoggerProvider")) { if (category.StartsWith("Microsoft")) { return (level.CompareTo(LogLevel.Warning) >= 0); } } return true; }); } );
- 既定で登録される4つのログプロバイダのうち、ConsoleLogger と DebugLogger にはフィルタが適用されました。EventSourceLogger と EventLogLogger には適用されませんでした。フィルタメソッドで設定すると provider に ConsoleLoggerProvider と DebugLoggerProvider が指定された呼び出しは発生しましたが、EventSourceLoggerProvider と EventLogLoggerProvider が指定された呼び出しは発生しませんでした。
JSON構成ファイルによるログレベルの設定
ログレベルを設定する際に "Default" の代わりにカテゴリを設定すると、カテゴリに対してフィルタを設定したのと同じ意味になります。前述のログレベルの記述例を参照してください。
EventLogLogger にもフィルタが適用されました。
EventSourceLogger にもフィルタが適用されました。ログレベルは適用されませんでしたが、フィルタは適用されるようです。
構成ファイルの例{ "Logging": { // "GenericHostSample" で始まるカテゴリは Critical、それ以外は Debug // "Default"(=ログレベル)は適用されない? "EventSource": { "LogLevel": { "Default": "Error", "GenericHostSample": "Critical" } }, // "GenericHostSample" で始まるカテゴリは Critical、それ以外は Error "EventLog": { "LogLevel": { "Default": "Error", "GenericHostSample": "Critical" } } } }ETWに出力されたログの取得
LoggingEventSource クラスのページに EventListener を使ったログの取得方法が説明されています。
EventListener クラスを継承したリスナークラスを実装します。
MsExtLoggingEventListener// using System.Diagnostics.Tracing; // using Microsoft.Extensions.Logging.EventSource; // Microsoft.Extensions.Logging.EventSourceLogger から出力されるログを取得するリスナー public class MsExtLoggingEventListener : EventListener { // イベントソースが生成されたときの処理を行います。 protected override void OnEventSourceCreated(EventSource eventSource) { // EventSourceLogger は "Microsoft-Extensions-Logging" という名前のイベントソースにログを出力します。 if (eventSource.Name == "Microsoft-Extensions-Logging") { // この例では FormattedMessage イベントのすべてのログを有効にしています。 EnableEvents(eventSource, EventLevel.Verbose, LoggingEventSource.Keywords.FormattedMessage); } } // ログが書き込まれたときの処理を行います。 protected override void OnEventWritten(EventWrittenEventArgs eventData) { // この例ではイベントデータの内容をコンソールに出力しています。 Console.WriteLine($"EventId = {e.EventId}"); Console.WriteLine($"EventName = {e.EventName}"); Console.WriteLine($"Keywords = {e.Keywords}"); Console.WriteLine($"Level = {e.Level}"); Console.WriteLine($"Message = {e.Message}"); if (e.PayloadNames != null) { for (int i = 0; i < e.PayloadNames.Count; ++i) { Console.WriteLine($"Payload[{e.PayloadNames[i]}] = {e.Payload[i]}"); } } } }次のコードは上記のリスナーを使ってアプリケーションのログを取得する簡単な例です。
リスナーの利用例class Program { static async Task Main(string[] args) { // リスナーを生成 using (var listener = new MsExtLoggingEventListener()) { // イベントハンドラを使ってイベントソースが生成されたときの処理とログが書き込まれたときの処理を行うことも可能です。 listener.EventSourceCreated += listener_EventSourceCreated; listener.EventWritten += listener_EventWritten; // アプリケーションの処理を実行 // この中でイベントソースに対して出力されたログがリスナーによって取得されます await CreateHostBuilder(args).RunConsoleAsync(); } } // イベントソースが生成されたときの処理を行います。 static void listener_EventSourceCreated(object sender, EventSourceCreatedEventArgs e) { // 割愛 } // ログが書き込まれたときの処理を行います。 static void listener_EventWritten(object sender, EventWrittenEventArgs e) { // 割愛 } }ETW の最も大きなメリットは out-process なログ出力(ログを発生させるプロセスとログを記録するプロセスを分離する)ができることだと思うのですが、残念ながらこの EventListener を使った方法は out-process には対応していないようです。
out-process なログ出力を実現する方法には EtwStream があります。私も以前に EtwStream の動作確認をしたことがあり、C# ETW(Event Tracing for Windows)からログを取得する で紹介しています。まとめ
ログプロバイダとログレベル/フィルタを柔軟に構成できることがわかりました。ただ、EventLogger と EventSourceLogger 関連のやや不可解な挙動については情報の整理が必要であるとも感じました。
- 投稿日:2020-01-19T13:31:54+09:00
NuGetを使わずにNpgsqlをインストールする
課題
Npgsql をインストールしようとしたら、どの記事も NuGet の例ばかりだった。しかしなぜか自分の環境だと NuGet でダウンロードしようとしても、パッケージマネージャーが何も応答しない。そこで NuGet 以外でインストールする方法を調べた。
方法
https://symfoware.blog.fc2.com/blog-entry-2386.html の記事通りすることで解決。
https://github.com/npgsql/npgsql/releases にNpgsql v4.1.2のmsiインストーラがあるので、それをインストールするとC:\Windows\Microsoft.NET\assembly\GAC_MSIL\Npgsql\v4.0_4.1.2.0__5d8b90d52f46fda7
の下あたりにNpgsql.dllが見つかるはずだ。
- 投稿日:2020-01-19T13:31:54+09:00
NuGetできない場合にNpgsqlをインストールする
課題
Npgsql をインストールしようとしたら、どの記事も NuGet の例ばかりだった。しかしなぜか自分の環境だと NuGet でダウンロードしようとしても、パッケージマネージャーが何も応答しない。そこで NuGet 以外でインストールする方法を調べた。
対策
https://symfoware.blog.fc2.com/blog-entry-2386.html の記事通りすることで解決。
https://github.com/npgsql/npgsql/releases にNpgsql v4.1.2のmsiインストーラがあるので、それをインストールするとC:\Windows\Microsoft.NET\assembly\GAC_MSIL\Npgsql\v4.0_4.1.2.0__5d8b90d52f46fda7
の下あたりにNpgsql.dllが見つかるはずだ。
- 投稿日:2020-01-19T12:29:03+09:00
Awake, Start, Updateの違い
Awake
- 全てのオブジェクトが初期化された後に読み込まれる。
- 必ずStartの前に読み込まれる。
- scriptが有効か無効かに関わらず読み込まれる。(Unity上のチェックを外しても読み込まれます。)
Start
- Updateメソッドが最初に読みこまれる直前に読み込まれる。
- scriptが無効だと読み込まれない。
Update
- 毎フレーム読み込まれる。
- パソコンの性能によって読み込まれる回数が変わってくるため、Time.deltaTimeを使って性能差を埋める必要がある。
MonoBehaviourとは
- Unityで読み込まれる全てのscriptのベースクラス。
- C#を使うときは、MonoBehaviourから引き出して使う。
- 投稿日:2020-01-19T11:50:09+09:00
c# メモ イベント
public sealed class EventSample { public delegate void EventHandler(); public event EventHandler Event = () => { }; void Do() { Event() } public class Sample { EventSample sample; void Regist() { sample.Event += DoSomething; } private void DoSomething() { } } }
- 投稿日:2020-01-19T11:39:57+09:00
C# メモ
using System; using System.Threading.Tasks; public sealed class Timer { private Action _action; private int _ms; private bool _setsTime; private bool _setsAction; private bool _excecuteStartAsyncBefore; public void SetTime(int ms) { CheckSetsTimeYet(); _setsTime = true; _ms = ms; } public void SetAction(Action action) { CheckSetsActionYet(); _setsAction = true; _action = action; } public async void StartAsync() { CheckReady(); _excecuteStartAsyncBefore = true; await Task.Delay(_ms); Fire(); } private void Fire() { _action.Invoke(); } private void CheckReady() { CheckSetNeccesaryValues(); CheckFirstExcecte(); } private void CheckSetNeccesaryValues() { if (!_setsTime || !_setsAction) throw new Exception("Set Time and Action"); } private void CheckFirstExcecte() { if (_excecuteStartAsyncBefore) throw new Exception("Not first execute"); } private void CheckSetsTimeYet() { if (_setsTime) throw new Exception("Already Set Time"); } private void CheckSetsActionYet() { if (_setsAction) throw new Exception("Already Set Action"); } }
- 投稿日:2020-01-19T04:44:32+09:00
Visual Studio 2019でxUnitを使おうとしてハマったことメモ
前提
- Visual Studio 2019 のIDE上
- Chaining Assertion の話もあり、というか xUnit.net でユニットテストを始める が動くまで
xUnitプロジェクトの作成で躓く
VS2019の「xUnit テストプロジェクト(.NET Core)」で、プロジェクトを追加したら「コンパイルエラー(CS0246)」のエラーが3つ出ました。
トラブル解決のため焦っており、正確な所は失念しましたが、少なくとも
型または名前空間の名前 'Fact' が見つかりませんでした (using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
という、xUnitを使う上では致命的なエラーが出ていました。
「ソリューションのNuGetパッケージの管理」から見ると、以下のパッケージが最新版ではありませんでした。
- coverlet.collector
- Microsoft.NET.Test.Sdk
- xunit
- xunit.runner.visualstudio
ここでパッケージ最新版への更新を試みると C:\Program Files\dotnet\sdk\NuGetFallbackFolder の中にある System.AppContext.4.1.0 がエラー、などの不穏が発生。
該当のエラーが出ているフォルダは削除してよさそうな情報が見つかったので、エラーが出るフォルダを1つ1つ削除していったら、今度はフォルダ特定できないようなエラー発生。結論を言うと、キレ気味に NuGetFallbackFolder そのものを削除したら、すんなり更新できました。
Chaining Assertionの導入で躓く
冒頭に出した記事では「githubから直接コードをコピってテストプロジェクトにぶち込むのが良い」と書いてありましたが、正直美しくないと感じました。あくまで個人の主観ですが。
少なくともgithubのモノとバージョンは同一っぽいので、NuGetから入れても、何も起こらない。
(1+1).Is(2)
とやっても Is が定義されてない云々、と。これは、正直参りましたが ChainingAssertion-xUnit ではなく ChainingAssertion-xUnit.Bin を入れるのが正解のようです。
最後に
これ、いわゆるおま環「おまえの環境の問題」かなーとも思ったんですよね…ただ、もし同じことに引っかかってる人がいたらと思い、ざっくりと記事に起こしました。
おまけ
ちゃんと C# のコードを書ける人には一目瞭然でしょうけど、xUnit.net でユニットテストを始めるのコードは、テストプロジェクトを簡単に動かすため、テスト対象コードをテストプロジェクト内に書いているようです。
実際にテストコードを書くときは、ちゃんとテストプロジェクトとプロダクトプロジェクトを分離しましょうね…大きなお世話だったかな?