You can think of the incoming serial buffer as a bucket of water under a dripping tap. Next to the bucket is a cup.
Water arrives through the tap and drips into the bucket. When you want to get some water, if there is enough water in the bucket, you dip the cup in and remove a cupful of water. If there isn't enough water you don't get anything in your cup.
If you don't dip the cup in for a while too much water will dribble into the bucket and it will overflow all over the floor.
It's exactly the same with serial data. It arrives through the UART hardware and is placed into the "bucket" by the UART receive interrupt. If you don't dip your cup in fast enough (Serial.read()) the bucket will overflow and any new data that arrives will spill onto the metaphorical floor.
To test this out you can just send more data to the Arduino than will fit into the serial buffer (typically 64 bytes) without reading any. Then after you have finished flooding the buffer you can read what is actually in the buffer and send it back again (maybe formatted nicely) so you can see what has actually ended up in the buffer and what hasn't.
One quick and dirty test would be to send the raw bytes 0 to 255 to the Arduino, then after a delay read them back and display the DEC values for them (Serial.print(Serial.read(), DEC))
) to see exactly which bytes arrived and which didn't (hint: you should see the numbers 0 to 63 if the buffer is 64 bytes big).
For the transmit buffer it's slightly different. You can think of it more as a bucket with a small hole in the bottom.
You pour data (water) in the top at whatever speed you want, and it trickles out of the bottom at a pre-set rate (the baud rate - the size of the hole). You don't like wet messes though, so if the bucket is full you wait a while before putting more water into it. Just like with serial - if the transmit buffer is full any attempts to add to it will block until data has been sent out by the UART transmit interrupt.
HardwareSerial.h