본문 바로가기

Programing/테스트

[NUnit] 사례TDD - 완벽하지 않은 테스트

확장메서드를 이용해서 byte 배열을 Int32로 변환하는 정적메서드를 만들었다.

모양은 아래와 같았다. (빅엔디안이라서 Reverse로 순서를 바꾸어주었다.)

        public static Int32 FromBytes(this byte[] value)

        {

            byte[] t = new byte[value.Length];

            Array.Copy(value, t, value.Length);

            Array.Reverse(t);

            return BitConverter.ToInt32(t, 0);

        }


위에 대한 테스트는 아래와 같았다.

        [Test]

        public void FromBytesTest()

        {

            Byte[] b = new Byte[] { 0x29, 0x4A, 0xA6, 0xA5 };

            Assert.AreEqual(0x294AA6A5, b.FromBytes());

        }


물론 테스트는 아래와 같이 성공했다. (NUnit)



나중에 이 유틸리티 메서드를 이용해서 짤 때 이 유틸리티가 완벽하지 않음을 깨달았다.

왜냐하면 아래와 같이 2 바이트의 배열에서 Int32로 변환하려고 사용을 했기 때문이다.

byte[] errorCode = new byte[2];

byte[] data = parser.ReceivedData();    // 6 bytes : [A,B,C,D,E,F]

Buffer.BlockCopy(data, 4, errorCode, 0, 2); // [E,F]를취함

int errorCode = errorCode.FromBytes();

예외가 발생하는데, 기존 코드는 4바이트만 처리할 수 있었던 것인데 2바이트에도 사용을 해서 발생한 문제였다.


5바이트~0바이트까지 테스트를 해보면 아래와 같이 실패한다.


수정된 테스트 코드는 아래와 같다. 사실 단순 노동적이다. FromBytes_4bytes는 기존에 달랑 있던 테스트인데 이름을 수정했다.

        [Test]

        public void FromBytes_5bytes()

        {

            Byte[] b = new Byte[] { 0x29, 0x4A, 0xA6, 0xA5, 0xA7 };

            Assert.AreEqual(0x294AA6A5, b.FromBytes(), "초과하는 바이트는 앞의 바이트 4개를 취한다.");

        }


        [Test]

        public void FromBytes_4bytes()

        {

            Byte[] b = new Byte[] { 0x29, 0x4A, 0xA6, 0xA5 };

            Assert.AreEqual(0x294AA6A5, b.FromBytes());

        }


        [Test]

        public void FromBytes_3bytes()

        {

            Byte[] b = new Byte[] { 0x29, 0x4A, 0xA6 };

            Assert.AreEqual(0x294AA6, b.FromBytes());

        }


        [Test]

        public void FromBytes_2bytes()

        {

            Byte[] b = new Byte[] { 0x29, 0x4A };

            Assert.AreEqual(0x294A, b.FromBytes());

        }


        [Test]

        public void FromBytes_1byte()

        {

            Byte[] b = new Byte[] { 0x29 };

            Assert.AreEqual(0x29, b.FromBytes());

        }


        [Test]

        public void FromBytes_0byte()

        {

            Byte[] b = new Byte[] { };

            Assert.AreEqual(0, b.FromBytes());

        }

사실 4바이트 초과는 처음에는 생각을 하지 못했는데, System.ArgumentException로 예외를 던질까 생각을 했는데, BitConverter.ToInt32 메서드가 4바이트를 초과하는 것에 대해서는 그냥 앞의 4바이트만 취하는 동작을 보고 동일하게 처리하였다.


수정된 코드는 아래와 같다.

        public static Int32 FromBytes(this byte[] value)

        {

            int howManyCopy = (value.Length >= Int32Length ? Int32Length : value.Length);

            int whereToCopy = (value.Length >= Int32Length ? 0 : Int32Length - value.Length);

            byte[] t = new byte[Int32Length];

            Buffer.BlockCopy(value, 0, t, whereToCopy, howManyCopy);

            Array.Reverse(t);

            return BitConverter.ToInt32(t, 0);

        }


위의 코드로 멈출까 생각을 했는데, Buffer.BlockCopy에 인자로 들어갈 복사할 위치와 크기가 인라인으로 구현을 해서 나중에 보면 직관적이지 않기 때문에 메소드 Extract 리팩토링을 적용해서 최종 코드는 아래와 같아졌다.

물론 테스트로 기능이 바뀌지 않았음을 검증했다. 이런 것이 단위 테스트가 주는 자신감이다.

        public static Int32 FromBytes(this byte[] value)

        {

            int whereToCopy = GetDstOffset(Int32Length, value.Length);

            int howManyCopy = GetCountWithin(Int32Length, value.Length);

            

            byte[] t = new byte[Int32Length];

            Buffer.BlockCopy(value, 0, t, whereToCopy, howManyCopy);

            Array.Reverse(t);

            return BitConverter.ToInt32(t, 0);

        }


        private static int GetDstOffset(int max, int wish)

        {

            if (wish < max)         // 크기가 작을 때는

            {

                return (max - wish);// 앞에 0으로 채워진다

            }

            else

            {

                return 0;           // 꽉 채우고, 뒤는 버려짐

            }

        }


        private static int GetCountWithin(int max, int wish)

        {

            if (wish < max)

            {

                return wish;

            }

            else

            {

                return max;

            }

        }