dotnet-testing-private-internal-testing
npx skills add https://github.com/kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-private-internal-testing
Agent 安装分布
Skill 文档
ç§æèå §é¨æå¡æ¸¬è©¦çç¥æå
æ¬æè½å婿¨å¨ .NET æ¸¬è©¦ä¸æ£ç¢ºèçç§æèå §é¨æå¡ç測試ï¼å¼·èª¿è¨è¨åªå çæ¸¬è©¦æç¶ã
é©ç¨æ å¢
ç¶è¢«è¦æ±å·è¡ä»¥ä¸ä»»åæï¼è«ä½¿ç¨æ¤æè½ï¼
- 測試 private æ internal æ¹æ³è屬æ§
- è¨å® InternalsVisibleTo ååå §é¨æå¡
- è©ä¼°æ¯å¦éè¦æ¸¬è©¦ç§ææ¹æ³ææéæ§è¨è¨
- 使ç¨åå°ï¼Reflectionï¼ååç§ææå¡
- æåç¨å¼ç¢¼ç坿¸¬è©¦æ§è¨è¨
æ ¸å¿ååï¼è¨è¨åªå æç¶
é»éæ³å
好çè¨è¨èªç¶å°±æå¥½ç坿¸¬è©¦æ§ãå¦æä½ ç¼ç¾èªå·±ç¶å¸¸éè¦æ¸¬è©¦ç§ææ¹æ³ï¼å¾å¯è½æ¯è¨è¨åºäºåé¡ã
è¨è¨åé¡çå¾µå
ç¶ä½ æ³æ¸¬è©¦ç§ææ¹æ³æï¼å 檢æ¥ä»¥ä¸å¾µå ï¼
- â ç§ææ¹æ³è¶ é 10 è¡ä¸å å«è¤éé輯
- â ç§ææ¹æ³å å«éè¦çæ¥åè¦å
- â ç§ææ¹æ³é£ä»¥ééå ¬éæ¹æ³éæ¥æ¸¬è©¦
- â é¡å¥æ¿æå¤åè·è²¬
è§£æ±ºæ¹æ¡ï¼éæ§è鿏¬è©¦
// â æåé¡çè¨è¨
public class OrderProcessor
{
public OrderResult ProcessOrder(Order order)
{
// 使ç¨å¤åè¤éçç§ææ¹æ³
var discount = CalculateDiscount(order); // 20 è¡é輯
var tax = CalculateTax(order, discount); // 15 è¡é輯
// ...
}
private decimal CalculateDiscount(Order order) { /* è¤éé輯 */ }
private decimal CalculateTax(Order order, decimal discount) { /* è¤éé輯 */ }
}
// â
æ¹é²çè¨è¨ï¼è²¬ä»»åé¢
public class OrderProcessor
{
private readonly IDiscountCalculator _discountCalculator;
private readonly ITaxCalculator _taxCalculator;
public OrderProcessor(
IDiscountCalculator discountCalculator,
ITaxCalculator taxCalculator)
{
_discountCalculator = discountCalculator;
_taxCalculator = taxCalculator;
}
public OrderResult ProcessOrder(Order order)
{
var discount = _discountCalculator.Calculate(order);
var tax = _taxCalculator.Calculate(order, discount);
// ...
}
}
// ç¾å¨å¯ä»¥ç¨ç«æ¸¬è©¦æ¯åè¨ç®å¨
public class DiscountCalculator : IDiscountCalculator
{
public decimal Calculate(Order order)
{
// è¤éé輯ç¾å¨æ¯å
¬éæ¹æ³ï¼å®¹ææ¸¬è©¦
}
}
Internal æå¡æ¸¬è©¦çç¥
使éè¦æ¸¬è©¦ Internal æå¡
é©åçæ å¢ï¼
- â æ¡æ¶æé¡å¥åº«éç¼
- â è¤éçå §é¨æ¼ç®æ³é©è
- â æè½ééµçå §é¨çµä»¶
- â å®å ¨ç¸éçå §é¨é輯
ä¸é©åçæ å¢ï¼
- â æç¨å±¤çæ¥åéè¼¯ï¼æè©²æ¯ publicï¼
- â ç°¡å®çè¼å©æ¹æ³
- â å¯ä»¥ééå ¬é API éæ¥æ¸¬è©¦çé輯
æ¹æ³ä¸ï¼ä½¿ç¨ InternalsVisibleTo 屬æ§
æç´æ¥çæ¹æ³ï¼é©åç°¡å®æ æ³ï¼
// å¨ä¸»å°æ¡ä¸ç AssemblyInfo.cs æä»»ä½é¡å¥æªæ¡ä¸
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("YourProject.Tests")]
[assembly: InternalsVisibleTo("YourProject.IntegrationTests")]
åªé»ï¼
- ç°¡å®ç´æ¥
- ä¸éè¦é¡å¤å¥ä»¶
缺é»ï¼
- éè¦ç¡¬ç·¨ç¢¼çµä»¶å稱
- 簽署çµä»¶æéè¦å å«å ¬é°
æ¹æ³äºï¼å¨ csproj ä¸è¨å®
éé MSBuild 屬æ§è¨å®ï¼
<!-- YourProject.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>$(AssemblyName).Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project>
åªé»ï¼
- å¯ä»¥ä½¿ç¨ MSBuild è®æ¸
- éä¸ç®¡ç
æ¹æ³ä¸ï¼ä½¿ç¨ Meziantou.MSBuild.InternalsVisibleToï¼æ¨è¦ï¼
å°æ¼è¤éå°æ¡ï¼æ¨è¦ä½¿ç¨æ¤ NuGet å¥ä»¶ï¼
<!-- YourProject.csproj -->
<ItemGroup>
<PackageReference Include="Meziantou.MSBuild.InternalsVisibleTo" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
<InternalsVisibleTo Include="$(AssemblyName).IntegrationTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />
</ItemGroup>
åªé»ï¼
- èªåèç簽署çµä»¶çå ¬é°
- æ¯æ´ DynamicProxyGenAssembly2ï¼NSubstitute/Moqï¼
- å¯è®æ§é«
åèè³æºï¼
- Declaring InternalsVisibleTo in the csproj – Meziantou’s blog
- GitHub – meziantou/Meziantou.MSBuild.InternalsVisibleTo
Internal 測試ç風éªè©ä¼°
| è©ä¼°é¢å | 風éªç¨åº¦ | 說æ |
|---|---|---|
| å°è£æ§ç ´å£ | ä¸ç | å¢å äºæ¸¬è©¦å°å §é¨å¯¦ä½çä¾è³´ |
| éæ§é»å | é« | æ¹è® internal æå¡æå½±é¿æ¸¬è©¦ |
| ç¶è·ææ¬ | ä¸ç | éè¦åæ¥ç¶è·çç¢ä»£ç¢¼å測試代碼 |
| è¨è¨å質 | ä½ | 妿é度使ç¨ï¼å¯è½è¡¨ç¤ºè¨è¨æåé¡ |
ç§ææ¹æ³æ¸¬è©¦æè¡
æ¶µèæ±ºçæ¨¹ï¼æ¯å¦ææ¸¬è©¦ç§ææ¹æ³ï¼ãåå°æ¸¬è©¦ç§æå¯¦ä¾æ¹æ³èéæ
æ¹æ³ãReflectionTestHelper è¼å©é¡å¥å°è£ï¼ä»¥ååå°æ¸¬è©¦ç風éªèæä½³å¯¦è¸ã
ð 宿´ç¨å¼ç¢¼ç¯ä¾èæè¡ç´°ç¯è«åè references/private-method-testing.md
測試ååçè¨è¨æ¨¡å¼
çç¥æ¨¡å¼æ¹å坿¸¬è©¦æ§
å°è¤éçç§æéè¼¯éæ§çºçç¥æ¨¡å¼ï¼
éæ§åï¼é£ä»¥æ¸¬è©¦çè¨è¨
public class PricingService
{
public decimal CalculatePrice(Product product, Customer customer)
{
var basePrice = product.BasePrice;
var discount = CalculateDiscount(customer, product); // ç§ææ¹æ³
var tax = CalculateTax(product, customer.Location); // ç§ææ¹æ³
return basePrice - discount + tax;
}
private decimal CalculateDiscount(Customer customer, Product product)
{
// 20 è¡è¤éçææ£è¨ç®é輯
}
private decimal CalculateTax(Product product, Location location)
{
// 15 è¡è¤éçç¨
çè¨ç®
}
}
éæ§å¾ï¼ä½¿ç¨çç¥æ¨¡å¼
// çç¥ä»é¢
public interface IDiscountStrategy
{
decimal Calculate(Customer customer, Product product);
}
public interface ITaxStrategy
{
decimal Calculate(Product product, Location location);
}
// å
·é«çç¥å¯¦ä½
public class StandardDiscountStrategy : IDiscountStrategy
{
public decimal Calculate(Customer customer, Product product)
{
// ææ£é輯ç¾å¨æ¯å
¬éæ¹æ³ï¼å®¹ææ¸¬è©¦
if (customer.IsVIP)
return product.BasePrice * 0.1m;
return 0;
}
}
public class TaiwanTaxStrategy : ITaxStrategy
{
public decimal Calculate(Product product, Location location)
{
// ç¨
çé輯ç¾å¨æ¯å
¬éæ¹æ³ï¼å®¹ææ¸¬è©¦
return product.BasePrice * 0.05m;
}
}
// æ¹é²çæå
public class PricingService
{
private readonly IDiscountStrategy _discountStrategy;
private readonly ITaxStrategy _taxStrategy;
public PricingService(
IDiscountStrategy discountStrategy,
ITaxStrategy taxStrategy)
{
_discountStrategy = discountStrategy;
_taxStrategy = taxStrategy;
}
public decimal CalculatePrice(Product product, Customer customer)
{
var basePrice = product.BasePrice;
var discount = _discountStrategy.Calculate(customer, product);
var tax = _taxStrategy.Calculate(product, customer.Location);
return basePrice - discount + tax;
}
}
åªé»ï¼
- æ¯åçç¥å¯ä»¥ç¨ç«æ¸¬è©¦
- 符åéæ¾å°éåå
- ææ¼æ´å±æ°ççç¥
- æ¸å°å°åå°çä¾è³´
é¨å模æ¬ï¼Partial Mockï¼
ææéè¦æ¨¡æ¬é¡å¥çé¨åè¡çºï¼
// éè¦é¨å模æ¬çé¡å¥
public class DataProcessor
{
public ProcessResult Process(string input)
{
var validated = ValidateInput(input);
if (!validated)
return ProcessResult.InvalidInput();
var data = TransformData(input);
var saved = SaveData(data); // æ³æ¨¡æ¬éåæ¹æ³é¿å
實éè³æåº«æä½
return saved
? ProcessResult.Success()
: ProcessResult.Failed();
}
protected virtual bool SaveData(string data)
{
// 實éçè³æåº«æä½
return true;
}
private bool ValidateInput(string input) => !string.IsNullOrEmpty(input);
private string TransformData(string input) => input.ToUpper();
}
// 測試ç¨çåé¡å¥
public class TestableDataProcessor : DataProcessor
{
protected override bool SaveData(string data)
{
// 模æ¬å¯¦ä½ï¼é¿å
實éè³æåº«æä½
return true;
}
}
// 測試
[Fact]
public void Process_使ç¨é¨å模æ¬_ææåèç()
{
// Arrange
var processor = new TestableDataProcessor();
// Act
var result = processor.Process("test");
// Assert
result.Success.Should().BeTrue();
}
坦忱ºçæ¡æ¶
ä¸å±¤æ¬¡é¢¨éªè©ä¼°æ³
第ä¸å±¤ï¼è¨è¨å質è©ä¼°
åé¡ï¼éæ¯è¨è¨åé¡éæ¯æ¸¬è©¦åé¡ï¼
- ç§ææ¹æ³æ¯å¦éæ¼è¤éï¼ï¼> 10 è¡ï¼
- é¡å¥æ¯å¦æ¿æå¤åè·è²¬ï¼
- æ¯å¦å¯ä»¥æåçºç¨ç«é¡å¥ï¼
建è°è¡åï¼
- åªå èæ ®éæ§ï¼æåé¡å¥ãçç¥æ¨¡å¼ï¼
- ééæ¹åè¨è¨ä¾æ¹å坿¸¬è©¦æ§
第äºå±¤ï¼ç¶è·ææ¬è©ä¼°
åé¡ï¼æ¸¬è©¦æ¯å¦ææçºéæ§çé»ç¤ï¼
- 測試æ¯å¦ä¾è³´å¯¦ä½ç´°ç¯ï¼
- éæ§ææ¸¬è©¦æ¯å¦éè¦å¤§éä¿®æ¹ï¼
- æ¸¬è©¦å¤±æææ¯å¦é£ä»¥å®ä½åé¡ï¼
建è°è¡åï¼
- 妿ç¶è·ææ¬é«ï¼éæ°èæ ®æ¸¬è©¦çç¥
- èæ ®ééå ¬é API çæ´åæ¸¬è©¦
第ä¸å±¤ï¼å¹å¼ç¢åºè©ä¼°
åé¡ï¼æ¸¬è©¦å¸¶ä¾çå¹å¼æ¯å¦è¶ éææ¬ï¼
è©ä¼°æ¸¬è©¦å¹å¼ï¼
- â è½æå°çå¯¦çæ¥åé輯é¯èª¤
- â æä¾æ¸ æ¥ç失æè¨æ¯
- â å¨åçææ¬ä¸é·æç©©å®éè¡
建è°è¡åï¼
- 妿å¹å¼ä¸è¶³ï¼å°æ¾æ¿ä»£æ¸¬è©¦çç¥
- èæ ®æè½æ¸¬è©¦ãæ´å測試çå ¶ä»æ¹å¼
決çç©é£
| æ å¢ | 建è°åæ³ | çç± |
|---|---|---|
| ç°¡å®ç§ææ¹æ³ï¼< 10 è¡ï¼ | ééå ¬éæ¹æ³æ¸¬è©¦ | ç¶è·ææ¬ä½ |
| è¤éç§æé輯ï¼> 10 è¡ï¼ | éæ§çºç¨ç«é¡å¥ | æ¹åè¨è¨è坿¸¬è©¦æ§ |
| æ¡æ¶å §é¨æ¼ç®æ³ | ä½¿ç¨ InternalsVisibleTo | éè¦ç²¾ç¢ºæ¸¬è©¦å §é¨è¡çº |
| éºçç³»çµ±ç§ææ¹æ³ | èæ ®ä½¿ç¨åå°æ¸¬è©¦ | çæå §é£ä»¥éæ§ |
| å®å ¨ç¸éç§æé輯 | éæ§æä½¿ç¨åå°æ¸¬è©¦ | éè¦ç¨ç«é©èæ£ç¢ºæ§ |
| é »ç¹è®åç實ä½ç´°ç¯ | é¿å ç´æ¥æ¸¬è©¦ | 測試æè®å¾èå¼± |
DO – 建è°åæ³
-
è¨è¨åªå
- â åªå èæ ®éæ§è鿏¬è©¦ç§ææ¹æ³
- â 使ç¨ä¾è³´æ³¨å ¥åä»é¢æ½è±¡
- â æç¨çç¥æ¨¡å¼åé¢è¤éé輯
- â ä¿æå®ä¸è·è²¬åå
-
æ¸¬è©¦å ¬éè¡çº
- â å°æ³¨æ¸¬è©¦å ¬é API çè¡çº
- â ééå ¬éæ¹æ³éæ¥æ¸¬è©¦ç§æé輯
- â ä½¿ç¨æ´å測試è¦èè¤éæµç¨
-
ææºä½¿ç¨ InternalsVisibleTo
- â å ç¨æ¼æ¡æ¶æé¡å¥åº«éç¼
- â ä½¿ç¨ Meziantou.MSBuild.InternalsVisibleTo ç°¡åè¨å®
- â è¨éçºä½éè¦éæ¾ internal å¯è¦æ§
-
謹æ 使ç¨åå°
- â 建ç«è¼å©æ¹æ³å°è£åå°é輯
- â 卿¸¬è©¦åç¨±ä¸æ¨ç¤ºä½¿ç¨åå°
- â å®ææª¢è¦æ¯å¦å¯ä»¥éæ§
DON’T – é¿å åæ³
-
ä¸è¦éåº¦æ¸¬è©¦ç§ææ¹æ³
- â é¿å çºæ¯åç§ææ¹æ³å¯«æ¸¬è©¦
- â ä¸è¦æ¸¬è©¦ç°¡å®ç getter/setter
- â é¿å 測試ç´ç²¹çå§æ´¾å¼å«
-
ä¸è¦å¿½ç¥è¨è¨åé¡
- â ä¸è¦ææ¸¬è©¦ç¶ä½è¨è¨åé¡çæ¿ä»£æ¹æ¡
- â é¿å å çºæ¸¬è©¦èç ´å£å°è£
- â ä¸è¦è®æ¸¬è©¦é»ç¤éæ§
-
ä¸è¦ä¾è³´å¯¦ä½ç´°ç¯
- â é¿å æ¸¬è©¦ç§ææ¹æ³çå¼å«é åº
- â ä¸è¦é©èç§ææ¬ä½çå¼
- â é¿å æ¸¬è©¦é »ç¹è®åç實ä½ç´°ç¯
-
ä¸è¦æ¿«ç¨ InternalsVisibleTo
- â ä¸è¦çºæç¨å±¤ç¨å¼ç¢¼éæ¾ internal
- â é¿å éå¤çæ¸¬è©¦å°æ¡å¯è¦æ§
- â ä¸è¦ç¨å®å代é©ç¶çå ¬é API
ç¯ä¾åè
åè templates/ ç®éä¸ç宿´ç¯ä¾ï¼
internals-visible-to-examples.cs– InternalsVisibleTo è¨å®ç¯ä¾reflection-testing-examples.cs– åå°æ¸¬è©¦æè¡ç¯ä¾strategy-pattern-refactoring.cs– çç¥æ¨¡å¼éæ§ç¯ä¾
åèè³æº
åå§æç«
æ¬æè½å §å®¹æç èªãèæ´¾è»é«å·¥ç¨å¸«ç測試修練 – 30 å¤©ææ°ãç³»åæç« ï¼
- Day 09 – æ¸¬è©¦ç§æèå
§é¨æå¡ï¼Private è Internal çæ¸¬è©¦çç¥
- éµäººè³½æç« ï¼https://ithelp.ithome.com.tw/articles/10374866
- ç¯ä¾ç¨å¼ç¢¼ï¼https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day09
宿¹æä»¶
ç¸éæè½
unit-test-fundamentals– å®å 測試åºç¤nsubstitute-mocking– 測試æ¿èº«è模æ¬
æ¸¬è©¦æ¸ å®
å¨èçç§æèå §é¨æå¡æ¸¬è©¦æï¼ç¢ºèªä»¥ä¸æª¢æ¥é ç®ï¼
- å·²è©ä¼°æ¯å¦æè©²éæ§è鿏¬è©¦ç§ææ¹æ³
- Internal æå¡ç¢ºå¯¦éè¦éæ¾çµ¦æ¸¬è©¦å°æ¡
- 使ç¨é©ç¶ç InternalsVisibleTo è¨å®æ¹æ³
- åå°æ¸¬è©¦å·²ä½¿ç¨è¼å©æ¹æ³å°è£
- 測試åç¨±æ¸ æ¥æ¨ç¤ºæ¸¬è©¦é¡åï¼å¦ä½¿ç¨åå°ï¼
- çç¥æ¨¡å¼çè¨è¨æ¨¡å¼å·²èæ ®ç¨æ¼è¤éé輯
- æ¸¬è©¦ä¸ææçºéæ§çé»ç¤
- 測試æä¾çå¹å¼è¶ éç¶è·ææ¬
- æ²æé度ä¾è³´å¯¦ä½ç´°ç¯
- å®ææª¢è¦æ¸¬è©¦çç¥çé©åæ§