assertion-helper
4
总安装量
3
周安装量
#49375
全站排名
安装命令
npx skills add https://github.com/protagonistss/ithinku-plugins --skill assertion-helper
Agent 安装分布
opencode
2
claude-code
2
antigravity
2
gemini-cli
1
Skill 文档
æè¨å©ææè½
è¿ä¸ªæè½è´è´£åæå½æ°é»è¾å¹¶çæåéçæè¨ï¼ç¡®ä¿æµè¯è½å¤åç¡®éªè¯ä»£ç çè¡ä¸ºã
æè½è½å
1. é»è¾åæ
- åæå½æ°çè¿åå¼ç±»ååç»æ
- è¯å«å¯è½çè¾åºèå´
- æ£æµå¯ä½ç¨ï¼å¦ç¶æä¿®æ¹ãäºä»¶è§¦åï¼
- åæå¼å¸¸åé误æ åµ
2. æè¨çæ
- çæç²¾ç¡®çç±»åæè¨
- å建边çå¼éªè¯
- çææ·±åº¦å¯¹è±¡æ¯è¾
- æ·»å 彿°è°ç¨éªè¯
3. éªè¯çç¥
- 精确å¹é vs 模ç³å¹é
- é¨å对象å¹é
- æ°ç»å å«å顺åºéªè¯
- å¼å¸¸æè¨
ä½¿ç¨æ¹å¼
åºç¡æè¨çæ
// ä¸ºå½æ°çææè¨
await generateAssertions('calculateTotal', {
inputs: [{ items: [{ price: 10, qty: 2 }] }],
framework: 'vitest'
});
// çæç±»æ¹æ³çæè¨
await generateAssertions('UserService.createUser', {
context: 'class',
framework: 'vitest'
});
é«çº§é项
await generateAssertions(target, {
framework: 'pytest',
assertionStyle: 'strict', // 'strict' | 'loose' | 'custom'
includeSideEffects: true,
verifyMocks: true,
customAssertions: {
'shouldBeValidUser': 'expect(user).toMatchObject({ id: expect.any(Number), name: expect.any(String) })'
}
});
æè¨æ¨¡æ¿
Jestæè¨æ¨¡æ¿
// åºç¡ç±»åæè¨
{{#if returns}}
{{#eq returns.type 'string'}}
expect(result).toBeString();
expect(result).toHaveLength({{#if returns.length}}{{returns.length}}{{else}}greaterThan(0){{/if}});
{{/eq}}
{{#eq returns.type 'number'}}
expect(result).toBeNumber();
expect(result).{{#if returns.range}}toBeGreaterThanOrEqual({{returns.range.min}});
expect(result).toBeLessThanOrEqual({{returns.range.max}});{{/if}}
{{/eq}}
{{#eq returns.type 'boolean'}}
expect(result).toBeBoolean();
{{/eq}}
{{#eq returns.type 'array'}}
expect(result).toBeArray();
expect(result).toHaveLength({{#if returns.length}}{{returns.length}}{{else}}greaterThan(0){{/if}});
{{#if returns.items}}
expect(result[0]).toMatchObject({{{json returns.items}}});
{{/if}}
{{/eq}}
{{#eq returns.type 'object'}}
expect(result).toBeObject();
{{#if returns.properties}}
expect(result).toMatchObject({
{{#each returns.properties}}
{{@key}}: {{#if this.type}}expect.any({{camelCase this.type}}){{else}}{{{json this}}}{{/if}},
{{/each}}
});
{{/if}}
{{/eq}}
{{/if}}
// 弿¥æè¨
{{#if isAsync}}
await expect(asyncFunction()).resolves.toBe(expectedValue);
await expect(asyncFunction()).resolves.not.toThrow();
// é误æè¨
await expect(asyncFunction()).rejects.toThrow('Expected error message');
{{/if}}
// Mockéªè¯æè¨
{{#each mocks}}
{{#if this.shouldBeCalled}}
expect({{name}}).toHaveBeenCalled();
expect({{name}}).toHaveBeenCalledTimes({{this.callCount}});
{{#if this.withParams}}
expect({{name}}).toHaveBeenCalledWith({{#each this.withParams}}{{this}}{{#unless @last}}, {{/unless}}{{/each}});
{{/if}}
{{else}}
expect({{name}}).not.toHaveBeenCalled();
{{/if}}
{{/each}}
// å¯ä½ç¨éªè¯
{{#if sideEffects}}
{{#each sideEffects}}
{{#eq this.type 'console'}}
// Consoleè¾åºéªè¯
expect(console.{{this.method}}).toHaveBeenCalledWith({{{json this.args}}});
{{/eq}}
{{#eq this.type 'event'}}
// äºä»¶è§¦åéªè¯
expect(eventEmitter.emit).toHaveBeenCalledWith('{{this.eventName}}', {{#if this.payload}}{{{json this.payload}}}{{else}}expect.any(Object){{/if}});
{{/eq}}
{{#eq this.type 'state'}}
// ç¶æååéªè¯
expect(component.state.{{this.property}}).toBe({{{json this.value}}});
{{/eq}}
{{/each}}
{{/if}}
Vitestæè¨æ¨¡æ¿
import { expect } from 'vitest';
// ç±»åæè¨
{{#if returns}}
expect(result).toBeDefined();
{{#eq returns.type 'string'}}
expect(typeof result).toBe('string');
{{/eq}}
{{#eq returns.type 'number'}}
expect(typeof result).toBe('number');
{{/eq}}
{{#eq returns.type 'object'}}
expect(typeof result).toBe('object');
expect(result).not.toBeNull();
{{/eq}}
{{/if}}
// 精确å¹é
expect(result).toEqual(expectedValue);
// é¨åå¹é
expect(result).toMatchObject(partialExpected);
// æ°ç»æè¨
expect(result).toContain(expectedItem);
expect(result).toHaveLength(expectedLength);
// å¼å¸¸æè¨
expect(() => functionThatThrows()).toThrow();
expect(() => functionThatThrows()).toThrowError('Error message');
Pythonæè¨æ¨¡æ¿
# åºç¡æè¨
assert result is not None
{{#eq returns.type 'string'}}
assert isinstance(result, str)
assert len(result) > 0
{{/eq}}
{{#eq returns.type 'number'}}
assert isinstance(result, (int, float))
assert result >= {{#if returns.min}}{{returns.min}}{{else}}0{{/if}}
{{/eq}}
{{#eq returns.type 'list'}}
assert isinstance(result, list)
assert len(result) > 0
{{#if returns.items}}
assert result[0] == {{returns.items}}
{{/if}}
{{/eq}}
{{#eq returns.type 'dict'}}
assert isinstance(result, dict)
{{#if returns.keys}}
for key in {{json returns.keys}}:
assert key in result
{{/if}}
{{/eq}}
# å¼å¸¸æè¨
with pytest.raises({{errorType}}, match="{{errorMessage}}"):
function_that_raises()
# Mockéªè¯
{{#each mocks}}
{{#if this.shouldBeCalled}}
assert {{name}}.called
assert {{name}}.call_count == {{this.callCount}}
{{#if this.withArgs}}
{{name}}.assert_called_with({{#each this.withArgs}}{{this}}{{#unless @last}}, {{/unless}}{{/each}})
{{/if}}
{{else}}
assert not {{name}}.called
{{/if}}
{{/each}}
æè¨çæçç¥
1. åºäºè¿åå¼çæè¨
class AssertionGenerator {
static generateForReturn(returnType: TypeInfo, value?: any): string[] {
const assertions: string[] = [];
// ç±»åæè¨
switch (returnType.type) {
case 'string':
assertions.push('expect(typeof result).toBe("string")');
if (value) {
assertions.push(`expect(result).toBe("${value}")`);
}
break;
case 'number':
assertions.push('expect(typeof result).toBe("number")');
if (returnType.constraints?.min !== undefined) {
assertions.push(`expect(result).toBeGreaterThanOrEqual(${returnType.constraints.min})`);
}
if (returnType.constraints?.max !== undefined) {
assertions.push(`expect(result).toBeLessThanOrEqual(${returnType.constraints.max})`);
}
break;
case 'boolean':
assertions.push('expect(typeof result).toBe("boolean")');
break;
case 'array':
assertions.push('expect(Array.isArray(result)).toBe(true)');
if (value?.length) {
assertions.push(`expect(result).toHaveLength(${value.length})`);
}
break;
case 'object':
assertions.push('expect(typeof result).toBe("object")');
assertions.push('expect(result).not.toBeNull()');
if (value) {
assertions.push(`expect(result).toMatchObject(${JSON.stringify(value)})`);
}
break;
}
return assertions;
}
static generateBoundaryTests(
functionName: string,
params: ParameterInfo[]
): string[] {
const tests: string[] = [];
params.forEach(param => {
if (param.constraints?.min !== undefined) {
tests.push(`
it('should handle minimum value for ${param.name}', () => {
const result = ${functionName}(${param.constraints.min});
expect(result).toBeDefined();
});
`);
}
if (param.constraints?.max !== undefined) {
tests.push(`
it('should handle maximum value for ${param.name}', () => {
const result = ${functionName}(${param.constraints.max});
expect(result).toBeDefined();
});
`);
}
});
return tests;
}
}
2. é误å¤çæè¨
// éè¯¯åºæ¯æè¨çæ
function generateErrorAssertions(functionInfo) {
const assertions = [];
// å¯è½çé误æ¡ä»¶
if (functionInfo.params.some(p => p.required)) {
assertions.push(`
it('should throw error when required parameter is missing', () => {
expect(() => ${functionInfo.name}()).toThrow();
expect(() => ${functionInfo.name}(null)).toThrow();
});
`);
}
if (functionInfo.params.some(p => p.type === 'number')) {
assertions.push(`
it('should handle invalid number inputs', () => {
expect(() => ${functionInfo.name}(NaN)).toThrow();
expect(() => ${functionInfo.name}(Infinity)).toThrow();
});
`);
}
return assertions;
}
3. 弿¥æä½æè¨
// 弿¥å½æ°æè¨çæ
function generateAsyncAssertions(functionInfo) {
return `
describe('async behavior', () => {
it('should resolve with expected value', async () => {
const result = await ${functionInfo.name}(${generateValidParams(functionInfo)});
expect(result).toBeDefined();
${generateReturnAssertions(functionInfo.returnType)}
});
it('should handle rejection', async () => {
// æµè¯æç»æ
åµ
await expect(${functionInfo.name}(${generateErrorParams()}))
.rejects.toThrow();
});
it('should handle timeout', async () => {
// 妿æè¶
æ¶é
ç½®
await expect(${functionInfo.name}(${generateSlowParams()}))
.rejects.toThrow('timeout');
});
});
`;
}
å¤æåºæ¯å¤ç
1. 深度对象æ¯è¾
// 深度å¹é
æè¨
function generateDeepObjectAssertions(obj: any, path: string = ''): string[] {
const assertions: string[] = [];
for (const [key, value] of Object.entries(obj)) {
const currentPath = path ? `${path}.${key}` : key;
if (typeof value === 'object' && value !== null) {
if (Array.isArray(value)) {
assertions.push(`
expect(result.${currentPath}).toBeArray();
expect(result.${currentPath}).toHaveLength(${value.length});
`);
} else {
assertions.push(`
expect(result.${currentPath}).toBeObject();
`);
// éå½å¤çåµå¥å¯¹è±¡
assertions.push(...generateDeepObjectAssertions(value, currentPath));
}
} else {
assertions.push(`
expect(result.${currentPath}).toBe(${JSON.stringify(value)});
`);
}
}
return assertions;
}
2. 彿°è°ç¨éªè¯
// Mock彿°è°ç¨æè¨
function generateMockAssertions(mocks: MockInfo[]): string[] {
return mocks.map(mock => {
const assertions: string[] = [];
if (mock.expectCall) {
assertions.push(`expect(${mock.name}).toHaveBeenCalled();`);
if (mock.expectedCallCount) {
assertions.push(
`expect(${mock.name}).toHaveBeenCalledTimes(${mock.expectedCallCount});`
);
}
if (mock.expectedArgs) {
assertions.push(
`expect(${mock.name}).toHaveBeenCalledWith(${mock.expectedArgs.join(', ')});`
);
}
if (mock.expectedCalls) {
mock.expectedCalls.forEach((call, index) => {
assertions.push(
`expect(${mock.name}).toHaveBeenNthCalledWith(${index + 1}, ${call.join(', ')});`
);
});
}
} else {
assertions.push(`expect(${mock.name}).not.toHaveBeenCalled();`);
}
return assertions.join('\n');
});
}
3. å¯ä½ç¨éªè¯
// å¯ä½ç¨æè¨çæ
function generateSideEffectAssertions(sideEffects: SideEffect[]): string[] {
return sideEffects.map(effect => {
switch (effect.type) {
case 'console':
return `expect(console.${effect.method}).toHaveBeenCalledWith(${JSON.stringify(effect.args)});`;
case 'state':
return `expect(${effect.target}).toBe(${JSON.stringify(effect.value)});`;
case 'event':
return `expect(${effect.emitter}.emit).toHaveBeenCalledWith('${effect.event}', ${JSON.stringify(effect.payload)});`;
case 'storage':
return `expect(localStorage.getItem('${effect.key}')).toBe('${effect.value}');`;
default:
return '';
}
}).filter(Boolean);
}
æä½³å®è·µ
1. æè¨éæ©åå
- ä½¿ç¨æå ·ä½çæè¨
- é¿å è¿åº¦èå¼±çæè¨
- å ³æ³¨è¡ä¸ºèéå®ç°
- ä¸ä¸ªæµè¯ä¸ä¸ªå ³æ³¨ç¹
2. æè¨å¯è¯»æ§
- ä½¿ç¨ææä¹çåéå
- æ·»å æè¿°æ§æ³¨é
- ç»ç»ç¸å ³æè¨
- 使ç¨èªå®ä¹å¹é å¨
3. æµè¯è¦ç
- è¦çæ£å¸¸è·¯å¾
- æµè¯è¾¹çæ¡ä»¶
- éªè¯é误å¤ç
- æ£æ¥å¯ä½ç¨
4. æè¨ç»´æ¤
- ä¿ææè¨ç¬ç«
- é¿å é夿è¨
- 使ç¨è¾ å©å½æ°
- å®ææ´æ°æè¨