programing tip

단위 테스트 파일 I / O

itbloger 2020. 12. 5. 09:22
반응형

단위 테스트 파일 I / O


Stack Overflow에서 기존의 단위 테스트 관련 스레드를 읽어 보니 파일 I / O 작업 단위 테스트 방법에 대한 명확한 답변을 찾을 수 없었습니다. 나는 이전에 장점을 알고 있었지만 먼저 테스트를 작성하는 데 어려움을 겪고 있었기 때문에 최근에야 단위 테스트를 조사하기 시작했습니다. NUnit과 Rhino Mocks를 사용하도록 프로젝트를 설정했으며 그 뒤에있는 개념을 이해하고 있지만 Mock Objects를 사용하는 방법을 이해하는 데 약간의 어려움이 있습니다.

구체적으로 대답하고 싶은 두 가지 질문이 있습니다. 첫째, 단위 테스트 파일 I / O 작업에 적합한 방법은 무엇입니까? 둘째, 단위 테스트에 대해 배우려는 시도에서 종속성 주입을 발견했습니다. Ninject를 설정하고 작동 한 후 단위 테스트 내에서 DI를 사용해야할지 아니면 객체를 직접 인스턴스화해야할지 궁금했습니다.


Rhino MocksSystemWrapper를 사용하여 TDD에 대한 자습서를 확인하십시오 .

SystemWrapper는 File, FileInfo, Directory, DirectoryInfo, ... 등 많은 System.IO 클래스를 래핑합니다. 전체 목록을 볼 수 있습니다 .

이 튜토리얼에서는 MbUnit으로 테스트하는 방법을 보여주고 있지만 NUnit의 경우와 정확히 동일합니다.

테스트는 다음과 같이 보일 것입니다.

[Test]
public void When_try_to_create_directory_that_already_exists_return_false()
{
    var directoryInfoStub = MockRepository.GenerateStub<IDirectoryInfoWrap>();
    directoryInfoStub.Stub(x => x.Exists).Return(true);
    Assert.AreEqual(false, new DirectoryInfoSample().TryToCreateDirectory(directoryInfoStub));

    directoryInfoStub.AssertWasNotCalled(x => x.Create());
}

파일 시스템을 테스트 할 때 반드시 해야 할 일은 가지 가 아닙니다 . 사실 상황에 따라 할 수있는 일이 몇 가지 있습니다.

질문해야 할 질문은 다음과 같습니다. 내가 무엇을 테스트하고 있습니까?

  • 파일 시스템이 작동합니까? 매우 익숙하지 않은 운영 체제를 사용하지 않는 한 테스트 필요가 없습니다 . 예를 들어 단순히 파일을 저장하라는 명령을 내리는 경우 실제로 저장되는지 확인하는 테스트를 작성하는 것은 시간 낭비입니다.

  • 파일이 올바른 위치에 저장된다는 것입니까? 글쎄, 올바른 장소가 무엇인지 어떻게 알 수 있습니까? 아마도 경로와 파일 이름을 결합하는 코드가있을 것입니다. 이것은 쉽게 테스트 할 수있는 코드입니다. 입력은 두 개의 문자열이고 출력은 두 문자열을 사용하여 구성된 유효한 파일 위치 인 문자열이어야합니다.

  • 디렉토리에서 올바른 파일 세트를 얻었습니까? 파일 시스템을 실제로 테스트하는 파일 가져 오기 클래스에 대한 테스트를 작성해야 할 것입니다. 그러나 변경되지 않는 파일이있는 테스트 디렉토리를 사용해야합니다. 이 테스트는 파일 시스템에 따라 다르기 때문에 실제 단위 테스트가 아니기 때문에 통합 테스트 프로젝트에 넣어야합니다.

  • 하지만 내가받은 파일로 뭔가를해야합니다. 들어 테스트, 당신은 사용해야 가짜 파일-게터 클래스를. 가짜는 하드 코딩 된 파일 목록을 반환해야합니다. 당신이 사용하는 경우 실제 파일 게터와 실제 파일 프로세서를 하나의 테스트 실패를 일으키는 알 수 없습니다. 따라서 테스트에서 파일 프로세서 클래스는 가짜 파일 가져 오기 클래스를 사용해야합니다. 파일 프로세서 클래스는 파일 가져 오기 인터페이스를 가져야합니다 . 실제 코드에서는 실제 파일 가져 오기 도구를 전달합니다. 테스트 코드에서 알려진 정적 목록을 반환하는 가짜 file-getter를 전달합니다.

기본 원칙은 다음과 같습니다.

  • 파일 시스템 자체를 테스트하지 않을 때는 인터페이스 뒤에 숨겨진 가짜 파일 시스템을 사용하십시오.
  • 실제 파일 작업을 테스트해야하는 경우
    • 테스트를 단위 테스트가 아닌 통합 테스트로 표시합니다.
    • 항상 변경되지 않은 상태로있을 지정된 테스트 디렉토리, 파일 세트 등이 있으므로 파일 지향 통합 테스트가 일관되게 통과 할 수 있습니다.

Q1 :

여기에는 세 가지 옵션이 있습니다.

옵션 1 : 그것과 함께 살아라.

(예 : P)

옵션 2 : 필요한 경우 약간의 추상화를 만듭니다.

테스트중인 메서드에서 파일 I / O (File.ReadAllBytes 등)를 수행하는 대신 IO가 외부에서 수행되고 대신 스트림이 전달되도록 변경할 수 있습니다.

public class MyClassThatOpensFiles
{
    public bool IsDataValid(string filename)
    {
        var filebytes = File.ReadAllBytes(filename);
        DoSomethingWithFile(fileBytes);
    }
}

될 것이다

// File IO is done outside prior to this call, so in the level 
// above the caller would open a file and pass in the stream
public class MyClassThatNoLongerOpensFiles
{
    public bool IsDataValid(Stream stream) // or byte[]
    {
        DoSomethingWithStreamInstead(stream); // can be a memorystream in tests
    }
}

이 접근 방식은 절충안입니다. 첫째, 예, 더 테스트 가능합니다. 그러나 복잡성에 약간의 추가를 위해 테스트 가능성을 교환합니다. 이것은 유지 보수 가능성과 작성해야하는 코드의 양에 영향을 미칠 수 있으며 테스트 문제를 한 수준 위로 이동할 수 있습니다.

그러나 내 경험상 이것은 완전히 래핑 된 파일 시스템을 사용하지 않고도 중요한 논리를 일반화하고 테스트 가능하게 만들 수 있으므로 훌륭하고 균형 잡힌 접근 방식입니다. 즉, 나머지는 그대로두고 정말 관심있는 부분을 일반화 할 수 있습니다.

옵션 3 : 전체 파일 시스템 래핑

한 단계 더 나아가 파일 시스템을 조롱하는 것이 유효한 접근 방법이 될 수 있습니다. 그것은 당신이 기꺼이 살기를 원하는 정도에 달려 있습니다.

나는 전에이 길을 갔다. 래핑 된 파일 시스템 구현이 있었지만 결국 삭제했습니다. API에는 미묘한 차이가 있었기 때문에 모든 곳에 주입해야했고, API를 사용하는 많은 클래스가 나에게 그다지 중요하지 않았기 때문에 궁극적으로 약간의 이득을 위해 추가적인 고통이있었습니다. IoC 컨테이너를 사용하거나 중요하고 테스트 속도가 빨라야하는 내용을 작성했다면이 문제를 고수했을 수 있습니다. 이러한 모든 옵션과 마찬가지로 귀하의 마일리지는 다를 수 있습니다.

IoC 컨테이너 질문 :

테스트를 수동으로 두 배로 주입하십시오. 반복적 인 작업을 많이해야하는 경우 테스트에서 설정 / 공장 방법을 사용하십시오. 테스트를 위해 IoC 컨테이너를 사용하는 것은 극도로 과잉입니다! 그래도 두 번째 질문을 이해하지 못할 수도 있습니다.


System.IO.AbstractionsNuGet 패키지를 사용합니다 .

이 웹 사이트에는 테스트를 위해 주입을 사용하는 방법을 보여주는 멋진 예제가 있습니다. http://dontcodetired.com/blog/post/Unit-Testing-C-File-Access-Code-with-SystemIOAbstractions

다음은 웹 사이트에서 복사 한 코드의 사본입니다.

using System.IO;
using System.IO.Abstractions;

namespace ConsoleApp1
{
    public class FileProcessorTestable
    {
        private readonly IFileSystem _fileSystem;

        public FileProcessorTestable() : this (new FileSystem()) {}

        public FileProcessorTestable(IFileSystem fileSystem)
        {
            _fileSystem = fileSystem;
        }

        public void ConvertFirstLineToUpper(string inputFilePath)
        {
            string outputFilePath = Path.ChangeExtension(inputFilePath, ".out.txt");

            using (StreamReader inputReader = _fileSystem.File.OpenText(inputFilePath))
            using (StreamWriter outputWriter = _fileSystem.File.CreateText(outputFilePath))
            {
                bool isFirstLine = true;

                while (!inputReader.EndOfStream)
                {
                    string line = inputReader.ReadLine();

                    if (isFirstLine)
                    {
                        line = line.ToUpperInvariant();
                        isFirstLine = false;
                    }

                    outputWriter.WriteLine(line);
                }
            }
        }
    }
}





using System.IO.Abstractions.TestingHelpers;
using Xunit;

namespace XUnitTestProject1
{
    public class FileProcessorTestableShould
    {
        [Fact]
        public void ConvertFirstLine()
        {
            var mockFileSystem = new MockFileSystem();

            var mockInputFile = new MockFileData("line1\nline2\nline3");

            mockFileSystem.AddFile(@"C:\temp\in.txt", mockInputFile);

            var sut = new FileProcessorTestable(mockFileSystem);
            sut.ConvertFirstLineToUpper(@"C:\temp\in.txt");

            MockFileData mockOutputFile = mockFileSystem.GetFile(@"C:\temp\in.out.txt");

            string[] outputLines = mockOutputFile.TextContents.SplitLines();

            Assert.Equal("LINE1", outputLines[0]);
            Assert.Equal("line2", outputLines[1]);
            Assert.Equal("line3", outputLines[2]);
        }
    }
}

현재 저는 의존성 주입을 통해 IFileSystem 객체를 사용합니다. 프로덕션 코드의 경우 래퍼 클래스가 인터페이스를 구현하여 필요한 특정 IO 함수를 래핑합니다. 테스트 할 때 null 또는 stub 구현을 만들어 테스트중인 클래스에 제공 할 수 있습니다. 테스트를 거친 수업은 현명하지 않습니다.


2012 년부터는 코드베이스가 이미 동결 되었기 때문에 코드베이스를 변경할 필요없이 Microsoft Fakes 를 사용하여이를 수행 할 수 있습니다 .

먼저 System.dll 또는 다른 패키지에 대한 가짜 어셈블리생성 한 다음 예상되는 반환을 다음과 같이 모의합니다.

using Microsoft.QualityTools.Testing.Fakes;
...
using (ShimsContext.Create())
{
     System.IO.Fakes.ShimFile.ExistsString = (p) => true;
     System.IO.Fakes.ShimFile.ReadAllTextString = (p) => "your file content";

      //Your methods to test
}

참고URL : https://stackoverflow.com/questions/1528134/unit-testing-file-i-o

반응형