Skip to content

Commit

Permalink
feat(unit-test): Improve chat script for creating unit tests (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
skarllot authored Jan 8, 2025
1 parent 77f117b commit 8daa3d1
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Options:

This command will generate a unit test for the specified code file, optionally using an example unit test file as a reference.

#### Updating Unit Tests
### Updating Unit Tests

To update an existing unit test with code changes, use the `unittest update` command:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,23 @@ public sealed class CreateUnitTestChatDefinition(
Instruction.StepInstruction.Of(
"Create unit tests for the specified code"),
Instruction.StepInstruction.Of(
"Ensure the unit test cover every path"),
"Ensure the unit tests cover every possible execution path in the code"),
Instruction.StepInstruction.Of(
"Ensure the unit test does not create any mutants on mutation analysis"),
"Ensure the unit tests are sensitive to mutations in the source code. " +
"When mutation testing introduces small changes to the implementation (mutants), " +
"at least one test should fail. " +
"This verifies that the tests can detect potential bugs or behavioral changes."),
Instruction.StepInstruction.Of(
"Remove any redundant tests while maintaining full coverage"),
Instruction.CodeExtractInstruction.Of(
CodeResponseKey,
"Return only the unit tests code inside a code block (```)"),
"Return the entire final version of the unit tests file content inside a code block (```)"),
Instruction.StepInstruction.Of(
"Where the new file for the unit tests should be located " +
"according to language and project standards?"),
Instruction.JsonConvertInstruction.Of(
JsonResponseKey,
"""
Reply the file path in a valid JSON format.
The schema of the JSON object must be:
""",
"Reply the file path in a valid JSON format. The schema of the JSON object must be:",
FilePathResponse.Schema),
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
"role": "user",
"content": "Return only the unit tests code inside a code block (\u0060\u0060\u0060)"
},
{
"role": "assistant",
"content": "<NEW INSTRUCTION RESPONSE>"
},
{
"role": "assistant",
"content": "Here\u0027s the unit tests code inside a code block:\n\n\u0060\u0060\u0060csharp\nusing System.Collections.Generic;\nusing System.IO.Abstractions;\nusing System.IO.Abstractions.TestingHelpers;\nusing Raiqub.LlmTools.FlowPair.Agent.Operations.CreateUnitTest;\nusing Raiqub.LlmTools.FlowPair.Agent.Operations.CreateUnitTest.v1;\nusing Raiqub.LlmTools.FlowPair.Agent.Operations.Login;\nusing Raiqub.LlmTools.FlowPair.Agent.Services;\nusing Raiqub.LlmTools.FlowPair.Chats.Models;\nusing Raiqub.LlmTools.FlowPair.Chats.Services;\nusing Raiqub.LlmTools.FlowPair.Common;\nusing Raiqub.LlmTools.FlowPair.LocalFileSystem.Services;\nusing FluentAssertions;\nusing NSubstitute;\nusing Spectre.Console;\nusing Xunit;\n\nnamespace Raiqub.LlmTools.FlowPair.Tests.Agent.Operations.CreateUnitTest\n{\n public class CreateUnitTestCommandTests\n {\n private readonly IAnsiConsole _console;\n private readonly MockFileSystem _fileSystem;\n private readonly ICreateUnitTestChatDefinition _chatDefinition;\n private readonly IWorkingDirectoryWalker _workingDirectoryWalker;\n private readonly IProjectFilesMessageFactory _projectFilesMessageFactory;\n private readonly IDirectoryStructureMessageFactory _directoryStructureMessageFactory;\n private readonly ILoginUseCase _loginUseCase;\n private readonly IChatService _chatService;\n\n public CreateUnitTestCommandTests()\n {\n _console = Substitute.For\u003CIAnsiConsole\u003E();\n _fileSystem = new MockFileSystem();\n _chatDefinition = Substitute.For\u003CICreateUnitTestChatDefinition\u003E();\n _workingDirectoryWalker = Substitute.For\u003CIWorkingDirectoryWalker\u003E();\n _projectFilesMessageFactory = Substitute.For\u003CIProjectFilesMessageFactory\u003E();\n _directoryStructureMessageFactory = Substitute.For\u003CIDirectoryStructureMessageFactory\u003E();\n _loginUseCase = Substitute.For\u003CILoginUseCase\u003E();\n _chatService = Substitute.For\u003CIChatService\u003E();\n }\n\n private CreateUnitTestCommand CreateCommand() =\u003E\n new CreateUnitTestCommand(\n _console,\n _fileSystem,\n _chatDefinition,\n _workingDirectoryWalker,\n _projectFilesMessageFactory,\n _directoryStructureMessageFactory,\n _loginUseCase,\n _chatService);\n\n [Fact]\n public void Execute_FileDoesNotExist_ReturnsError()\n {\n var command = CreateCommand();\n var nonExistentFilePath = \u0022/path/to/nonexistent/file.cs\u0022;\n\n var result = command.Execute(nonExistentFilePath);\n\n result.Should().Be(1);\n _console.Received(1).MarkupLine(Arg.Is\u003Cstring\u003E(s =\u003E s.Contains(\u0022The specified file does not exist\u0022)));\n _workingDirectoryWalker.DidNotReceiveWithAnyArgs().TryFindRepositoryRoot(default);\n }\n\n [Fact]\n public void Execute_ExampleFileDoesNotExist_ReturnsError()\n {\n var command = CreateCommand();\n var filePath = \u0022/path/to/existing/file.cs\u0022;\n var nonExistentExampleFilePath = \u0022/path/to/nonexistent/example.cs\u0022;\n\n _fileSystem.AddFile(filePath, new MockFileData(\u0022Some content\u0022));\n\n var result = command.Execute(filePath, nonExistentExampleFilePath);\n\n result.Should().Be(2);\n _console.Received(1).MarkupLine(Arg.Is\u003Cstring\u003E(s =\u003E s.Contains(\u0022The specified example file does not exist\u0022)));\n _workingDirectoryWalker.DidNotReceiveWithAnyArgs().TryFindRepositoryRoot(default);\n }\n\n [Fact]\n public void Execute_RepositoryRootNotFound_ReturnsError()\n {\n var command = CreateCommand();\n var filePath = \u0022/path/to/existing/file.cs\u0022;\n\n _fileSystem.AddFile(filePath, new MockFileData(\u0022Some content\u0022));\n _workingDirectoryWalker.TryFindRepositoryRoot(Arg.Any\u003Cstring\u003E()).Returns(Result\u003CIDirectoryInfo\u003E.Err());\n\n var result = command.Execute(filePath);\n\n result.Should().Be(3);\n _console.Received(1).MarkupLine(Arg.Is\u003Cstring\u003E(s =\u003E s.Contains(\u0022Could not locate Git repository\u0022)));\n _loginUseCase.DidNotReceiveWithAnyArgs().Execute(default);\n }\n\n [Fact]\n public void Execute_LoginFails_ReturnsError()\n {\n var command = CreateCommand();\n var filePath = \u0022/path/to/existing/file.cs\u0022;\n\n _fileSystem.AddFile(filePath, new MockFileData(\u0022Some content\u0022));\n _workingDirectoryWalker.TryFindRepositoryRoot(Arg.Any\u003Cstring\u003E()).Returns(Result\u003CIDirectoryInfo\u003E.Ok(_fileSystem.DirectoryInfo.New(\u0022/repo/root\u0022)));\n _loginUseCase.Execute(true).Returns(1);\n\n var result = command.Execute(filePath);\n\n result.Should().Be(4);\n _chatService.DidNotReceiveWithAnyArgs().Run(default, default, default, default);\n }\n\n [Fact]\n public void Execute_ChatServiceFails_ReturnsError()\n {\n var command = CreateCommand();\n var filePath = \u0022/repo/root/src/file.cs\u0022;\n\n _fileSystem.AddFile(filePath, new MockFileData(\u0022Some content\u0022));\n _workingDirectoryWalker.TryFindRepositoryRoot(Arg.Any\u003Cstring\u003E()).Returns(Result\u003CIDirectoryInfo\u003E.Ok(_fileSystem.DirectoryInfo.New(\u0022/repo/root\u0022)));\n _loginUseCase.Execute(true).Returns(0);\n _chatService.Run(Arg.Any\u003CProgress\u003E(), Arg.Any\u003CLlmModelType\u003E(), Arg.Any\u003CICreateUnitTestChatDefinition\u003E(), Arg.Any\u003CIEnumerable\u003CMessage\u003E\u003E())\n .Returns(Result\u003CCreateUnitTestResponse\u003E.Err(\u0022Chat service error\u0022));\n\n var result = command.Execute(filePath);\n\n result.Should().Be(5);\n _console.Received(1).MarkupLineInterpolated(Arg.Is\u003CFormattableString\u003E(f =\u003E f.ToString().Contains(\u0022Chat service error\u0022)));\n _fileSystem.AllFiles.Should().NotContain(f =\u003E f.Contains(\u0022Tests.cs\u0022));\n }\n\n [Fact]\n public void Execute_Success_WithoutExampleFile_CreatesUnitTestFile()\n {\n var command = CreateCommand();\n var filePath = \u0022/repo/root/src/file.cs\u0022;\n var expectedTestFilePath = \u0022/repo/root/tests/FileTests.cs\u0022;\n\n _fileSystem.AddFile(filePath, new MockFileData(\u0022public class File {}\u0022));\n\n var rootDir = _fileSystem.DirectoryInfo.New(\u0022/repo/root\u0022);\n _workingDirectoryWalker.TryFindRepositoryRoot(Arg.Any\u003Cstring\u003E()).Returns(Result\u003CIDirectoryInfo\u003E.Ok(rootDir));\n _loginUseCase.Execute(true).Returns(0);\n\n var response = new CreateUnitTestResponse\n {\n FilePath = \u0022tests/FileTests.cs\u0022,\n Content = \u0022public class FileTests {}\u0022\n };\n _chatService.Run(Arg.Any\u003CProgress\u003E(), Arg.Any\u003CLlmModelType\u003E(), Arg.Any\u003CICreateUnitTestChatDefinition\u003E(), Arg.Any\u003CIEnumerable\u003CMessage\u003E\u003E())\n .Returns(Result\u003CCreateUnitTestResponse\u003E.Ok(response));\n\n var result = command.Execute(filePath);\n\n result.Should().Be(0);\n _fileSystem.File.Exists(expectedTestFilePath).Should().BeTrue();\n _fileSystem.File.ReadAllText(expectedTestFilePath).Should().Be(\u0022public class FileTests {}\u0022);\n _console.Received(1).MarkupLineInterpolated(Arg.Is\u003CFormattableString\u003E(f =\u003E f.ToString().Contains(\u0022File created: tests/FileTests.cs\u0022)));\n }\n\n [Fact]\n public void Execute_Success_WithExampleFile_CreatesUnitTestFile()\n {\n var command = CreateCommand();\n var filePath = \u0022/repo/root/src/file.cs\u0022;\n var exampleFilePath = \u0022/repo/root/tests/example_test.cs\u0022;\n var expectedTestFilePath = \u0022/repo/root/tests/FileTests.cs\u0022;\n\n _fileSystem.AddFile(filePath, new MockFileData(\u0022public class File {}\u0022));\n _fileSystem.AddFile(exampleFilePath, new MockFileData(\u0022public class ExampleTest {}\u0022));\n\n var rootDir = _fileSystem.DirectoryInfo.New(\u0022/repo/root\u0022);\n _workingDirectoryWalker.TryFindRepositoryRoot(Arg.Any\u003Cstring\u003E()).Returns(Result\u003CIDirectoryInfo\u003E.Ok(rootDir));\n _loginUseCase.Execute(true).Returns(0);\n\n var response = new CreateUnitTestResponse\n {\n FilePath = \u0022tests/FileTests.cs\u0022,\n Content = \u0022public class FileTests {}\u0022\n };\n _chatService.Run(Arg.Any\u003CProgress\u003E(), Arg.Any\u003CLlmModelType\u003E(), Arg.Any\u003CICreateUnitTestChatDefinition\u003E(), Arg.Any\u003CIEnumerable\u003CMessage\u003E\u003E())\n .Returns(Result\u003CCreateUnitTestResponse\u003E.Ok(response));\n\n var result = command.Execute(filePath, exampleFilePath);\n\n result.Should().Be(0);\n _fileSystem.File.Exists(expectedTestFilePath).Should().BeTrue();\n _fileSystem.File.ReadAllText(expectedTestFilePath).Should().Be(\u0022public class FileTests {}\u0022);\n _console.Received(1).MarkupLineInterpolated(Arg.Is\u003CFormattableString\u003E(f =\u003E f.ToString().Contains(\u0022File created: tests/FileTests.cs\u0022)));\n }\n\n [Fact]\n public void Execute_Success_CreatesNecessaryDirectories()\n {\n var command = CreateCommand();\n var filePath = \u0022/repo/root/src/file.cs\u0022;\n var expectedTestFilePath = \u0022/repo/root/tests/nested/directory/FileTests.cs\u0022;\n\n _fileSystem.AddFile(filePath, new MockFileData(\u0022public class File {}\u0022));\n\n var rootDir = _fileSystem.DirectoryInfo.New(\u0022/repo/root\u0022);\n _workingDirectoryWalker.TryFindRepositoryRoot(Arg.Any\u003Cstring\u003E()).Returns(Result\u003CIDirectoryInfo\u003E.Ok(rootDir));\n _loginUseCase.Execute(true).Returns(0);\n\n var response = new CreateUnitTestResponse\n {\n FilePath = \u0022tests/nested/directory/FileTests.cs\u0022,\n Content = \u0022public class FileTests {}\u0022\n };\n _chatService.Run(Arg.Any\u003CProgress\u003E(), Arg.Any\u003CLlmModelType\u003E(), Arg.Any\u003CICreateUnitTestChatDefinition\u003E(), Arg.Any\u003CIEnumerable\u003CMessage\u003E\u003E())\n .Returns(Result\u003CCreateUnitTestResponse\u003E.Ok(response));\n\n var result = command.Execute(filePath);\n\n result.Should().Be(0);\n _fileSystem.Directory.Exists(\u0022/repo/root/tests/nested/directory\u0022).Should().BeTrue();\n _fileSystem.File.Exists(expectedTestFilePath).Should().BeTrue();\n _fileSystem.File.ReadAllText(expectedTestFilePath).Should().Be(\u0022public class FileTests {}\u0022);\n _console.Received(1).MarkupLineInterpolated(Arg.Is\u003CFormattableString\u003E(f =\u003E f.ToString().Contains(\u0022File created: tests/nested/directory/FileTests.cs\u0022)));\n }\n\n [Fact]\n public void Execute_InitialMessagesBuiltCorrectly()\n {\n var command = CreateCommand();\n var filePath = \u0022/repo/root/src/file.cs\u0022;\n var exampleFilePath = \u0022/repo/root/tests/example_test.cs\u0022;\n\n _fileSystem.AddFile(filePath, new MockFileData(\u0022public class File {}\u0022));\n _fileSystem.AddFile(exampleFilePath, new MockFileData(\u0022public class ExampleTest {}\u0022));\n\n var rootDir = _fileSystem.DirectoryInfo.New(\u0022/repo/root\u0022);\n _workingDirectoryWalker.TryFindRepositoryRoot(Arg.Any\u003Cstring\u003E()).Returns(Result\u003CIDirectoryInfo\u003E.Ok(rootDir));\n _loginUseCase.Execute(true).Returns(0);\n\n var response = new CreateUnitTestResponse\n {\n FilePath = \u0022tests/FileTests.cs\u0022,\n Content = \u0022public class FileTests {}\u0022\n };\n _chatService.Run(Arg.Any\u003CProgress\u003E(), Arg.Any\u003CLlmModelType\u003E(), Arg.Any\u003CICreateUnitTestChatDefinition\u003E(), Arg.Any\u003CIEnumerable\u003CMessage\u003E\u003E())\n .Returns(Result\u003CCreateUnitTestResponse\u003E.Ok(response));\n\n command.Execute(filePath, exampleFilePath);\n\n _projectFilesMessageFactory.Received(1).CreateWithProjectFilesContent(rootDir);\n _directoryStructureMessageFactory.Received(1).CreateWithRepositoryStructure(rootDir);\n _chatService.Received(1).Run(\n Arg.Any\u003CProgress\u003E(),\n LlmModelType.Claude35Sonnet,\n _chatDefinition,\n Arg.Is\u003CIEnumerable\u003CMessage\u003E\u003E(messages =\u003E \n messages.Count() == 4 \u0026\u0026\n messages.Any(m =\u003E m.SenderRole == SenderRole.User \u0026\u0026 m.Content.Contains(\u0022Unit tests example:\u0022)) \u0026\u0026\n messages.Any(m =\u003E m.SenderRole == SenderRole.User \u0026\u0026 m.Content.Contains(\u0022The source file to be tested is located at\u0022))\n )\n );\n }\n }\n}\n\u0060\u0060\u0060"
Expand Down

0 comments on commit 8daa3d1

Please sign in to comment.