├── .gitattributes
├── .github
└── workflows
│ ├── dotnet-build.yml
│ └── nuget-push.yml
├── .gitignore
├── .vscode
└── settings.json
├── Directory.Build.props
├── How to Contribute.md
├── LICENSE
├── MSTest.Extensions.sln
├── MSTest.Extensions.sln.DotSettings
├── README.md
├── build
├── GenericGenerator
│ ├── GenericGenerator.csproj
│ ├── GenericTypeGenerator.cs
│ └── Program.cs
├── Version.props
└── code-style.ruleset
├── demo
└── dotnetCampus.UITest.WPF.Demo
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── AssemblyInfo.cs
│ ├── FooTest.cs
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ └── dotnetCampus.UITest.WPF.Demo.csproj
├── docs
├── README.md
├── en
│ └── README.md
├── images
│ ├── 2018-02-12-08-54-31.png
│ ├── 2018-02-13-13-09-26.png
│ ├── 2018-02-13-14-35-29.png
│ ├── 2018-02-27-11-11-10.png
│ ├── 2018-02-27-11-15-09.png
│ ├── 2018-02-27-11-32-59.png
│ ├── 2018-02-27-11-33-49.png
│ ├── 2018-02-27-11-36-50.png
│ ├── 2018-02-27-11-38-16.png
│ ├── 2018-02-27-11-39-09.png
│ ├── 2018-02-27-11-48-07.png
│ ├── 2018-02-27-11-50-18.png
│ ├── 2018-02-27-11-51-43.png
│ ├── 2018-02-27-11-56-40.png
│ ├── 2018-02-27-11-59-28.png
│ ├── 2018-02-27-12-01-29.png
│ ├── 2018-02-27-12-16-05.png
│ ├── 2018-02-27-12-19-51.png
│ ├── unit-test-result-of-demo.jp.png
│ ├── unit-test-result-of-demo.png
│ ├── unit-test-result-of-demo.zh-chs.png
│ └── unit-test-result-of-demo.zh-cht.png
├── jp
│ └── README.jp.md
├── zh-chs
│ ├── README.md
│ ├── README.zh-chs.md
│ └── resharper-code-templates.md
└── zh-cht
│ ├── README.md
│ └── README.zh-cht.md
├── icon.png
├── src
├── MSTest.Extensions
│ ├── AssertExtensions
│ │ ├── AssertExtensions.cs
│ │ ├── CollectionAssertExtensions.cs
│ │ └── StringAssertExtensions.cs
│ ├── Contracts
│ │ ├── ContractTest.01.cs
│ │ ├── ContractTest.cs
│ │ ├── ContractTestCaseAttribute.cs
│ │ ├── ContractTestConfiguration.cs
│ │ └── ContractTestContext.cs
│ ├── Core
│ │ ├── ContractTestCase.cs
│ │ ├── ITestCase.cs
│ │ ├── ReadonlyTestCase.cs
│ │ ├── TestCaseIndexer.cs
│ │ ├── TestMethodProxy.cs
│ │ └── ThreadSafeStringWriter.cs
│ ├── CustomTestManagers
│ │ ├── CustomTestManager.cs
│ │ ├── CustomTestManagerRunContext.cs
│ │ ├── TestExceptionResult.cs
│ │ └── TestManagerRunResult.cs
│ ├── MSTest.Extensions.csproj
│ ├── Properties
│ │ ├── Annotations.cs
│ │ └── AssemblyInfo.cs
│ └── Utils
│ │ ├── Constants.cs
│ │ └── ReflectionExtensions.cs
└── dotnetCampus.UITest.WPF
│ ├── UIContractTestCaseAttribute.cs
│ ├── UITestManager.cs
│ ├── UITestMethodProxy.cs
│ └── dotnetCampus.UITest.WPF.csproj
└── tests
└── MSTest.Extensions.Tests
├── Contracts
├── ContractTestCaseAttributeTests.cs
├── ContractTestContextTests.cs
├── ContractTestGenericTests.cs
├── ContractTestInitilizeCleanupTests.cs
└── ContractTestTests.cs
├── Core
├── ContractTestCaseTests.cs
└── TestCaseIndexerTests.cs
├── MSTest.Extensions.Tests.csproj
└── Properties
└── GlobalSuppressions.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-build.yml:
--------------------------------------------------------------------------------
1 | name: .NET Build & Test
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | strategy:
12 | matrix:
13 | configuration: [Debug, Release]
14 | runs-on: windows-latest
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v2
18 |
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@v1
21 | with:
22 | dotnet-version: |
23 | 3.1.x
24 | 5.0.x
25 | 6.0.x
26 |
27 | - name: Build
28 | run: dotnet build --configuration $env:Configuration
29 | env:
30 | Configuration: ${{ matrix.configuration }}
31 | - name: Test
32 | run: dotnet test --configuration $env:Configuration
33 | env:
34 | Configuration: ${{ matrix.configuration }}
35 |
--------------------------------------------------------------------------------
/.github/workflows/nuget-push.yml:
--------------------------------------------------------------------------------
1 | name: NuGet Push
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | build:
10 | runs-on: windows-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v2
14 |
15 | - name: Setup .NET
16 | uses: actions/setup-dotnet@v1
17 | with:
18 | dotnet-version: |
19 | 3.1.x
20 | 5.0.x
21 | 6.0.x
22 |
23 | - name: Install tool
24 | run: dotnet tool install -g dotnetCampus.TagToVersion
25 |
26 | - name: Set tag to version
27 | run: dotnet TagToVersion -t ${{ github.ref }}
28 |
29 | - name: Pack
30 | run: dotnet build --configuration Release
31 | - name: Push
32 | run: dotnet nuget push .\bin\Release\*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NugetKey }} --skip-duplicate --no-symbols 1
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
262 |
263 | # GenericGeneratedCode
264 | **/*.02.cs
265 | **/*.03.cs
266 | **/*.04.cs
267 | **/*.05.cs
268 | **/*.06.cs
269 | **/*.07.cs
270 | **/*.08.cs
271 | **/*.09.cs
272 | **/*.10.cs
273 | **/*.11.cs
274 | **/*.12.cs
275 | **/*.13.cs
276 | **/*.14.cs
277 | **/*.15.cs
278 | **/*.16.cs
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "pasteImage.path": "${projectRoot}/docs/images",
3 | "pasteImage.basePath": "${projectRoot}",
4 | "pasteImage.prefix": "/"
5 | }
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildThisFileDirectory)bin\$(Configuration)
5 | dotnet-campus
6 | dotnet-campus
7 | latest
8 | false
9 | source;dotnet;nuget;msbuild
10 | MIT
11 |
12 |
13 |
14 | icon.png
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/How to Contribute.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 | ## Contributing to Code
3 | There are many ways to contribute to the Code project: logging bugs, submitting pull requests, reporting issues, and creating suggestions.
4 |
5 | After cloning and building the repo, check out the [issues list](https://github.com/dotnet-campus/MSTestEnhancer/issues). Issues are good candidates to pick up if you are in the code for the first time.
6 |
7 | ## Build and Run
8 | If you want to understand how Code works or want to debug an issue, you'll want to get the source, build it, and run the tool locally.
9 |
10 | ### Getting the sources
11 | git clone https://github.com/dotnet-campus/MSTestEnhancer.git
12 |
13 | ### Prerequisites
14 | + [Git](https://git-scm.com/)
15 | + [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard)
16 | + [.NET Core](https://docs.microsoft.com/en-us/dotnet/core/)
17 | + [Unit Test](https://msdn.microsoft.com/en-us/library/dd264975.aspx)
18 | + [MStest V2](https://github.com/Microsoft/testfx)
19 |
20 | Finally, install MSTest V2 packages using Nuget:
21 |
22 | Install-Package MSTest.TestFramework
23 |
24 | ### Build
25 | + Building with Visual Studio(VS)
26 |
27 | You can open /MSTest.Extensions.sln in VS and trigger a build of the entire code base using Build Solution(Ctrl+Shift+B) from the Solution Explorer or the Build menu.
28 |
29 | The bits get dropped at /bin/Debug/.
30 |
31 | + Building with command line(CLI)
32 |
33 | MSBuild.exe /MSTest.Extensions.sln
34 |
35 | ### Test
36 | + Running tests with Visual Studio
37 |
38 | All the tests in the MSTestEnhancer repo can be run via the Visual Studio Test Explorer. Building /MSTest.Extensions.sln as described in the build section above should populate all the tests in the Test Explorer.
39 |
40 | + Running tests with command line(CLI)
41 |
42 | vstest.console.exe /tests/MSTest.Extensions.Tests/bin/Debug/net47/MSTest.Extensions.Tests.dll
43 |
44 |
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 dotnet职业技术学院
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MSTest.Extensions.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest.Extensions", "src\MSTest.Extensions\MSTest.Extensions.csproj", "{055E6C63-3CC7-427E-B14F-69C85D7E971A}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{1C4D72B5-A7C1-4AAE-A66F-4DDAC5979C28}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DA39C42F-B4EA-43F0-AB8C-40987B63F4D6}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MSTest.Extensions.Tests", "tests\MSTest.Extensions.Tests\MSTest.Extensions.Tests.csproj", "{7AB54453-67E4-46BC-A314-EBA72C082E7E}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_SolutionItems", "_SolutionItems", "{F30DFDFF-C789-402F-888A-385F1D38EB11}"
15 | ProjectSection(SolutionItems) = preProject
16 | .gitattributes = .gitattributes
17 | .gitignore = .gitignore
18 | build\code-style.ruleset = build\code-style.ruleset
19 | LICENSE = LICENSE
20 | EndProjectSection
21 | EndProject
22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.UITest.WPF", "src\dotnetCampus.UITest.WPF\dotnetCampus.UITest.WPF.csproj", "{E62C4947-CD69-411D-8749-78C8B2C25ACE}"
23 | EndProject
24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnetCampus.UITest.WPF.Demo", "demo\dotnetCampus.UITest.WPF.Demo\dotnetCampus.UITest.WPF.Demo.csproj", "{F1D52FE3-2E23-4C7D-AA64-CAC204B4EBBF}"
25 | EndProject
26 | Global
27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
28 | Debug|Any CPU = Debug|Any CPU
29 | Release|Any CPU = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
32 | {055E6C63-3CC7-427E-B14F-69C85D7E971A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {055E6C63-3CC7-427E-B14F-69C85D7E971A}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {055E6C63-3CC7-427E-B14F-69C85D7E971A}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {055E6C63-3CC7-427E-B14F-69C85D7E971A}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {7AB54453-67E4-46BC-A314-EBA72C082E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {7AB54453-67E4-46BC-A314-EBA72C082E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {7AB54453-67E4-46BC-A314-EBA72C082E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {7AB54453-67E4-46BC-A314-EBA72C082E7E}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {E62C4947-CD69-411D-8749-78C8B2C25ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {E62C4947-CD69-411D-8749-78C8B2C25ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {E62C4947-CD69-411D-8749-78C8B2C25ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {E62C4947-CD69-411D-8749-78C8B2C25ACE}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {F1D52FE3-2E23-4C7D-AA64-CAC204B4EBBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {F1D52FE3-2E23-4C7D-AA64-CAC204B4EBBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {F1D52FE3-2E23-4C7D-AA64-CAC204B4EBBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {F1D52FE3-2E23-4C7D-AA64-CAC204B4EBBF}.Release|Any CPU.Build.0 = Release|Any CPU
48 | EndGlobalSection
49 | GlobalSection(SolutionProperties) = preSolution
50 | HideSolutionNode = FALSE
51 | EndGlobalSection
52 | GlobalSection(NestedProjects) = preSolution
53 | {055E6C63-3CC7-427E-B14F-69C85D7E971A} = {1C4D72B5-A7C1-4AAE-A66F-4DDAC5979C28}
54 | {7AB54453-67E4-46BC-A314-EBA72C082E7E} = {DA39C42F-B4EA-43F0-AB8C-40987B63F4D6}
55 | {E62C4947-CD69-411D-8749-78C8B2C25ACE} = {1C4D72B5-A7C1-4AAE-A66F-4DDAC5979C28}
56 | {F1D52FE3-2E23-4C7D-AA64-CAC204B4EBBF} = {DA39C42F-B4EA-43F0-AB8C-40987B63F4D6}
57 | EndGlobalSection
58 | GlobalSection(ExtensibilityGlobals) = postSolution
59 | SolutionGuid = {21BFBA8A-2901-4A78-A722-0DDA95D3773F}
60 | EndGlobalSection
61 | EndGlobal
62 |
--------------------------------------------------------------------------------
/MSTest.Extensions.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
3 | 055E6C63-3CC7-427E-B14F-69C85D7E971A/d:Properties
4 | *.02.cs
5 | *.03.cs
6 | *.04.cs
7 | *.05.cs
8 | *.06.cs
9 | *.07.cs
10 | *.08.cs
11 | True
12 | ERROR
13 | ERROR
14 | ERROR
15 | ERROR
16 | ERROR
17 | ERROR
18 | ERROR
19 | ERROR
20 | ERROR
21 | WARNING
22 | ERROR
23 | ERROR
24 | ERROR
25 | ERROR
26 | ERROR
27 | ERROR
28 | ERROR
29 | ERROR
30 | ERROR
31 | ERROR
32 | ERROR
33 | ERROR
34 | ERROR
35 | ERROR
36 | ERROR
37 | ERROR
38 | WARNING
39 | ERROR
40 | ERROR
41 | ERROR
42 | ERROR
43 | ERROR
44 | ERROR
45 | ERROR
46 | WARNING
47 | WARNING
48 | True
49 | True
50 | Contract Unit Test
51 | 4
52 | Test Fixture
53 | 5
54 | True
55 | True
56 | Unit Tests
57 | Convert to MSTestEnhancer test case
58 | True
59 | constant("Please type a test contract here...")
60 | 0
61 | True
62 | 5
63 | True
64 | True
65 | 5.0
66 | InCSharpStatement
67 | True
68 | 5.0
69 | InCSharpExpression
70 | test
71 | True
72 | "$contract$".Test(() =>
73 | {
74 | $SELECTION$
75 | });
76 | True
77 | True
78 | Unit Tests
79 | cs
80 | UnitTest
81 | False
82 | Contract Unit Test
83 | True
84 | getAlphaNumericFileNameWithoutExtension()
85 | -1
86 | 2
87 | True
88 | fileheader()
89 | 0
90 | True
91 | fileDefaultNamespace()
92 | -1
93 | 1
94 | True
95 | True
96 | InCSharpProjectFile
97 | True
98 | $HEADER$using MSTest.Extensions.Contracts;
99 | namespace $NAMESPACE$
100 | {
101 | public class $CLASS$
102 | {
103 | [MSTest.Extensions.Contracts.ContractTestCase]
104 | public void Test1()
105 | {
106 | "$SELSTART$Please type a test contract here...$SELEND$".Test(() =>
107 | {
108 | // Arrange
109 |
110 | // Action
111 |
112 | // Assert
113 | });
114 | }
115 | }
116 | }
117 | True
118 | True
119 | Unit Tests
120 | Insert a test case
121 | True
122 | constant("Please type a test contract here...")
123 | 0
124 | True
125 | True
126 | 5.0
127 | InCSharpStatement
128 | True
129 | 5.0
130 | InCSharpExpression
131 | test
132 | True
133 | "$contract$".Test(() =>
134 | {
135 | // Arrange
136 | $SELSTART$// TODO: Write your test case here...$SELEND$
137 |
138 | // Action
139 |
140 | // Assert
141 | });
142 | True
143 | True
144 | Unit Tests
145 | Insert a test method
146 | True
147 | constant("Please type a test contract here...")
148 | 1
149 | True
150 | 0
151 | True
152 | True
153 | 5.0
154 | InCSharpTypeMember
155 | test
156 | True
157 | [MSTest.Extensions.Contracts.ContractTestCase]
158 | public void $TheMethodNameYouWantToTest$()
159 | {
160 | "$contract$".Test(() =>
161 | {
162 | // Arrange
163 | $SELSTART$// TODO: Write your test case here...$SELEND$
164 |
165 | // Action
166 |
167 | // Assert
168 | });
169 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [English][en]|[日本語][jp]|[简体中文][zh-chs]|[繁體中文][zh-cht]
2 | -|-|-|-
3 |
4 | [en]: /README.md
5 | [jp]: /docs/jp/README.jp.md
6 | [zh-chs]: /docs/zh-chs/README.zh-chs.md
7 | [zh-cht]: /docs/zh-cht/README.zh-cht.md
8 |
9 |  
10 |
11 |
12 | | Name | NuGet|
13 | |--|--|
14 | |MSTestEnhancer|[](https://www.nuget.org/packages/MSTestEnhancer)|
15 | |dotnetCampus.UITest.WPF|[](https://www.nuget.org/packages/dotnetCampus.UITest.WPF)|
16 |
17 | # CUnit
18 |
19 | Don't you think that naming is very very hard? Especially naming for unit test method? Read this article for more data of naming: [Don’t go into programming if you don’t have a good thesaurus - ITworld](https://www.itworld.com/article/2833265/cloud-computing/don-t-go-into-programming-if-you-don-t-have-a-good-thesaurus.html).
20 |
21 | CUnit (MSTestEnhancer) helps you to write unit tests without naming any method.
22 |
23 | CUnit is a contract-style unit test extension for MSTestv2. You can write method contract descriptions instead of writing confusing test method name when writing unit tests.
24 |
25 | ---
26 |
27 | ## Getting Started with CUnit
28 |
29 | You can write unit test like this:
30 |
31 | ```csharp
32 | [TestClass]
33 | public class DemoTest
34 | {
35 | [ContractTestCase]
36 | public void Foo()
37 | {
38 | "When A happened, result A'.".Test(() =>
39 | {
40 | // Arrange
41 | // Action
42 | // Assert
43 | });
44 |
45 | "But when B happened, result B'".Test(() =>
46 | {
47 | // Arrange
48 | // Action
49 | // Assert
50 | });
51 | }
52 | }
53 | ```
54 |
55 | Then you'll see this kind of test result in testing explorer window:
56 |
57 | 
58 |
59 | For more usages, please visit:
60 |
61 | - [English](/README.md)
62 | - [日本語](/docs/jp/README.md)
63 | - [简体中文](/docs/zh-chs/README.md)
64 | - [繁體中文](/docs/zh-cht/README.md)
65 |
66 | ### Contributing Guide
67 |
68 | There are many ways to contribute to MSTestEnhancer
69 |
70 | - [Submit issues](https://github.com/dotnet-campus/MSTestEnhancer/issues) and help verify fixes as they are checked in.
71 | - Review the [documentation changes](https://github.com/dotnet-campus/MSTestEnhancer/pulls).
72 | - [How to Contribute](How%20to%20Contribute.md)
73 |
74 | ## License
75 |
76 | MSTestEnhancer is licensed under the [MIT license](/LICENSE)
77 |
--------------------------------------------------------------------------------
/build/GenericGenerator/GenericGenerator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 | bin\
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/build/GenericGenerator/GenericTypeGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace GenericGenerator
5 | {
6 | public class GenericTypeGenerator
7 | {
8 | private const string ToolName = "https://github.easiwin.io/mstest-enhancer";
9 | private const string ToolVersion = "1.0";
10 |
11 | private static readonly string GeneratedHeader =
12 | $@"//------------------------------------------------------------------------------
13 | //
14 | // This code was generated by a tool.
15 | // Runtime Version:{Environment.Version.ToString(4)}
16 | //
17 | // Changes to this file may cause incorrect behavior and will be lost if
18 | // the code is regenerated.
19 | //
20 | //------------------------------------------------------------------------------
21 |
22 | #define GENERATED_CODE
23 | ";
24 |
25 | private static readonly string GeneratedFooter =
26 | $@"";
27 |
28 | private static readonly string Generatedattribute =
29 | $"[System.CodeDom.Compiler.GeneratedCode(\"{ToolName}\", \"{ToolVersion}\")]";
30 |
31 | private readonly string _genericTemplate;
32 |
33 | public GenericTypeGenerator(string genericTemplate)
34 | {
35 | _genericTemplate = genericTemplate;
36 | }
37 |
38 | public string Generate(int genericCount)
39 | {
40 | var content = _genericTemplate
41 | // 替换泛型。
42 | .Replace("{ForT(t)}", FromTemplate("{0}", "{ForT(t{n})}", ", ", genericCount))
43 | .Replace("ForT(t)", FromTemplate("{0}", "ForT(t{n})", ", ", genericCount))
44 | .Replace(" T[] ", FromTemplate(" ({0})[] ", "T{n}", ", ", genericCount))
45 | .Replace("", FromTemplate("<{0}>", "out T{n}", ", ", genericCount))
46 | .Replace("Task", FromTemplate("Task<({0})>", "T{n}", ", ", genericCount))
47 | .Replace("Func", FromTemplate("Func<{0}, Task>", "T{n}", ", ", genericCount))
48 | .Replace(" T, Task>", FromTemplate(" {0}, Task>", "T{n}", ", ", genericCount))
49 | .Replace("(T, bool", FromTemplate("({0}, bool", "T{n}", ", ", genericCount))
50 | .Replace("var (t, ", FromTemplate("var ({0}, ", "t{n}", ", ", genericCount))
51 | .Replace("var t in", FromTemplate("var ({0}) in", "t{n}", ", ", genericCount))
52 | .Replace(", t)", FromTemplate(", {0})", "t{n}", ", ", genericCount))
53 | .Replace("return (t, ", FromTemplate("return ({0}, ", "t{n}", ", ", genericCount))
54 | .Replace("({t})", FromTemplate("({0})", "{t{n}}", ", ", genericCount))
55 | .Replace("", FromTemplate("<{0}>", "T{n}", ", ", genericCount))
56 | .Replace("{T}", FromTemplate("{{{0}}}", "T{n}", ", ", genericCount))
57 | .Replace("(T value)", FromTemplate("(({0}) value)", "T{n}", ", ", genericCount))
58 | .Replace("(T t)", FromTemplate("({0})", "T{n} t{n}", ", ", genericCount))
59 | .Replace("(t)", FromTemplate("({0})", "t{n}", ", ", genericCount))
60 | .Replace("var t =", FromTemplate("var ({0}) =", "t{n}", ", ", genericCount))
61 | .Replace(" t =>", FromTemplate(" ({0}) =>", "t{n}", ", ", genericCount))
62 | .Replace(" T ", FromTemplate(" ({0}) ", "T{n}", ", ", genericCount))
63 | .Replace(" t;", FromTemplate(" ({0});", "t{n}", ", ", genericCount))
64 | // 生成 [GeneratedCode]。
65 | .Replace(" public interface ", $" {Generatedattribute}{Environment.NewLine} public interface ")
66 | .Replace(" public class ", $" {Generatedattribute}{Environment.NewLine} public class ")
67 | .Replace(" public sealed class ", $" {Generatedattribute}{Environment.NewLine} public sealed class ");
68 | return GeneratedHeader + Environment.NewLine + content.Trim() + Environment.NewLine + GeneratedFooter;
69 | }
70 |
71 | private static string FromTemplate(string template, string part, string seperator, int count)
72 | {
73 | return string.Format(template,
74 | string.Join(seperator, Enumerable.Range(1, count).Select(x => part.Replace("{n}", x.ToString()))));
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/build/GenericGenerator/Program.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Linq;
3 | using System.Text;
4 |
5 | namespace GenericGenerator
6 | {
7 | class Program
8 | {
9 | static void Main(string[] args)
10 | {
11 | foreach (var argument in args)
12 | {
13 | GenerateGenericTypes(argument, 8);
14 | }
15 | }
16 |
17 | private static void GenerateGenericTypes(string file, int count)
18 | {
19 | // 读取原始文件并创建泛型代码生成器。
20 | var template = File.ReadAllText(file, Encoding.UTF8);
21 | var generator = new GenericTypeGenerator(template);
22 |
23 | // 根据泛型个数生成目标文件路径和文件内容。
24 | var format = GetIndexedFileNameFormat(file);
25 | (string targetFileName, string targetFileContent)[] contents = Enumerable.Range(2, count - 1).Select(i =>
26 | (string.Format(format, i.ToString().PadLeft(2, '0')), generator.Generate(i))
27 | ).ToArray();
28 |
29 | // 写入目标文件。
30 | foreach (var writer in contents)
31 | {
32 | File.WriteAllText(writer.targetFileName, writer.targetFileContent);
33 | }
34 | }
35 |
36 | private static string GetIndexedFileNameFormat(string fileName)
37 | {
38 | var directory = Path.GetDirectoryName(fileName);
39 | var name = Path.GetFileNameWithoutExtension(fileName);
40 | if (name.EndsWith(".01"))
41 | {
42 | name = name.Substring(0, name.Length - 3);
43 | }
44 | if (name.EndsWith("1"))
45 | {
46 | name = name.Substring(0, name.Length - 1);
47 | }
48 |
49 | return Path.Combine(directory, name + ".{0}.cs");
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/build/Version.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2.0.1
4 |
5 |
--------------------------------------------------------------------------------
/build/code-style.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/demo/dotnetCampus.UITest.WPF.Demo/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/dotnetCampus.UITest.WPF.Demo/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace dotnetCampus.UITest.WPF.Demo
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/demo/dotnetCampus.UITest.WPF.Demo/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/demo/dotnetCampus.UITest.WPF.Demo/FooTest.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Threading.Tasks;
3 | using System.Windows;
4 | using System.Windows.Threading;
5 |
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | using MSTest.Extensions.Contracts;
9 | using MSTest.Extensions.Utils;
10 |
11 | namespace dotnetCampus.UITest.WPF.Demo
12 | {
13 | [TestClass]
14 | public class FooTest
15 | {
16 | [AssemblyInitialize]
17 | public static void InitializeApplication(TestContext testContext)
18 | {
19 | UITestManager.InitializeApplication(() => new App());
20 | }
21 |
22 | [UIContractTestCase]
23 | public void TestAsyncLoad()
24 | {
25 | "Waiting with async Loaded, then it do not lock UI Thread.".Test(async () =>
26 | {
27 | var mainWindow = new MainWindow();
28 | var taskCompletionSource = new TaskCompletionSource();
29 | mainWindow.Loaded += (sender, args) => taskCompletionSource.SetResult();
30 | await mainWindow.Dispatcher.InvokeAsync(mainWindow.Show);
31 | await taskCompletionSource.Task;
32 | });
33 | }
34 |
35 | [UIContractTestCase]
36 | public void TestMainWindow()
37 | {
38 | "Test Open MainWindow, MainWindow be opened".Test(() =>
39 | {
40 | Assert.AreEqual(Application.Current.Dispatcher, Dispatcher.CurrentDispatcher);
41 | var mainWindow = new MainWindow();
42 | bool isMainWindowLoaded = false;
43 | mainWindow.Loaded += (sender, args) => isMainWindowLoaded = true;
44 | mainWindow.Show();
45 | Assert.AreEqual(true, isMainWindowLoaded);
46 | });
47 |
48 | "Test Close MainWindow, MainWindow be closed".Test(() =>
49 | {
50 | var window = Application.Current.MainWindow;
51 | Assert.AreEqual(true, window is MainWindow);
52 | bool isMainWindowClosed = false;
53 | Assert.IsNotNull(window);
54 | window.Closed += (sender, args) => isMainWindowClosed = true;
55 | window.Close();
56 | Assert.AreEqual(true, isMainWindowClosed);
57 | });
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/demo/dotnetCampus.UITest.WPF.Demo/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/dotnetCampus.UITest.WPF.Demo/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 |
16 | namespace dotnetCampus.UITest.WPF.Demo
17 | {
18 | ///
19 | /// Interaction logic for MainWindow.xaml
20 | ///
21 | public partial class MainWindow : Window
22 | {
23 | public MainWindow()
24 | {
25 | InitializeComponent();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/demo/dotnetCampus.UITest.WPF.Demo/dotnetCampus.UITest.WPF.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | exe
5 | true
6 | net6.0-windows
7 | enable
8 | true
9 | false
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Designer
19 | MSBuild:Compile
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/README.md
--------------------------------------------------------------------------------
/docs/en/README.md:
--------------------------------------------------------------------------------
1 | # MSTest Enhancer
2 |
3 | MSTestEnhancer is a MSTest v2 extension to connect unit test and the method that should be tested. You'll find out that all unit test contracts are listed under target methods, and you can see all the result of them directly, no need to translate the obscure method name into what you want to test.
4 |
5 | ## Getting Started
6 |
7 | 1. Install [MSTestEnhancer](https://www.nuget.org/packages/MSTestEnhancer/) from the [nuget.org](https://www.nuget.org/).
8 | 1. Write unit test code in the style listed below.
9 |
10 | ## Recommended Style of Writing Unit Tests
11 |
12 | Assuming that you want to test a class named `TheTestedClass` containing a method named `TheTestedMethod`. Then you can write unit tests like this:
13 |
14 | ```csharp
15 | [TestClass]
16 | public class TheTestedClassTest
17 | {
18 | [ContractTestCase]
19 | public void TheTestedMethod()
20 | {
21 | "When Xxx happens, results in Yyy.".Test(() =>
22 | {
23 | // Write test case code here...
24 | });
25 |
26 | "When Zzz happens, results in Www.".Test(() =>
27 | {
28 | // Write test case code here...
29 | });
30 | }
31 | }
32 | ```
33 |
34 | Notice that the name of class and method are almost the name of the tested class and tested method. As a result, we don't need to think about anything about naming unit test, nor to read the obscure name of the unit test.
35 |
36 | 
37 |
38 | ## Unit Test with Arguments
39 |
40 | Some unit tests need multiple values to verify the contracts, so MSTestEnhancer provides `WithArguments` method to config the arguments.
41 |
42 | ```csharp
43 | "prime number.".Test((int num) =>
44 | {
45 | // Write test case code here...
46 | }).WithArguments(2, 3, 5, 7, 11);
47 |
48 | "{0} is not a prime number.".Test((int num) =>
49 | {
50 | // Write test case code here...
51 | }).WithArguments(1, 4);
52 | ```
53 |
54 | You can pass up to 8 parameters into the test case.
55 |
56 | ```csharp
57 | "Contract 1: {0} and {1} are allowed in the contract description.".Test((int a, int b) =>
58 | {
59 | // Now, a is 2 and b is 3.
60 | }).WithArguments(2, 3);
61 |
62 | "Contract 2".Test((int a, int b) =>
63 | {
64 | // Now the test case will run twice. The first group, a is 2 and b is 3; and the second group, a is 10 and b is 20.
65 | // ValueTuple is supported, too.
66 | }).WithArguments((2, 3), (10, 20));
67 | ```
68 |
69 | In this example, the contract description will be replaced to the arguments that you have passed into.
70 |
71 | ## Async Unit Test
72 |
73 | All `Test` extension method support async action so that you can test any async method.
74 |
75 | ## Some Fantastic Feature
76 |
77 | Nested unit test classes are supported by MSTest v2, so you can write an infinite level unit test tree.
78 |
--------------------------------------------------------------------------------
/docs/images/2018-02-12-08-54-31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-12-08-54-31.png
--------------------------------------------------------------------------------
/docs/images/2018-02-13-13-09-26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-13-13-09-26.png
--------------------------------------------------------------------------------
/docs/images/2018-02-13-14-35-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-13-14-35-29.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-11-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-11-10.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-15-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-15-09.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-32-59.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-32-59.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-33-49.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-33-49.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-36-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-36-50.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-38-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-38-16.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-39-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-39-09.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-48-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-48-07.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-50-18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-50-18.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-51-43.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-51-43.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-56-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-56-40.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-11-59-28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-11-59-28.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-12-01-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-12-01-29.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-12-16-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-12-16-05.png
--------------------------------------------------------------------------------
/docs/images/2018-02-27-12-19-51.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/2018-02-27-12-19-51.png
--------------------------------------------------------------------------------
/docs/images/unit-test-result-of-demo.jp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/unit-test-result-of-demo.jp.png
--------------------------------------------------------------------------------
/docs/images/unit-test-result-of-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/unit-test-result-of-demo.png
--------------------------------------------------------------------------------
/docs/images/unit-test-result-of-demo.zh-chs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/unit-test-result-of-demo.zh-chs.png
--------------------------------------------------------------------------------
/docs/images/unit-test-result-of-demo.zh-cht.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/docs/images/unit-test-result-of-demo.zh-cht.png
--------------------------------------------------------------------------------
/docs/jp/README.jp.md:
--------------------------------------------------------------------------------
1 | [English][en]|[日本語][jp]|[简体中文][zh-chs]|[繁體中文][zh-cht]
2 | -|-|-|-
3 |
4 | [en]: /README.md
5 | [jp]: /docs/jp/README.jp.md
6 | [zh-chs]: /docs/zh-chs/README.zh-chs.md
7 | [zh-cht]: /docs/zh-cht/README.zh-cht.md
8 |
9 | # MSTestEnhancer
10 |
11 | 命名は非常に難しいと思いませんか? 特にユニットテストメソッドの命名は? 詳細な命名についてはこの記事をお読みください。
12 |
13 | [あなたが良いシソーラスを持っていない場合、プログラミングに参加しないでください - ITworld](https://www.itworld.com/article/2833265/cloud-computing/don-t-go-into-programming-if-you-don-t-have-a-good-thesaurus.html)。
14 |
15 | MSTestEnhancerは、メソッドの名前を付けずに単体テストを記述するのに役立ちます。
16 |
17 | MSTestEnhancerは、MSTestv2の契約スタイルの単体テスト拡張です。 単体テストを書くときに、混乱しているテストメソッド名を記述するのではなく、メソッド契約の記述を書くことができます。
18 |
19 | ## 入門
20 |
21 | 次のようにユニットテストを書くことができます:
22 |
23 | ```csharp
24 | [TestClass]
25 | public class DemoTest
26 | {
27 | [ContractTestCase]
28 | public void Foo()
29 | {
30 | "A条件が満たされると、Xのことが起こるはずです。".Test(() =>
31 | {
32 | // Arrange
33 | // Action
34 | // Assert
35 | });
36 |
37 | "しかし、あなたがB条件を満たすとき、Y事が起こるはずです。".Test(() =>
38 | {
39 | // Arrange
40 | // Action
41 | // Assert
42 | });
43 | }
44 | }
45 | ```
46 |
47 | 次に、エクスプローラウィンドウのテストでこのようなテスト結果が表示されます。
48 |
49 | 
50 |
--------------------------------------------------------------------------------
/docs/zh-chs/README.md:
--------------------------------------------------------------------------------
1 | # MSTest Enhancer
2 |
3 | 在 MSTestEnhancer 的帮助下,单元测试将更加容易编写、理解和维护。因为它让单元测试方法和被测类本身之间的关系更加紧密。
4 |
5 | ## 如何开始
6 |
7 | 1. 在单元测试项目中安装 NuGet 包:[MSTestEnhancer](https://www.nuget.org/packages/MSTestEnhancer/)。
8 | 1. 现在就可以开始编写下文那种风格的单元测试代码了。
9 |
10 | ## 推荐的单元测试编写方式
11 |
12 | 假设你希望测试一个名为 `被测类名` 的类,其中包含一个名为 `被测方法名` 的方法。你可以这样编写单元测试:
13 |
14 | ```csharp
15 | [TestClass]
16 | public class 被测类名Test
17 | {
18 | [ContractTestCase]
19 | public void 被测方法名()
20 | {
21 | "契约 1(当 Xxx 时,应该发生 Yyy)".Test(() =>
22 | {
23 | // 测试用例代码
24 | });
25 |
26 | "契约 2(但当 Zzz 时,应该发生 Www)".Test(() =>
27 | {
28 | // 测试用例代码
29 | });
30 | }
31 | }
32 | ```
33 |
34 | 注意到单元测试类的名称和原本的类名称是一一对应的,方法名和原本的方法名是一模一样的;于是,我们不再需要为任何一个单元测试思考命名问题。
35 |
36 | 
37 |
38 | ## 参数化的单元测试
39 |
40 | 有些契约需要更多的值组合来验证正确性,那么可以在契约测试用例的后面添加参数。
41 |
42 | ```csharp
43 | "质数".Test((int num) =>
44 | {
45 | // 测试用例代码
46 | }).WithArguments(2, 3, 5, 7, 11);
47 |
48 | "{0} 不是质数".Test((int num) =>
49 | {
50 | // 测试用例代码
51 | }).WithArguments(1, 4);
52 | ```
53 |
54 | 也可以添加多个参数(最多支持 8 个):
55 |
56 | ```csharp
57 | "契约 1,参数中可以带 {0} 和 {1}。".Test((int a, int b) =>
58 | {
59 | // 现在,a 会等于 2,b 会等于 3。
60 | }).WithArguments(2, 3);
61 |
62 | "契约 2".Test((int a, int b) =>
63 | {
64 | // 现在有两组代码,一组 a=2, b=3;另一组 a=10, b=20。
65 | // 当然也可以传入元组数组。
66 | }).WithArguments((2, 3), (10, 20));
67 | ```
68 |
69 | 在显示单元测试结果时,如果契约字符串中含有格式化占位符 `{0}`、`{1}` 等,会被自动替换为参数的值。
70 |
71 | ## 异步的单元测试
72 |
73 | `Test` 方法中传入的每个 `Action` 都支持 `async` 关键字,并会在执行测试用例时等待异步操作结束。
74 |
75 | ## 额外的黑科技
76 |
77 | MSTest v2 支持嵌套类型的单元测试。也就是说,我们可以利用这一点做出近乎无限层级的单元测试树出来。
78 |
--------------------------------------------------------------------------------
/docs/zh-chs/README.zh-chs.md:
--------------------------------------------------------------------------------
1 | [English][en]|[日本語][jp]|[简体中文][zh-chs]|[繁體中文][zh-cht]
2 | -|-|-|-
3 |
4 | [en]: /README.md
5 | [jp]: /docs/jp/README.jp.md
6 | [zh-chs]: /docs/zh-chs/README.zh-chs.md
7 | [zh-cht]: /docs/zh-cht/README.zh-cht.md
8 |
9 | # MSTestEnhancer
10 |
11 | 有没有觉得命名太难?有没有觉得单元测试的命名更难?没错,你不是一个人!看看这个你就知道了:[程序员最头疼的事:命名](http://blog.jobbole.com/50708/#rd?sukey=fc78a68049a14bb285ac0d81ca56806ac10192f4946a780ea3f3dd630804f86056e6fcfe6fcaeddb3dc04830b7e3b3eb) 或它的英文原文 [Don’t go into programming if you don’t have a good thesaurus - ITworld](https://www.itworld.com/article/2833265/cloud-computing/don-t-go-into-programming-if-you-don-t-have-a-good-thesaurus.html)。
12 |
13 | **MSTestEnhancer** 的出现将解决令你头疼的单元测试命名问题——因为,你再也不需要为任何单元测试方法命名了!
14 |
15 | MSTestEnhancer 是 MSTest v2 的一个扩展。使用它,你可以用契约的方式来描述一个又一个的测试用例,这些测试用例将在单元测试运行结束后显示到单元测试控制台或 GUI 窗口中。全过程你完全不需要为任何单元测试方法进行命名——你关注的,是测试用例本身。
16 |
17 | ## 新手入门
18 |
19 | 现在,你的单元测试可以这样写了:
20 |
21 | ```csharp
22 | [TestClass]
23 | public class DemoTest
24 | {
25 | [ContractTestCase]
26 | public void Foo()
27 | {
28 | "当满足 A 条件时,应该发生 A' 事。".Test(() =>
29 | {
30 | // Arrange
31 | // Action
32 | // Assert
33 | });
34 |
35 | "当满足 B 条件时,应该发生 B' 事。".Test(() =>
36 | {
37 | // Arrange
38 | // Action
39 | // Assert
40 | });
41 | }
42 | }
43 | ```
44 |
45 | 于是,运行单元测试将看到这样的结果视图:
46 |
47 | 
48 |
49 | ## 开源社区需要你的加入
50 |
51 | // 编写中……
52 |
53 | ### 发现并提出问题
54 |
55 | // 编写中……
56 |
57 | ### 贡献你的代码
58 |
59 | // 编写中……
60 |
61 | ## 许可协议
62 |
63 | [MIT 许可](./LICENSE)
64 |
--------------------------------------------------------------------------------
/docs/zh-chs/resharper-code-templates.md:
--------------------------------------------------------------------------------
1 | # MSTestEnhancer 风格的 ReSharper 代码片段
2 |
3 | MSTest 有着独特的单元测试编写风格,这意味着目前并没有现成的工具提供这种风格单元测试的快速生成方案。但是你可以使用 ReSharper 手工导入我们预设好的代码片段来自动生成这种风格的代码。
4 |
5 | ## 目前提供的代码片段
6 |
7 | 目前提供了四种代码片段:
8 |
9 | - [生成单元测试类](#生成单元测试文件和类)
10 | - [生成测试方法](#生成单元测试方法)
11 | - [生成测试用例](#生成单元测试用例)
12 | - [转换现有测试到 MSTestEnhancer 风格](#%E8%BD%AC%E6%8D%A2%E7%8E%B0%E6%9C%89%E7%9A%84%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B%E5%88%B0-mstestenhancer-%E9%A3%8E%E6%A0%BC)
13 |
14 | ### 生成单元测试文件和类
15 |
16 | 如果你使用 Visual Studio(带 ReSharper 插件)开发,那么在项目上或者项目的文件夹上点击右键 → `添加` → `New from Template` → `Contract Unit Test` 即可添加一个 MSTestEnhancer 风格的单元测试类。
17 |
18 | 
19 | ▲ 根据模板创建类
20 |
21 | 
22 | ▲ 填写文件名(正好也是类名)
23 |
24 | 添加好的类会默认选中那段契约文字,这样可以立刻开始编写或者粘贴契约。
25 |
26 | 
27 | ▲ 生成的类
28 |
29 | ### 生成单元测试方法
30 |
31 | 在类中输入 `test` 会出现 `Insert a test method` 代码片段。敲回车或者 Tab 便能够插入单元测试方法。
32 |
33 | 
34 | ▲ 输入 test(并不需要完整输入,ReSharper 还是很厉害的)
35 |
36 | 
37 | ▲ 生成的方法(命名空间会自动加入)
38 |
39 | ### 生成单元测试用例
40 |
41 | 使用 MSTestEnhancer 风格编写单元测试后,一个方法内部可以包含多个单元测试用例了。所以如果在方法内部输入 `test` 会出现 `Insert a test case` 代码片段。敲回车或者 Tab 便能够插入单元测试用例。
42 |
43 | 
44 | ▲ 输入 test(同样不需要完整输入)
45 |
46 | 
47 | ▲ 生成的测试用例
48 |
49 | ### 转换现有的单元测试用例到 MSTestEnhancer 风格
50 |
51 | 如果项目中已经存在传统风格的单元测试,可以使用此代码片段将测试用例转换成 MSTestEnhancer 风格。
52 |
53 | 1. 选中传统风格的测试用例。
54 | 1. 直接输入 `test`,不用担心覆盖掉刚刚选中的代码。
55 | 1. 敲回车或者 Tab 便会转换刚刚选中的代码到 MSTestEnhancer 风格。
56 |
57 | 
58 | ▲ 选中测试用例代码(可使用多次 Ctrl + W 快捷键快速选中)
59 |
60 | 
61 | ▲ 输入 `test` 转换测试用例
62 |
63 | 
64 | ▲ 转换的测试用例
65 |
66 | 步骤 2 也可以替换成菜单操作:按下 Ctrl + Enter,选择 `Surround with...` → `test`。
67 |
68 | 
69 | ▲ 使用菜单操作(当然效率会低很多)
70 |
71 | ## 将代码片段导入到我的 ReSharper
72 |
73 | 你可以在 [MSTestEnhancer/MSTest.Extensions.sln.DotSettings](/MSTest.Extensions.sln.DotSettings) 文件中找到我们预设好的 ReSharper 代码片段。不过这种格式的文件人类可读性较差,我们更推荐你使用导入的方式来使用这些代码片段。
74 |
75 | ### 如何导入到我的 ReSharper
76 |
77 | 在 ReSharper 菜单中选择 `Tools` → `Templates Explorer` 打开模板浏览器。
78 |
79 | 
80 |
81 | 依次选中所有种类的模板标签,点击 `导入`,选择从本仓库下载的 [MSTestEnhancer/MSTest.Extensions.sln.DotSettings](/MSTest.Extensions.sln.DotSettings) 文件即可。
82 |
83 | 
84 |
85 | 在 File Templates 标签的工具栏中选择漏斗状的图标,保持 `Show Predefined Templates` 为选中状态。然后,将 这样可以将我们刚刚导入的模板设置到快速新建的菜单中(也就是本文一开始就贴出的那张添加文件和类的图)。
86 |
87 | 
88 | ▲ 选中 `显示预定义的模板`
89 |
90 | 
91 | ▲ 将导入的模板拖拽到快速列表中
92 |
--------------------------------------------------------------------------------
/docs/zh-cht/README.md:
--------------------------------------------------------------------------------
1 | # MSTest Enhancer
2 |
3 | 在 MSTestEnhancer 的幫助下,單元測試將更加容易編寫、理解和維護。因為它讓單元測試方法和被測類本身之間的關係更加緊密。
4 |
5 | ## 如何開始
6 |
7 | 1. 在單元測試項目中安裝 NuGet 包:[MSTestEnhancer](https://www.nuget.org/packages/MSTestEnhancer/)。
8 | 1. 現在就可以開始編寫下文那種風格的單元測試代碼了。
9 |
10 | ## 推薦的單元測試編寫方式
11 |
12 | 假設你希望測試一個名為 `被測類名` 的類,其中包含一個名為 `被測方法名` 的方法。你可以這樣編寫單元測試:
13 |
14 | ```csharp
15 | [TestClass]
16 | public class 被測類名Test
17 | {
18 | [ContractTestCase]
19 | public void 被測方法名()
20 | {
21 | "契約 1(當 Xxx 時,應該發生 Yyy)".Test(() =>
22 | {
23 | // 測試用例代碼
24 | });
25 |
26 | "契約 2(但當 Zzz 時,應該發生 Www)".Test(() =>
27 | {
28 | // 測試用例代碼
29 | });
30 | }
31 | }
32 | ```
33 |
34 | 注意到單元測試類的名稱和原本的類名稱是一一對應的,方法名和原本的方法名是一模一樣的;於是,我們不再需要為任何一個單元測試思考命名問題。
35 |
36 | 
37 |
38 | ## 參數化的單元測試
39 |
40 | 有些契約需要更多的值組合來驗證正確性,那麼可以在契約測試用例的後面添加參數。
41 |
42 | ```csharp
43 | "質數".Test((int num) =>
44 | {
45 | // 測試用例代碼
46 | }).WithArguments(2, 3, 5, 7, 11);
47 |
48 | "{0} 不是質數".Test((int num) =>
49 | {
50 | // 測試用例代碼
51 | }).WithArguments(1, 4);
52 | ```
53 |
54 | 也可以添加多個參數(最多支持 8 個):
55 |
56 | ```csharp
57 | "契約 1,參數中可以帶 {0} 和 {1}。".Test((int a, int b) =>
58 | {
59 | // 現在,a 會等於 2,b 會等於 3。
60 | }).WithArguments(2, 3);
61 |
62 | "契約 2".Test((int a, int b) =>
63 | {
64 | // 現在有兩組代碼,一組 a=2, b=3;另一組 a=10, b=20。
65 | // 當然也可以傳入元組數組。
66 | }).WithArguments((2, 3), (10, 20));
67 | ```
68 |
69 | 在顯示單元測試結果時,如果契約字符串中含有格式化佔位符 `{0}`、`{1}` 等,會被自動替換為參數的值。
70 |
71 | ## 異步的單元測試
72 |
73 | `Test` 方法中傳入的每個 `Action` 都支持 `async` 關鍵字,並會在執行測試用例時等待異步操作結束。
74 |
75 | ## 額外的黑科技
76 |
77 | MSTest v2 支持嵌套類型的單元測試。也就是說,我們可以利用這一點做出近乎無限層級的單元測試樹出來。
78 |
--------------------------------------------------------------------------------
/docs/zh-cht/README.zh-cht.md:
--------------------------------------------------------------------------------
1 | [English][en]|[日本語][jp]|[简体中文][zh-chs]|[繁體中文][zh-cht]
2 | -|-|-|-
3 |
4 | [en]: /README.md
5 | [jp]: /docs/jp/README.jp.md
6 | [zh-chs]: /docs/zh-chs/README.zh-chs.md
7 | [zh-cht]: /docs/zh-cht/README.zh-cht.md
8 |
9 | # MSTestEnhancer
10 |
11 | 有沒有覺得命名太難?有沒有覺得單元測試的命名更難?沒錯,你不是一個人!看看這個你就知道了:[程序員最頭疼的事:命名](http://blog.jobbole.com/50708/#rd?sukey=fc78a68049a14bb285ac0d81ca56806ac10192f4946a780ea3f3dd630804f86056e6fcfe6fcaeddb3dc04830b7e3b3eb) 或它的英文原文 [Don't go into programming if you don't have a good thesaurus - ITworld](https://www.itworld.com/article/2833265/cloud-computing/don-t-go-into-programming-if-you-don-t-have-a-good-thesaurus.html)。
12 |
13 | **MSTestEnhancer** 的出現將解決令你頭疼的單元測試命名問題——因為,你再也不需要為任何單元測試方法命名了!
14 |
15 | MSTestEnhancer 是 MSTest v2 的一個擴展。使用它,你可以用契約的方式來描述一個又一個的測試用例,這些測試用例將在單元測試運行結束後顯示到單元測試控制台或 GUI 窗口中。全過程你完全不需要為任何單元測試方法進行命名——你關注的,是測試用例本身。
16 |
17 | ## 新手入門
18 |
19 | 現在,你的單元測試可以這樣寫了:
20 |
21 | ```csharp
22 | [TestClass]
23 | public class DemoTest
24 | {
25 | [ContractTestCase]
26 | public void Foo()
27 | {
28 | "當滿足 A 條件時,應該發生 X 事。".Test(() =>
29 | {
30 | // Arrange
31 | // Action
32 | // Assert
33 | });
34 |
35 | "但是當滿足 B 條件時,應該發生 Y 事。".Test(() =>
36 | {
37 | // Arrange
38 | // Action
39 | // Assert
40 | });
41 | }
42 | }
43 | ```
44 |
45 | 於是,運行單元測試將看到這樣的結果視圖:
46 |
47 | 
48 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dotnet-campus/CUnit/1affc9f7a34f5ddd508845aa974ba36097de0f30/icon.png
--------------------------------------------------------------------------------
/src/MSTest.Extensions/AssertExtensions/AssertExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 |
3 | namespace MSTest.Extensions.AssertExtensions
4 | {
5 | ///
6 | /// A set of assertion libraries that support chained syntax
7 | ///
8 | public static class AssertExtensions
9 | {
10 | ///
11 | /// Tests whether the specified object is an instance of the expected
12 | /// type and throws an exception if the expected type is not in the
13 | /// inheritance hierarchy of the object.
14 | ///
15 | ///
16 | ///
17 | /// The object the test expects to be of the specified type.
18 | ///
19 | ///
20 | /// The expected type of .
21 | ///
22 | ///
23 | /// The message to include in the exception when
24 | /// is not an instance of . The message is
25 | /// shown in test results.
26 | ///
27 | ///
28 | /// Thrown if is null or
29 | /// is not in the inheritance hierarchy
30 | /// of .
31 | ///
32 | public static Assert IsInstanceOfType(this Assert assert, object value, string message = "")
33 | {
34 | Assert.IsInstanceOfType(value, typeof(T));
35 | return assert;
36 | }
37 |
38 | ///
39 | /// Tests whether the specified object is not an instance of the wrong
40 | /// type and throws an exception if the specified type is in the
41 | /// inheritance hierarchy of the object.
42 | ///
43 | ///
44 | ///
45 | /// The object the test expects not to be of the specified type.
46 | ///
47 | ///
48 | /// The type that should not be.
49 | ///
50 | ///
51 | /// The message to include in the exception when
52 | /// is an instance of . The message is shown
53 | /// in test results.
54 | ///
55 | ///
56 | /// Thrown if is not null and
57 | /// is in the inheritance hierarchy
58 | /// of .
59 | ///
60 | public static Assert IsNotInstanceOfType(this Assert assert, object value, string message = "")
61 | {
62 | Assert.IsNotInstanceOfType(value, typeof(T), message, null);
63 | return assert;
64 | }
65 |
66 | ///
67 | /// Tests whether the specified object is null and throws an exception
68 | /// if it is not.
69 | ///
70 | ///
71 | ///
72 | /// The object the test expects to be null.
73 | ///
74 | ///
75 | /// The message to include in the exception when
76 | /// is not null. The message is shown in test results.
77 | ///
78 | ///
79 | /// Thrown if is not null.
80 | ///
81 | public static Assert IsNullT(this Assert assert, [CanBeNull]object value, string message = "")
82 | {
83 | Assert.IsNull(value);
84 | return assert;
85 | }
86 |
87 | ///
88 | /// Tests whether the specified condition is true and throws an exception
89 | /// if the condition is false.
90 | ///
91 | ///
92 | ///
93 | /// The condition the test expects to be true.
94 | ///
95 | ///
96 | /// The message to include in the exception when
97 | /// is false. The message is shown in test results.
98 | ///
99 | ///
100 | /// Thrown if is false.
101 | ///
102 | public static Assert IsTrueT(this Assert assert, bool condition, string message = "")
103 | {
104 | Assert.IsTrue(condition, message);
105 | return assert;
106 | }
107 |
108 | ///
109 | /// Tests whether the specified condition is false and throws an exception
110 | /// if the condition is true.
111 | ///
112 | ///
113 | ///
114 | /// The condition the test expects to be false.
115 | ///
116 | ///
117 | /// The message to include in the exception when
118 | /// is true. The message is shown in test results.
119 | ///
120 | ///
121 | /// Thrown if is true.
122 | ///
123 | public static Assert IsFalseT(this Assert assert, bool condition, string message = "")
124 | {
125 | Assert.IsFalse(condition, message);
126 | return assert;
127 | }
128 |
129 | ///
130 | /// Tests whether the specified object is non-null and throws an exception
131 | /// if it is null.
132 | ///
133 | ///
134 | ///
135 | /// The object the test expects not to be null.
136 | ///
137 | ///
138 | /// The message to include in the exception when
139 | /// is null. The message is shown in test results.
140 | ///
141 | ///
142 | /// Thrown if is null.
143 | ///
144 | public static Assert IsNotNullT(this Assert assert, [CanBeNull]object value, string message = "")
145 | {
146 | Assert.IsNotNull(value, message, null);
147 | return assert;
148 | }
149 |
150 | ///
151 | /// Tests whether the specified objects both refer to the same object and
152 | /// throws an exception if the two inputs do not refer to the same object.
153 | ///
154 | ///
155 | ///
156 | /// The first object to compare. This is the value the test expects.
157 | ///
158 | ///
159 | /// The second object to compare. This is the value produced by the code under test.
160 | ///
161 | ///
162 | /// The message to include in the exception when
163 | /// is not the same as . The message is shown
164 | /// in test results.
165 | ///
166 | ///
167 | /// Thrown if does not refer to the same object
168 | /// as .
169 | ///
170 | public static Assert AreSameT(this Assert assert, object expected, object actual, string message = "")
171 | {
172 | Assert.AreSame(expected, actual, message, null);
173 | return assert;
174 | }
175 |
176 | ///
177 | /// Tests whether the specified objects refer to different objects and
178 | /// throws an exception if the two inputs refer to the same object.
179 | ///
180 | ///
181 | ///
182 | /// The first object to compare. This is the value the test expects not
183 | /// to match .
184 | ///
185 | ///
186 | /// The second object to compare. This is the value produced by the code under test.
187 | ///
188 | ///
189 | /// The message to include in the exception when
190 | /// is the same as . The message is shown in
191 | /// test results.
192 | ///
193 | ///
194 | /// Thrown if refers to the same object
195 | /// as .
196 | ///
197 | public static Assert AreNotSameT(this Assert assert, object notExpected, object actual, string message)
198 | {
199 | Assert.AreNotSame(notExpected, actual, message, null);
200 | return assert;
201 | }
202 |
203 | ///
204 | /// Tests whether the specified values are equal and throws an exception
205 | /// if the two values are not equal. Different numeric types are treated
206 | /// as unequal even if the logical values are equal. 42L is not equal to 42.
207 | ///
208 | ///
209 | /// The type of values to compare.
210 | ///
211 | ///
212 | ///
213 | /// The first value to compare. This is the value the tests expects.
214 | ///
215 | ///
216 | /// The second value to compare. This is the value produced by the code under test.
217 | ///
218 | ///
219 | /// The message to include in the exception when
220 | /// is not equal to . The message is shown in
221 | /// test results.
222 | ///
223 | ///
224 | /// Thrown if is not equal to
225 | /// .
226 | ///
227 | public static Assert AreEqualT(this Assert assert, T expected, T actual, string message = "")
228 | {
229 | Assert.AreEqual(expected, actual, message, null);
230 | return assert;
231 | }
232 |
233 | ///
234 | /// Tests whether the specified values are unequal and throws an exception
235 | /// if the two values are equal. Different numeric types are treated
236 | /// as unequal even if the logical values are equal. 42L is not equal to 42.
237 | ///
238 | ///
239 | /// The type of values to compare.
240 | ///
241 | ///
242 | ///
243 | /// The first value to compare. This is the value the test expects not
244 | /// to match .
245 | ///
246 | ///
247 | /// The second value to compare. This is the value produced by the code under test.
248 | ///
249 | ///
250 | /// The message to include in the exception when
251 | /// is equal to . The message is shown in
252 | /// test results.
253 | ///
254 | ///
255 | /// Thrown if is equal to .
256 | ///
257 | public static Assert AreNotEqualT(this Assert assert, T notExpected, T actual, string message = "")
258 | {
259 | Assert.AreNotEqual(notExpected, actual, message, null);
260 | return assert;
261 | }
262 |
263 | ///
264 | /// Tests whether the specified doubles are equal and throws an exception
265 | /// if they are not equal.
266 | ///
267 | ///
268 | ///
269 | /// The first double to compare. This is the double the tests expects.
270 | ///
271 | ///
272 | /// The second double to compare. This is the double produced by the code under test.
273 | ///
274 | ///
275 | /// The required accuracy. An exception will be thrown only if
276 | /// is different than
277 | /// by more than .
278 | ///
279 | ///
280 | /// The message to include in the exception when
281 | /// is different than by more than
282 | /// . The message is shown in test results.
283 | ///
284 | ///
285 | /// Thrown if is not equal to .
286 | ///
287 | public static Assert AreEqualT(this Assert assert, double expected, double actual, double delta, string message = "")
288 | {
289 | Assert.AreEqual(expected, actual, delta, message, null);
290 | return assert;
291 | }
292 |
293 | ///
294 | /// Tests whether the specified doubles are unequal and throws an exception
295 | /// if they are equal.
296 | ///
297 | ///
298 | ///
299 | /// The first double to compare. This is the double the test expects not to
300 | /// match .
301 | ///
302 | ///
303 | /// The second double to compare. This is the double produced by the code under test.
304 | ///
305 | ///
306 | /// The required accuracy. An exception will be thrown only if
307 | /// is different than
308 | /// by at most .
309 | ///
310 | ///
311 | /// The message to include in the exception when
312 | /// is equal to or different by less than
313 | /// . The message is shown in test results.
314 | ///
315 | ///
316 | /// Thrown if is equal to .
317 | ///
318 | public static Assert AreNotEqualT(this Assert assert, double notExpected, double actual, double delta, string message = "")
319 | {
320 | Assert.AreNotEqual(notExpected, actual, delta, message, null);
321 | return assert;
322 | }
323 |
324 | ///
325 | /// Tests whether the specified strings are equal and throws an exception
326 | /// if they are not equal. The invariant culture is used for the comparison.
327 | ///
328 | ///
329 | ///
330 | /// The first string to compare. This is the string the tests expects.
331 | ///
332 | ///
333 | /// The second string to compare. This is the string produced by the code under test.
334 | ///
335 | ///
336 | /// A Boolean indicating a case-sensitive or insensitive comparison. (true
337 | /// indicates a case-insensitive comparison.)
338 | ///
339 | ///
340 | /// The message to include in the exception when
341 | /// is not equal to . The message is shown in
342 | /// test results.
343 | ///
344 | ///
345 | /// Thrown if is not equal to .
346 | ///
347 | public static Assert AreEqualT(this Assert assert, string expected, string actual, bool ignoreCase, string message = "")
348 | {
349 | Assert.AreEqual(expected, actual, ignoreCase, message, null);
350 | return assert;
351 | }
352 |
353 | ///
354 | /// Tests whether the specified strings are unequal and throws an exception
355 | /// if they are equal. The invariant culture is used for the comparison.
356 | ///
357 | ///
358 | ///
359 | /// The first string to compare. This is the string the test expects not to
360 | /// match .
361 | ///
362 | ///
363 | /// The second string to compare. This is the string produced by the code under test.
364 | ///
365 | ///
366 | /// A Boolean indicating a case-sensitive or insensitive comparison. (true
367 | /// indicates a case-insensitive comparison.)
368 | ///
369 | ///
370 | /// The message to include in the exception when
371 | /// is equal to . The message is shown in
372 | /// test results.
373 | ///
374 | ///
375 | /// Thrown if is equal to .
376 | ///
377 | public static Assert AreNotEqualT(this Assert assert, string notExpected, string actual, bool ignoreCase, string message = "")
378 | {
379 | Assert.AreNotEqual(notExpected, actual, ignoreCase, message, null);
380 | return assert;
381 | }
382 | }
383 | }
384 |
--------------------------------------------------------------------------------
/src/MSTest.Extensions/AssertExtensions/CollectionAssertExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 |
5 | namespace MSTest.Extensions.AssertExtensions
6 | {
7 | ///
8 | /// A set of assertion libraries that support chained syntax
9 | ///
10 | public static class CollectionAssertExtensions
11 | {
12 | ///
13 | /// Tests whether the specified collection contains the specified element
14 | /// and throws an exception if the element is not in the collection.
15 | ///
16 | ///
17 | ///
18 | /// The collection in which to search for the element.
19 | ///
20 | ///
21 | /// The element that is expected to be in the collection.
22 | ///
23 | ///
24 | /// The message to include in the exception when
25 | /// is not in . The message is shown in
26 | /// test results.
27 | ///
28 | ///
29 | /// Thrown if is not found in
30 | /// .
31 | ///
32 | public static CollectionAssert ContainsT(this CollectionAssert collectionAssert, ICollection collection, object element, string message = "")
33 | {
34 | CollectionAssert.Contains(collection, element, message, null);
35 | return collectionAssert;
36 | }
37 |
38 | ///
39 | /// Tests whether the specified collection does not contain the specified
40 | /// element and throws an exception if the element is in the collection.
41 | ///
42 | ///
43 | ///
44 | /// The collection in which to search for the element.
45 | ///
46 | ///
47 | /// The element that is expected not to be in the collection.
48 | ///
49 | ///
50 | /// The message to include in the exception when
51 | /// is in . The message is shown in test
52 | /// results.
53 | ///
54 | ///
55 | /// Thrown if is found in
56 | /// .
57 | ///
58 | public static CollectionAssert DoesNotContainT(this CollectionAssert collectionAssert, ICollection collection, object element, string message = "")
59 | {
60 | CollectionAssert.DoesNotContain(collection, element, message, null);
61 | return collectionAssert;
62 | }
63 |
64 | ///
65 | /// Tests whether all items in the specified collection are non-null and throws
66 | /// an exception if any element is null.
67 | ///
68 | ///
69 | ///
70 | /// The collection in which to search for null elements.
71 | ///
72 | ///
73 | /// The message to include in the exception when
74 | /// contains a null element. The message is shown in test results.
75 | ///
76 | ///
77 | /// Thrown if a null element is found in .
78 | ///
79 | public static CollectionAssert AllItemsAreNotNullT(this CollectionAssert collectionAssert, ICollection collection, string message = "")
80 | {
81 | CollectionAssert.AllItemsAreNotNull(collection, message, null);
82 | return collectionAssert;
83 | }
84 |
85 | ///
86 | /// Tests whether all items in the specified collection are unique or not and
87 | /// throws if any two elements in the collection are equal.
88 | ///
89 | ///
90 | ///
91 | /// The collection in which to search for duplicate elements.
92 | ///
93 | ///
94 | /// The message to include in the exception when
95 | /// contains at least one duplicate element. The message is shown in
96 | /// test results.
97 | ///
98 | ///
99 | /// Thrown if a two or more equal elements are found in
100 | /// .
101 | ///
102 | public static CollectionAssert AllItemsAreUniqueT(this CollectionAssert collectionAssert, ICollection collection, string message = "")
103 | {
104 | CollectionAssert.AllItemsAreUnique(collection, message, null);
105 | return collectionAssert;
106 | }
107 |
108 | ///
109 | /// Tests whether one collection is a subset of another collection and
110 | /// throws an exception if any element in the subset is not also in the
111 | /// superset.
112 | ///
113 | ///
114 | ///
115 | /// The collection expected to be a subset of .
116 | ///
117 | ///
118 | /// The collection expected to be a superset of
119 | ///
120 | ///
121 | /// The message to include in the exception when an element in
122 | /// is not found in .
123 | /// The message is shown in test results.
124 | ///
125 | ///
126 | /// Thrown if an element in is not found in
127 | /// .
128 | ///
129 | public static CollectionAssert IsSubsetOfT(this CollectionAssert collectionAssert, ICollection subset, ICollection superset, string message = "")
130 | {
131 | CollectionAssert.IsSubsetOf(subset, superset, message, null);
132 | return collectionAssert;
133 | }
134 |
135 | ///
136 | /// Tests whether one collection is not a subset of another collection and
137 | /// throws an exception if all elements in the subset are also in the
138 | /// superset.
139 | ///
140 | ///
141 | ///
142 | /// The collection expected not to be a subset of .
143 | ///
144 | ///
145 | /// The collection expected not to be a superset of
146 | ///
147 | ///
148 | /// The message to include in the exception when every element in
149 | /// is also found in .
150 | /// The message is shown in test results.
151 | ///
152 | ///
153 | /// Thrown if every element in is also found in
154 | /// .
155 | ///
156 | public static CollectionAssert IsNotSubsetOfT(this CollectionAssert collectionAssert, ICollection subset, ICollection superset, string message = "")
157 | {
158 | CollectionAssert.IsNotSubsetOf(subset, superset, message, null);
159 | return collectionAssert;
160 | }
161 |
162 | ///
163 | /// Tests whether two collections contain the same elements and throws an
164 | /// exception if either collection contains an element not in the other
165 | /// collection.
166 | ///
167 | ///
168 | ///
169 | /// The first collection to compare. This contains the elements the test
170 | /// expects.
171 | ///
172 | ///
173 | /// The second collection to compare. This is the collection produced by
174 | /// the code under test.
175 | ///
176 | ///
177 | /// The message to include in the exception when an element was found
178 | /// in one of the collections but not the other. The message is shown
179 | /// in test results.
180 | ///
181 | ///
182 | /// Thrown if an element was found in one of the collections but not
183 | /// the other.
184 | ///
185 | public static CollectionAssert AreEquivalentT(this CollectionAssert collectionAssert, ICollection expected, ICollection actual, string message = "")
186 | {
187 | CollectionAssert.AreEquivalent(expected, actual, message, null);
188 | return collectionAssert;
189 | }
190 |
191 | ///
192 | /// Tests whether two collections contain the different elements and throws an
193 | /// exception if the two collections contain identical elements without regard
194 | /// to order.
195 | ///
196 | ///
197 | ///
198 | /// The first collection to compare. This contains the elements the test
199 | /// expects to be different than the actual collection.
200 | ///
201 | ///
202 | /// The second collection to compare. This is the collection produced by
203 | /// the code under test.
204 | ///
205 | ///
206 | /// The message to include in the exception when
207 | /// contains the same elements as . The message
208 | /// is shown in test results.
209 | ///
210 | ///
211 | /// Thrown if the two collections contained the same elements, including
212 | /// the same number of duplicate occurrences of each element.
213 | ///
214 | public static CollectionAssert AreNotEquivalentT(this CollectionAssert collectionAssert, ICollection expected, ICollection actual, string message = "")
215 | {
216 | CollectionAssert.AreNotEquivalent(expected, actual, message, null);
217 | return collectionAssert;
218 | }
219 |
220 | ///
221 | /// Tests whether all elements in the specified collection are instances
222 | /// of the expected type and throws an exception if the expected type is
223 | /// not in the inheritance hierarchy of one or more of the elements.
224 | ///
225 | ///
226 | ///
227 | /// The collection containing elements the test expects to be of the
228 | /// specified type.
229 | ///
230 | ///
231 | /// The expected type of each element of .
232 | ///
233 | ///
234 | /// The message to include in the exception when an element in
235 | /// is not an instance of
236 | /// . The message is shown in test results.
237 | ///
238 | ///
239 | /// Thrown if an element in is null or
240 | /// is not in the inheritance hierarchy
241 | /// of an element in .
242 | ///
243 | public static CollectionAssert AllItemsAreInstancesOfTypeT(this CollectionAssert collectionAssert, ICollection collection, Type expectedType, string message = "")
244 | {
245 | CollectionAssert.AllItemsAreInstancesOfType(collection, expectedType, message, null);
246 | return collectionAssert;
247 | }
248 |
249 | ///
250 | /// Tests whether the specified collections are equal and throws an exception
251 | /// if the two collections are not equal. Equality is defined as having the same
252 | /// elements in the same order and quantity. Different references to the same
253 | /// value are considered equal.
254 | ///
255 | ///
256 | ///
257 | /// The first collection to compare. This is the collection the tests expects.
258 | ///
259 | ///
260 | /// The second collection to compare. This is the collection produced by the
261 | /// code under test.
262 | ///
263 | ///
264 | /// The message to include in the exception when
265 | /// is not equal to . The message is shown in
266 | /// test results.
267 | ///
268 | ///
269 | /// Thrown if is not equal to
270 | /// .
271 | ///
272 | public static CollectionAssert AreEqualT(this CollectionAssert collectionAssert, ICollection expected, ICollection actual, string message = "")
273 | {
274 | CollectionAssert.AreEqual(expected, actual, message, null);
275 | return collectionAssert;
276 | }
277 |
278 | ///
279 | /// Tests whether the specified collections are unequal and throws an exception
280 | /// if the two collections are equal. Equality is defined as having the same
281 | /// elements in the same order and quantity. Different references to the same
282 | /// value are considered equal.
283 | ///
284 | ///
285 | ///
286 | /// The first collection to compare. This is the collection the tests expects
287 | /// not to match .
288 | ///
289 | ///
290 | /// The second collection to compare. This is the collection produced by the
291 | /// code under test.
292 | ///
293 | ///
294 | /// The message to include in the exception when
295 | /// is equal to . The message is shown in
296 | /// test results.
297 | ///
298 | ///
299 | /// Thrown if is equal to .
300 | ///
301 | public static CollectionAssert AreNotEqualT(this CollectionAssert collectionAssert, ICollection notExpected, ICollection actual, string message = "")
302 | {
303 | CollectionAssert.AreNotEqual(notExpected, actual, message, null);
304 | return collectionAssert;
305 | }
306 |
307 | ///
308 | /// Tests whether the specified collections are equal and throws an exception
309 | /// if the two collections are not equal. Equality is defined as having the same
310 | /// elements in the same order and quantity. Different references to the same
311 | /// value are considered equal.
312 | ///
313 | ///
314 | ///
315 | /// The first collection to compare. This is the collection the tests expects.
316 | ///
317 | ///
318 | /// The second collection to compare. This is the collection produced by the
319 | /// code under test.
320 | ///
321 | ///
322 | /// The compare implementation to use when comparing elements of the collection.
323 | ///
324 | ///
325 | /// The message to include in the exception when
326 | /// is not equal to . The message is shown in
327 | /// test results.
328 | ///
329 | ///
330 | /// Thrown if is not equal to
331 | /// .
332 | ///
333 | public static CollectionAssert AreEqualT(this CollectionAssert collectionAssert, ICollection expected, ICollection actual, IComparer comparer, string message = "")
334 | {
335 | CollectionAssert.AreEqual(expected, actual, comparer, message, null);
336 | return collectionAssert;
337 | }
338 |
339 | ///
340 | /// Tests whether the specified collections are unequal and throws an exception
341 | /// if the two collections are equal. Equality is defined as having the same
342 | /// elements in the same order and quantity. Different references to the same
343 | /// value are considered equal.
344 | ///
345 | ///
346 | ///
347 | /// The first collection to compare. This is the collection the tests expects
348 | /// not to match .
349 | ///
350 | ///
351 | /// The second collection to compare. This is the collection produced by the
352 | /// code under test.
353 | ///
354 | ///
355 | /// The compare implementation to use when comparing elements of the collection.
356 | ///
357 | ///
358 | /// The message to include in the exception when
359 | /// is equal to . The message is shown in
360 | /// test results.
361 | ///
362 | ///
363 | /// Thrown if is equal to .
364 | ///
365 | public static CollectionAssert AreNotEqualT(this CollectionAssert collectionAssert, ICollection notExpected, ICollection actual, IComparer comparer, string message = "")
366 | {
367 | CollectionAssert.AreNotEqual(notExpected, actual, comparer, message, null);
368 | return collectionAssert;
369 | }
370 | }
371 | }
372 |
--------------------------------------------------------------------------------
/src/MSTest.Extensions/AssertExtensions/StringAssertExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 |
4 | namespace MSTest.Extensions.AssertExtensions
5 | {
6 | ///
7 | /// A set of assertion libraries that support chained syntax
8 | ///
9 | public static class StringAssertExtensions
10 | {
11 | ///
12 | /// Tests whether the specified string contains the specified substring
13 | /// and throws an exception if the substring does not occur within the
14 | /// test string.
15 | ///
16 | ///
17 | ///
18 | /// The string that is expected to contain .
19 | ///
20 | ///
21 | /// The string expected to occur within .
22 | ///
23 | ///
24 | /// The message to include in the exception when
25 | /// is not in . The message is shown in
26 | /// test results.
27 | ///
28 | ///
29 | /// Thrown if is not found in
30 | /// .
31 | ///
32 | public static StringAssert ContainsT(this StringAssert stringAssert, string value, string substring, string message = "")
33 | {
34 | StringAssert.Contains(value, substring, message, null);
35 | return stringAssert;
36 | }
37 |
38 | ///
39 | /// Tests whether the specified string begins with the specified substring
40 | /// and throws an exception if the test string does not start with the
41 | /// substring.
42 | ///
43 | ///
44 | ///
45 | /// The string that is expected to begin with .
46 | ///
47 | ///
48 | /// The string expected to be a prefix of .
49 | ///
50 | ///
51 | /// The message to include in the exception when
52 | /// does not begin with . The message is
53 | /// shown in test results.
54 | ///
55 | ///
56 | /// Thrown if does not begin with
57 | /// .
58 | ///
59 | public static StringAssert StartsWithT(this StringAssert stringAssert, string value, string substring, string message = "")
60 | {
61 | StringAssert.StartsWith(value, substring, message, null);
62 | return stringAssert;
63 | }
64 |
65 | ///
66 | /// Tests whether the specified string ends with the specified substring
67 | /// and throws an exception if the test string does not end with the
68 | /// substring.
69 | ///
70 | ///
71 | ///
72 | /// The string that is expected to end with .
73 | ///
74 | ///
75 | /// The string expected to be a suffix of .
76 | ///
77 | ///
78 | /// The message to include in the exception when
79 | /// does not end with . The message is
80 | /// shown in test results.
81 | ///
82 | ///
83 | /// Thrown if does not end with
84 | /// .
85 | ///
86 | public static StringAssert EndsWithT(this StringAssert stringAssert, string value, string substring, string message = "")
87 | {
88 | StringAssert.EndsWith(value, substring, message, null);
89 | return stringAssert;
90 | }
91 |
92 | ///
93 | /// Tests whether the specified string matches a regular expression and
94 | /// throws an exception if the string does not match the expression.
95 | ///
96 | ///
97 | ///
98 | /// The string that is expected to match .
99 | ///
100 | ///
101 | /// The regular expression that is
102 | /// expected to match.
103 | ///
104 | ///
105 | /// The message to include in the exception when
106 | /// does not match . The message is shown in
107 | /// test results.
108 | ///
109 | ///
110 | /// Thrown if does not match
111 | /// .
112 | ///
113 | public static StringAssert MatchesT(this StringAssert stringAssert, string value, Regex pattern, string message = "")
114 | {
115 | StringAssert.Matches(value, pattern, message, null);
116 | return stringAssert;
117 | }
118 |
119 | ///
120 | /// Tests whether the specified string does not match a regular expression
121 | /// and throws an exception if the string matches the expression.
122 | ///
123 | ///
124 | ///
125 | /// The string that is expected not to match .
126 | ///
127 | ///
128 | /// The regular expression that is
129 | /// expected to not match.
130 | ///
131 | ///
132 | /// The message to include in the exception when
133 | /// matches . The message is shown in test
134 | /// results.
135 | ///
136 | ///
137 | /// Thrown if matches .
138 | ///
139 | public static StringAssert DoesNotMatchT(this StringAssert stringAssert, string value, Regex pattern, string message = "")
140 | {
141 | StringAssert.DoesNotMatch(value, pattern, message, null);
142 | return stringAssert;
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/MSTest.Extensions/Contracts/ContractTest.01.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.Contracts;
3 | using System.Threading.Tasks;
4 | using dotnetCampus.Runtime.CompilerServices;
5 |
6 | namespace MSTest.Extensions.Contracts
7 | {
8 | [GenerateGenericFromThis(From = 2, To = 4)]
9 | public static partial class ContractTest
10 | {
11 | ///
12 | /// Create a test case for the specified .
13 | ///
14 | /// The description of a test contract.
15 | /// The action which is used to test the contract.
16 | [System.Diagnostics.Contracts.Pure, NotNull, PublicAPI]
17 | public static ContractTestContext Test([NotNull] this string contract, [NotNull] Action testCase)
18 | {
19 | if (contract == null) throw new ArgumentNullException(nameof(contract));
20 | if (testCase == null) throw new ArgumentNullException(nameof(testCase));
21 |
22 | Contract.EndContractBlock();
23 |
24 | return new ContractTestContext(contract, testCase);
25 | }
26 |
27 | ///
28 | /// Create an async test case for the specified .
29 | ///
30 | /// The description of a test contract.
31 | /// The async action which is used to test the contract.
32 | [System.Diagnostics.Contracts.Pure, NotNull, PublicAPI]
33 | public static ContractTestContext Test([NotNull] this string contract, [NotNull] Func testCase)
34 | {
35 | if (contract == null) throw new ArgumentNullException(nameof(contract));
36 | if (testCase == null) throw new ArgumentNullException(nameof(testCase));
37 |
38 | Contract.EndContractBlock();
39 |
40 | return new ContractTestContext(contract, testCase);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/MSTest.Extensions/Contracts/ContractTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics.Contracts;
3 | using System.Threading.Tasks;
4 | using MSTest.Extensions.Core;
5 |
6 | namespace MSTest.Extensions.Contracts
7 | {
8 | ///
9 | /// Contains methods to write contract based unit test code.
10 | ///
11 | public static partial class ContractTest
12 | {
13 | #region Style 1: "Contract".Test(() => { Test Case Code })
14 |
15 | ///
16 | /// Create a test case for the specified .
17 | ///
18 | /// The description of a test contract.
19 | /// The action of the which is used to test the contract.
20 | [PublicAPI]
21 | public static void Test([NotNull] this string contract, [NotNull] Action testCase)
22 | {
23 | if (contract == null) throw new ArgumentNullException(nameof(contract));
24 | if (testCase == null) throw new ArgumentNullException(nameof(testCase));
25 | Contract.EndContractBlock();
26 |
27 | Method.Current.Add(new ContractTestCase(contract, testCase));
28 | }
29 |
30 | ///
31 | /// Create an async test case for the specified .
32 | ///
33 | /// The description of a test contract.
34 | /// The async action of the which is used to test the contract.
35 | [PublicAPI]
36 | public static void Test([NotNull] this string contract, [NotNull] Func testCase)
37 | {
38 | if (contract == null) throw new ArgumentNullException(nameof(contract));
39 | if (testCase == null) throw new ArgumentNullException(nameof(testCase));
40 | Contract.EndContractBlock();
41 |
42 | Method.Current.Add(new ContractTestCase(contract, testCase));
43 | }
44 |
45 | #endregion
46 |
47 | #region Style 2: await "Contract" Test Case Code
48 |
49 | /////
50 | ///// Treat a string as test case contract description, and then treat the code below await as test case action.
51 | /////
52 | ///// The description of a test contract.
53 | /////
54 | //[EditorBrowsable(EditorBrowsableState.Never)]
55 | //public static IAwaiter GetAwaiter(this string contract)
56 | //{
57 | // var result = new TestCaseAwaitable(contract);
58 | // Method.Current.Add(result);
59 | // return result;
60 | //}
61 |
62 | #endregion
63 |
64 | ///
65 | /// Gets all test case information that is collected or will be collected from the test method.
66 | ///
67 | [NotNull]
68 | internal static TestCaseIndexer Method { get; } = new TestCaseIndexer();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/MSTest.Extensions/Contracts/ContractTestCaseAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.Diagnostics.Contracts;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Threading.Tasks;
8 | using Microsoft.VisualStudio.TestTools.UnitTesting;
9 | using MSTest.Extensions.Core;
10 | using MSTest.Extensions.Utils;
11 |
12 | // ## How it works?
13 | //
14 | // 1. When the MSTestv2 discover a method which is marked with a `TestMethodAttribute`,
15 | // it will search all `Attributes` to find out the one which derived from `ITestDataSource`.
16 | // 1. The first instance (#1) of `ContractTestCaseAttribute` will be created because it derived from `ITestDataSource`.
17 | // 1. `GetMethod` of #1 is called:
18 | // - Invoke the target unit test method.
19 | // - Collect all test cases that are created during the target unit test method invoking.
20 | // - Return an empty array with length equals to test case count to the MSTestv2 framework.
21 | // - *In this case, `Execute` would be called the same times to that array length.*
22 | // 1. The second instance (#2) of `ContractTestCaseAttribute` will be created because it derived from `TestMethodAttribute`.
23 | // 1. `Execute` of #2 and `GetDisplayName` of #1 will be called alternately by the MSTestv2 framework:
24 | // - When `Execute` is called, fetch a test case and run it to get the test result.
25 | // - When `GetDisplayName` is called, fetch a test case and get the contract description string from it.
26 |
27 | namespace MSTest.Extensions.Contracts
28 | {
29 | ///
30 | /// Enable the unit test writing style of `"contract string".Test(TestCaseAction)`.
31 | ///
32 | [PublicAPI]
33 | public class ContractTestCaseAttribute : TestMethodAttribute, ITestDataSource
34 | {
35 | #region Instance derived from TestMethodAttribute
36 |
37 | ///
38 | [NotNull]
39 | public override TestResult[] Execute([NotNull] ITestMethod testMethod)
40 | {
41 | if (_testMethodProxy is null)
42 | {
43 | _testMethodProxy = CreateTestMethodProxy(testMethod);
44 | }
45 |
46 | var result = _testMethodProxy.Invoke(null);
47 | return new[] { result };
48 | }
49 |
50 | [NotNull]
51 | internal Task ExecuteAsync([NotNull] ITestMethod testMethod)
52 | {
53 | _testMethodProxy ??= CreateTestMethodProxy(testMethod);
54 |
55 | return _testMethodProxy.InvokeAsync();
56 | }
57 |
58 | [NotNull]
59 | private protected virtual TestMethodProxy CreateTestMethodProxy([NotNull] ITestMethod testMethod)
60 | {
61 | return new TestMethodProxy(testMethod);
62 | }
63 |
64 | #endregion
65 |
66 | #region Instance derived from ITestDataSource
67 |
68 | ///
69 | /// When a unit test method is preparing to run, This method will be called
70 | /// to fetch all the test cases of target unit test method.
71 | ///
72 | /// Target unit test method.
73 | ///
74 | /// The parameter array which will be passed into the target unit test method.
75 | /// We don't need any parameter, so we return an all null array with length equals to test case count.
76 | ///
77 | [NotNull]
78 | public IEnumerable